- TypeScript 88.2%
- Shell 9.2%
- CSS 1.4%
- Go Template 0.6%
- JavaScript 0.5%
- Other 0.1%
|
|
||
|---|---|---|
| .superpowers/brainstorm/13605-1779295108/state | ||
| app | ||
| components | ||
| deploy | ||
| docs | ||
| hooks | ||
| lib | ||
| ops-agent | ||
| prisma | ||
| public | ||
| reviews | ||
| scripts | ||
| test | ||
| vendor | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| .gitmodules | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| components.json | ||
| Dockerfile | ||
| next.config.ts | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.mjs | ||
| prisma.config.ts | ||
| proxy.ts | ||
| README.md | ||
| task-2-fix-rereview.md | ||
| tsconfig.json | ||
| vitest.config.ts | ||
Ops Dashboard
Single-user ops dashboard voor jp-visser.nl.
See docs/runbooks/ for setup, deployment, and operational procedures.
Installation
Prerequisites
- Docker + Docker Compose (plugin) installed on the host
- A PostgreSQL service named
postgresalready running in the same Compose stack - The repository cloned to
/srv/scrum4me/ops-dashboard /srv/scrum4me/compose/docker-compose.ymlas the shared Compose file
1. Configure environment
cp deploy/ops-dashboard.env.example /srv/scrum4me/ops-dashboard/.env
# Edit /srv/scrum4me/ops-dashboard/.env — set DATABASE_URL, AUTH_SECRET, etc.
2. Install ops-agent
sudo deploy/ops-agent/setup.sh
This creates the ops-agent system user, installs /opt/ops-agent, generates
/etc/ops-agent/secret, and enables the systemd unit.
For an existing install, add or update the Docker inspection module:
sudo deploy/ops-agent/install-docker-inspection-module.sh
The installer restarts ops-agent by default. When batching this with a larger
setup/update, run it with OPS_AGENT_SKIP_RESTART=1 and restart the service once
after all changes are in place.
Docker inspection uses /etc/ops-agent/container-inspection.yml as the allowlist
for containers, expected env keys, and config file paths. Standard views show
missing expected keys, redacted logs, and redacted config content; raw env values
and config files are only returned by explicit reveal actions.
Copy the generated secret into the web-app env file:
sudo cat /etc/ops-agent/secret
# Paste the value as OPS_AGENT_SECRET= in /srv/scrum4me/ops-dashboard/.env
3. Build and start the dashboard
sudo docker compose -f /srv/scrum4me/compose/docker-compose.yml build ops-dashboard
sudo docker compose -f /srv/scrum4me/compose/docker-compose.yml up -d ops-dashboard
The dashboard is now reachable on 127.0.0.1:3001 (proxied by Caddy).
4. Install the self-update script
sudo deploy/ops-dashboard-updater/install.sh
To enable scheduled updates (daily at 03:00):
sudo systemctl enable --now ops-dashboard-updater.timer
To trigger a manual update via SSH:
sudo systemctl start ops-dashboard-updater.service
# or:
sudo /opt/ops-dashboard-updater/update.sh
Never trigger updates through the dashboard UI — the script restarts the container that serves the UI.
5. Register in the fleet (heartbeat)
Keep this host visible/"online" in the shared fleet registry via a systemd timer that POSTs to the in-container heartbeat endpoint (the containerized app can't run the ts-node CLI heartbeat used on native hosts like the Mac):
sudo cp deploy/ops-dashboard-heartbeat/ops-dashboard-heartbeat.service /etc/systemd/system/
sudo cp deploy/ops-dashboard-heartbeat/ops-dashboard-heartbeat.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now ops-dashboard-heartbeat.timer
sudo systemctl start ops-dashboard-heartbeat.service # register once now
The endpoint reads INSTANCE_SLUG and INSTANCE_BASE_URL (and optional
INSTANCE_CAPABILITIES, INSTANCE_HOSTNAME, INSTANCE_TAILSCALE_IP, APP_VERSION)
from the container env and authenticates with OPS_INGEST_SECRET (the same secret
the worker-logs ingest uses). In a container os.hostname() is the container id —
set INSTANCE_HOSTNAME (e.g. scrum4me-server) in the env for a readable name.
Configuration
| File | Purpose |
|---|---|
/srv/scrum4me/ops-dashboard/.env |
Web-app environment (DATABASE_URL, AUTH_SECRET, OPS_AGENT_SECRET, …) |
/etc/ops-agent/secret |
Shared HMAC secret between web-app and ops-agent |
/etc/ops-agent/commands.yml |
Whitelist of commands the ops-agent may run |
/etc/ops-agent/container-inspection.yml |
Allowed containers, env keys and configfile paths for Docker inspection/reveal; review and customize before use |
/etc/ops-agent/flows/ |
Flow YAML files (backup, caddy reload, etc.) |
/srv/scrum4me/compose/docker-compose.yml |
Main Compose file (add ops-dashboard fragment from deploy/) |
deploy/ops-agent/commands.darwin.yml |
macOS command whitelist (git, processes, MCP) for the Mac instance |
deploy/ops-dashboard-heartbeat/ |
systemd timer + service that POST the fleet heartbeat (keeps this host "online") |
Ops-agent auth
The web-app communicates with the ops-agent via a shared secret stored in
/etc/ops-agent/secret (mode 0640, owner root:ops-agent).
- The ops-agent reads the secret at startup via
OPS_AGENT_SECRET_PATH. - Every request from the web-app carries
Authorization: Bearer <secret>. - The agent validates using a constant-time comparison to prevent timing attacks.
- The web-app reads the secret value from the
OPS_AGENT_SECRETenvironment variable.
Fail-closed behavior
The agent refuses to start when no secret file is present at OPS_AGENT_SECRET_PATH
(it logs the reason and exits non-zero). For local development without a secret,
set OPS_AGENT_ALLOW_INSECURE=true to explicitly run in an unauthenticated mode —
never use this in a deployed environment. Even at runtime, a missing secret makes
every request return 503 (not a silent bypass).
Secret rotation procedure
- Generate a new secret on the server:
openssl rand -hex 32 | sudo tee /etc/ops-agent/secret sudo chown root:ops-agent /etc/ops-agent/secret sudo chmod 0640 /etc/ops-agent/secret - Update
OPS_AGENT_SECRETin the web-app's environment file (/srv/scrum4me/ops-dashboard/.env) with the new value. - Restart both services:
sudo systemctl restart ops-agent sudo docker compose -f /srv/scrum4me/compose/docker-compose.yml restart ops-dashboard - Verify the dashboard is operational and that
systemctl status ops-agentshows the service running without errors.