Collapse the three former check types into a single core `Monitor` entity
with one management surface (/monitors), one result table (monitor_results),
and a single scheduled task. Every type can now watch a free-standing custom
destination (optional host_id) — not just a registered Host.
- models: Monitor + MonitorResult replace PingResult/DnsResult; Host loses its
ping/dns facet columns (now Monitor rows linked by host_id).
- checks: monitors/{ping,dns,http}.py pure probes + runner.run_monitor
dispatcher; one monitor_check scheduler with a per-monitor due-filter.
- status: single monitor_status_source replaces the three sources.
- UI: /monitors blueprint (type-aware add/edit/list/widget); host hub shows a
host's linked monitors + "add monitor for this host"; nav + widget registry
+ alert metric catalog rewired. http plugin folded into core and removed.
- migration 0022 merges the http branch, data-migrates host facets +
http_monitors + all three result histories, drops the old tables/columns.
Resolves the per-host ping/dns auto-attach issue (#275): monitors are now
explicit, never auto-added to every host.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Jg27rgypiW2efULXJDtMC
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 |