Both zero-dependency (stdlib http.server + urllib), consistent with the project's no-dependency-tree stance for a security daemon. Web dashboard (enodia_sentinel/web.py + static/dashboard.html): - read-only JSON API over the log dir: /api/status, /api/alerts, /api/alerts/<id>, /api/events - bearer-token auth (constant-time; header or ?token=), required on non- loopback binds, auto-generated + persisted (0600) when unset - binds the host's Tailscale IP by default (auto-detected), reachable from the tailnet but not the LAN/internet - self-contained dark SPA: severity cards, live alert list, full snapshot viewer; 10s auto-refresh - path-traversal-safe alert lookup; `enodia-sentinel web` subcommand; daemon now writes a pidfile so the dashboard can show live status - hardened enodia-sentinel-web.service (read-only, no caps) Phone push (enodia_sentinel/notify/): - pluggable backends — ntfy, Pushover, generic webhook — each separating a pure build() (unit-tested, no network) from send() - a backend turns on when its config keys are set; pushes gated by notify_min_severity; severity → per-service priority/tags - fired from snapshot.capture on worker threads, errors swallowed - desktop notify-send retained Tests: +16 (9 web incl. a real-server 401/200 auth test, 7 notify request-build cases). 55/55 pass. Live end-to-end verified: daemon → alert → API → page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
33 lines
994 B
Desktop File
33 lines
994 B
Desktop File
[Unit]
|
|
Description=Enodia Sentinel - read-only web dashboard
|
|
After=network-online.target enodia-sentinel.service
|
|
Wants=network-online.target
|
|
Documentation=https://github.com/Enodia/enodia-sentinel
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=/usr/bin/env enodia-sentinel web
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
# The dashboard only READS the log dir and binds a port. No special privileges:
|
|
# lock it down hard. It does not need root, but reads root-owned snapshots, so
|
|
# it runs as root with an otherwise minimal surface and no capabilities.
|
|
ProtectSystem=strict
|
|
# Writable only so it can persist an auto-generated token; set web_token in the
|
|
# config to keep this purely read-only.
|
|
ReadWritePaths=/var/log/enodia-sentinel
|
|
ProtectHome=yes
|
|
NoNewPrivileges=yes
|
|
ProtectKernelTunables=yes
|
|
ProtectKernelModules=yes
|
|
ProtectControlGroups=yes
|
|
RestrictSUIDSGID=yes
|
|
MemoryDenyWriteExecute=yes
|
|
LockPersonality=yes
|
|
RestrictNamespaces=yes
|
|
CapabilityBoundingSet=
|
|
AmbientCapabilities=
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|