bvandeusenandClaude Opus 5.5 a805e1d539
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m20s
CI / unit (push) Failing after 1m32s
CI / publish (push) Skipped
fix(host_agent): every Docker read is bounded, and the agent reports its own footprint (#5764)
#5764 "The agent reads a container's whole log backlog into memory, on its
main loop, with no overall deadline". Docker03's 1.13.0 agent went silent
for a day at 2.1 GB and 46 h of CPU, then ignored SIGTERM.

- One shared reader, _docker_get: each answer has an overall deadline and a
  byte cap, not only a per-recv timeout. A trickling or endless body is cut
  off (15 s / 32 MiB for JSON, 5 s / 4 MiB for a log read).
- Container logs: since the cursor but only the newest 2,000 lines, all
  within 10 s. A note says when older lines were skipped, and a container
  whose answer is too big skips ahead to now instead of re-reading its
  backlog every interval.
- The agent reports its own RSS and CPU; Steward records agent_rss_bytes
  and agent_cpu_pct, so a runaway agent shows before it goes silent.

Agent 1.15.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:01:45 -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/core/backups.md Steward's daily backup to an S3 bucket (Fabled Archives), what it holds, and steward restore
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/plugins/prometheus.md Prometheus plugin: scraping any /metrics endpoint, how series are stored, the Fabled Archives recipe
docs/plugins/prometheus-widgets.md Prometheus widget templates: writing, sharing and copying one in, every block, the format reference
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%