Skip to content

Deployment

The root operational entrypoints are run.sh, status.sh, and stop.sh. Dashboard and container work has stricter rules than normal local code changes: builds, recreates, restarts, and duplicate deployable service starts require explicit operator approval in the current thread.

AGENT_REPO_GUIDE.md and CLAUDE.md now also require canonical-root discipline for deployable private-compose mutations: run steelmoth_private build/recreate/start commands from /home/mareekkk/steelmoth. Active implementation work belongs on task branches in WIP worktrees (default /home/mareekkk/steelmoth-wip), while canonical main remains the deploy source of truth.

For normal staging restarts, the repo guide points to scripts/compose-up.sh as the no-build path. Runtime-token handling is ephemeral by default: when no non-empty STEELMOTH_RUNTIME_AGENT_VAULT_TOKEN_FILE exists, the compose start path execs scripts/with-ephemeral-runtime-token.sh, which prepares a temporary token source from an existing mounted Docker secret, systemd credential, or one-shot stdin and deletes the source after compose returns. If a stopped runtime container still points at a deleted temp source, the startup script removes that stopped container before compose recreates it. Explicit rebuilds use scripts/compose-rebuild-changed.sh or scripts/compose-rebuild.sh. Dashboard deployment uses scripts/compose-dashboard-deploy.sh. That script checks dashboard package version drift against compose env, optionally builds the dashboard image, force-recreates the private dashboard service, recreates the standalone public dashboard container, waits for both health endpoints, and compares the private, public, and public-domain CSS fingerprints. When recreating the public dashboard container, it can override public auth routing/cookie env with STEELMOTH_PUBLIC_DASHBOARD_AUTH_URL and STEELMOTH_PUBLIC_DASHBOARD_AUTH_COOKIE_DOMAIN.

The three canonical deploy scripts — scripts/compose-runtime-deploy.sh, scripts/compose-dashboard-deploy.sh, and scripts/compose-provisioner-deploy.sh — run a capability-parity preflight (INV-028, steelmoth_runtime.capability_parity) before touching any container: if the operator brain has a capability that the owlet client template, the client dashboard flags, the /integrations card↔capability map, or the gsuite client OAuth wiring were never given, the deploy refuses with a message naming the missing wiring. Operator-only powers are exempt via owlet_profile; deliberate deferrals live in reviewed ledgers inside the comparator. The guard fails only on confirmed drift — unreadable inputs warn and continue — and STEELMOTH_SKIP_CAPABILITY_PARITY=1 is the loud emergency skip. The same comparator runs strict in CI (invariant-fitness-tests), so drift is normally caught at PR time. prepare_public_broker_env() in the dashboard deploy script force-sets ALL STEELMOTH_DASHBOARD_CLIENT_*_ENABLED integration flags the dashboard code consumes (24 as of 2026-07-12) onto the public container — previously only 5, which hid the connect form for 19 integrations from enrolled clients.

The public dashboard container resolves Auth.js secrets at startup through the prontera broker. The deploy script mounts a one-shot Agent Vault token volume; the dashboard entrypoint removes the token file immediately, attempts to mint a temporary session, and falls back to using the same proxy token in memory only for read-only broker secret fetches when Agent Vault refuses session minting. This keeps the runtime identity read-only after the Agent Vault 0.22 cutover while still loading AUTH_SECRET and AUTHENTIK_CLIENT_SECRET before the Next.js server handles login actions.

The default private startup path now includes both notifier and reminder-daemon profiles via scripts/compose-up.sh, and the default service list includes steelmoth-reminder-daemon, so reminder polling stays on during normal stack bring-up.

Worker-profile startup also brings up steelmoth-morpheus-init, which prepares the shared Morpheus workspace volume. It prefers the configured GitHub remote, falls back to the read-only host Git checkout when available, and can bootstrap a local Git worktree from the host snapshot when the remote has not been seeded yet. The helper keeps running as a periodic fetch loop after setup.

Public docs exposure is represented by ops/nginx/docs.steelmoth.com.conf. That file is a host-level nginx config that proxies docs.steelmoth.com to dashboard wiki routes using the shared dashboard proxy snippet. It is not a container deployment and does not by itself create DNS records, certificates, or a running dashboard/API backend.

The current Steelmoth cutover also has host-level nginx templates for the new public domains. ops/nginx/steelmoth.ai.conf proxies steelmoth.ai / www.steelmoth.ai to the marketing website and proxies app.steelmoth.ai to the standalone public dashboard container on 127.0.0.1:3012. The live container is named stm-dashboard-app and needs both the egress network for prontera Agent Vault DNS and the internal Steelmoth network for runtime API access. ops/nginx/steelmoth.app.conf is a redirect alias only and sends steelmoth.app / www.steelmoth.app traffic to app.steelmoth.ai. Prontera terminates public Authentik login at auth.steelmoth.ai with ops/nginx/auth.steelmoth.ai.conf; that config blocks /if/admin/ and /api/v3/admin/ on the public hostname and redirects auth.steelmoth.app to the .ai auth origin. Authentik admin access is the separate Tailnet-only ops/nginx/auth.tailnet.conf vhost on https://prontera.sole-augmented.ts.net:4443/if/admin/.

