bvandeusenandClaude Opus 5.5 29be9e3590
CI / lint (push) Successful in 4s
CI / integration (push) Successful in 1m33s
CI / unit (push) Failing after 1m59s
CI / publish (push) Skipped
feat(docker): the service and container pages say each fact once (#5310)
Service page:
- The header carries the verdict, mode, stack, image and when it was reported.
  'In trouble since' shows only while the service is in trouble; once recovered,
  'Last failed X ago · its logs'.
- The failure card shows only while the service is down, degraded or crash-looping.
- The tile strip and the kv card are gone.
- One Tasks table: running tasks first with CPU, memory and since when, then
  Swarm's record. The node is named as its Steward host throughout, and
  'N failed in 24h' sits in the caption.

Container page:
- Same shape: header verdict, a 'Last exit' card only while it is stopped
  after a failing exit, set-up and I/O in one kv card, and lifecycle as a table.
- A swarm task's breadcrumb goes through its service.

Both pages:
- Resource history is the one fragment (_usage_history.html): now and peak in
  cores and bytes, drawn with a floor of 1 core / 1 GiB (sparkline_svg floor=),
  so idle noise draws flat.
- The container page's old % sparklines, its private _sparkline and the unused
  bucket()/container_history() helpers are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:33:25 -04:00

Steward

A self-hosted network monitoring and infrastructure management hub for home servers. Steward gives you a single pane of glass over your hosts, services, and automation — with live-updating dashboards, alerting, and Ansible integration.


What It Does

  • Ping monitoring — TCP/ICMP probes with live latency history and configurable thresholds
  • DNS monitoring — resolution checks with optional expected-IP validation
  • Ansible — browse playbooks, trigger runs, stream output live
  • Alerting — threshold-based rules against any monitored metric, with email and webhook (Discord-compatible) notifications
  • Plugins — extend with additional data sources; Traefik metrics included out of the box
  • Dashboard widgets — all monitors and plugins contribute live-updating widgets

No JavaScript framework. No build step. No external workers or message brokers.


Quick Start — Docker

cp .env.example .env
# Set STEWARD_DATABASE_URL in .env

docker compose up -d

Open http://localhost:5000. On first run you'll be prompted to create the admin account.

This compose file builds the image locally and bind-mounts the source for live editing. To run a persistent instance off the published CI image instead, see Remote dev instance below.


Remote dev instance

For a long-lived instance on a remote server that tracks the dev CI line, use compose.deploy.yml. It pulls the published image (git.fabledsword.com/bvandeusen/steward:dev) rather than building, runs no source bind-mounts, and persists data in named volumes.

One-time prerequisite — registry push token (on the Git host). CI publishes the image, so the FabledSteward repo needs a push credential. The injected GITHUB_TOKEN lacks write:package, so create a Forgejo token scoped read:package + write:package and add it as the repo Actions secret REGISTRY_TOKEN. Until this exists, the publish CI job 401s and no :dev image is produced.

Standup (on the server):

# 1. Authenticate to the registry (read:package token is enough to pull)
docker login git.fabledsword.com -u <user>

# 2. Configure secrets
cp .env.example .env
# Set POSTGRES_PASSWORD. Leave STEWARD_SECRET_KEY unset to let the app
# generate + persist one on the /data volume.

# 3. Pull + boot
docker compose -f compose.deploy.yml up -d

Open http://<server>:5000 and create the admin account on first run.

Update to the latest dev build:

docker compose -f compose.deploy.yml pull
docker compose -f compose.deploy.yml up -d

Every push to dev that goes CI-green publishes a fresh :dev (and an immutable :<commit-sha> for rollback). To pin a specific commit, change the image: tag in compose.deploy.yml to :<sha>.


Quick Start — Bare Metal

Requires Python 3.11+ and PostgreSQL.

python -m venv .venv && source .venv/bin/activate
pip install -e .

cp config.example.yaml config.yaml
# Edit config.yaml — set database.url at minimum

steward --host 0.0.0.0 --port 5000

ICMP ping requires CAP_NET_RAW or the setuid ping binary. TCP mode (the default) needs no elevated privileges.


Configuration

Only two things must be set to run the app:

What How
Database URL STEWARD_DATABASE_URL env var or database.url in config.yaml
Secret key Auto-generated on first run and saved to /data/secret.key

Everything else — SMTP, webhooks, monitor intervals, Ansible sources, plugin settings — is configured through the web UI at /settings/.


Plugins

Drop a directory into plugins/ and enable it via the Settings UI. The Traefik plugin is included:

plugins/
└── traefik/      ← included; enable in Settings

See docs/plugins/ for a full plugin development guide.


Documentation

Document Contents
docs/architecture.md How the app works: startup sequence, routing, scheduler, DB pattern
docs/core/configuration.md Bootstrap config, DB-backed settings, all setting keys and defaults
docs/core/monitors.md Ping and DNS monitors: probe logic, metrics emitted, data models
docs/core/alerting.md Alert rules, state machine, notification channels, template variables
docs/core/ansible.md Playbook sources, run lifecycle, SSE streaming
docs/plugins/overview.md Plugin system: how loading works, plugin.yaml schema, required exports
docs/plugins/writing-a-plugin.md Step-by-step plugin development guide
docs/plugins/traefik.md Traefik plugin: config, metrics, alert rule examples
docs/reference/code-map.md Where every key function, model, and route lives in the codebase
S
Description
No description provided
Readme
6.1 MiB
Languages
Python 69.8%
HTML 29.8%
Jinja 0.2%
Dockerfile 0.1%