Private compose container_name overrides now use the stm-* prefix for the Steelmoth stack. The default running private baseline is stm-data-postgres, stm-data-redis, stm-memory-memlink, stm-runtime-api, stm-worker-sidecars, stm-worker-notifications, and stm-worker-reminders; optional profile containers follow the same prefix. Do not remove or rename stopped Steelmoth-era stm-* containers while working on this Steelmoth stack.

Private compose now also defines steelmoth-llm-usage-proxy, a loopback Anthropic-format proxy on port 4250 by default. It reuses the runtime image and serves steelmoth_runtime.llm_usage.proxy_app:app to record external-tool token usage without storing caller credentials. Because it is a deployable service, starting, rebuilding, or recreating it still requires current operator approval under the container mutation rules.

The observability stack (ADR 0226, phased in 2026-07-24) added a metrics plane without changing the canonical running baseline. steelmoth_runtime.metrics mounts /metrics plus a low-cardinality RED middleware on the brain (web_chat/app.py) and the LLM usage proxy; the Telegram dispatcher serves an inline /metrics (its purity invariant forbids importing the shared module); /metrics is auth-exempt for scraping (added to the auth public-paths alongside the health endpoints, memlink_scope_auth._PUBLIC_PATHS) and carries no secrets/PII by label discipline (ADR 0226 §4). Five off-the-shelf exporters (node, cAdvisor, Postgres, Redis, blackbox) live in compose.steelmoth.private.yml behind the observability profile — which NO canonical deploy script activates (compose-up/compose-rebuild use the private/worker/browser-pool profiles), so the running stack’s convergence is unchanged. Their scrape ports bind to the host tailnet IP only: the juno observability VPS (tag:observability) scrapes, the public internet cannot. The Postgres exporter uses the read-only monitor role, and its DSN comes from an env file kept outside the repo (PG_EXPORTER_DSN default-empty so unrelated compose invocations still interpolate). Phase 2 double-binds the runtime app port on the tailnet IP alongside the loopback binding so juno can scrape the brain’s /metrics and blackbox-probe the health endpoints; every loopback consumer is untouched. Phase 3 added the juno-side Grafana dashboards and Prometheus alert rules plus POST /v1/observability/alertmanager (web_chat/routes_observability.py), which turns a firing Alertmanager alert into an operator Inbox item. That webhook’s bearer token is a mounted file (/run/secrets/alert_webhook_token via ALERT_WEBHOOK_TOKEN_FILE, kept outside the repo — never an env var, because the raw-secret boot guard refuses those); an absent token leaves the webhook disabled with a loud startup log, never open. Phase 4 added the dashboard’s own /api/metrics (dashboard/app/api/metrics/route.ts) and the memlink /metrics sidecar (memlink_hybrid/infrastructure/metrics_server.py). Prometheus, Grafana, and Alertmanager themselves run on juno from observability-assets/ (a prontera-assets-style documentation-plus-config tree), not on this VPS. Phase 5 (the auditability layer, ADR 0227) is design-only as of 2026-07-25.

The same private compose file now standardizes the default operator identity for private runtime-facing services: owner defaults resolve to owner_mark_canarybuilds_com, the primary Authentik email defaults to mark@canarybuilds.com, the token dashboard slice is enabled, and mail-broker Gmail/admin flags are enabled through canonical STEELMOTH_* env keys. These are deployment defaults; live behavior still depends on the environment used to start the running containers.

The Memlink service env in private/shared compose explicitly enables the deployed memory ingestion and retrieval gates: LLM extraction, embeddings, relation extraction, retrieval orchestration, and the retrieval router. Live behavior still depends on recreating the running Memlink container from the updated compose config.

Private compose now also sets MOTH_HYBRID_TELEGRAM_AGENT_VAULT_BROKER_BASE_URL for steelmoth-worker-moth and steelmoth-notifier, defaulting to https://prontera.sole-augmented.ts.net/telegram. In broker mode, runtime config falls back to ${AGENT_VAULT_ADDR}/telegram only when this env is missing. In the current compose file, steelmoth-api enables broker mode but does not set the explicit base URL, so that container still relies on the fallback unless the env is provided externally.

The prontera Telegram Bot API broker resolves the bot token inside Agent Vault. For the Mark client rollout it first tries client-mark/CLIENT_MARK_TELEGRAM_BOT_TOKEN, the key written by the dashboard Telegram connector, then falls back to the existing client-mark/TELEGRAM_TOKEN so current Telegram delivery remains live before the operator completes the new connector flow. The broker still validates callers against the client-mark vault; steelmoth containers never receive the bot token.

  • Update this page when canonical deploy scripts or hard deploy rules change.
  • Update this page when tracked edge configs or public docs publish paths change.
  • This page is documentation only; always re-read the exact script before running operational commands.
  • This page does not assert which containers are currently running.