# fjx-mgmt deployment recipe `fjx-mgmt` is the long-running operator surface for FJX (see `openspec/changes/afk-mode/specs/mgmt-service/spec.md`). It serves the mobile-first dashboard and the MCP endpoint over the per-project telemetry on disk. It is **read-only at MVP** — no Forgejo or telemetry mutations. ## Image Built and published by `.forgejo/workflows/images.yaml` on every `v*` tag: - Image: `${REGISTRY}/${OWNER}/fjx-mgmt:${VERSION}` (also `:latest`) - Local build: `just images::build fjx-mgmt` - Local smoke: `just images::smoke fjx-mgmt` The Dockerfile is multi-stage. The builder runs `deno run -A dev.ts build` to produce the Fresh `_fresh/` bundle; the runtime stage carries only the deno binary, the source tree, and the prebuilt bundle. It runs as the unprivileged `deno` user shipped by the base image. ## Required configuration Environment variables (read once at startup by `mgmt/lib/config.ts`): | Variable | Required | Default | Notes | |---|---|---|---| | `FJX_PROJECTS_DIR` | yes | — | Path inside the container that holds one subdirectory per project; each must contain [`fjx.json`](./fjx-json.md) | | `FJX_MGMT_PORT` | no | `8000` | HTTP listen port | ## Volume mounts `fjx-mgmt` reads two things from each project directory: - `/fjx.json` — project config ([schema](./fjx-json.md)) - `/telemetry/*.jsonl` and `/fjx.sqlite` — telemetry shards and (optional) index It writes nothing. Mount the projects directory **read-only**: ```sh docker run -d --restart=unless-stopped \ --name fjx-mgmt \ -p 8000:8000 \ -v /srv/fjx/projects:/projects:ro \ -e FJX_PROJECTS_DIR=/projects \ ${REGISTRY}/${OWNER}/fjx-mgmt:latest ``` The dashboard renders an empty-state when `FJX_PROJECTS_DIR` is empty — useful before the first project lands. ## Routes - `GET /` — dashboard (active runs, recent failures, stuck work, tokens today) - `GET /runs` — cross-project runs list with filters - `GET /projects//runs/` — run detail with event timeline - `GET /projects//issues/` — issue view (brief + per-role ledgers) - `POST /mcp` — MCP JSON-RPC endpoint (tools: `fjx-recent-activity`, `fjx-failed-runs`, `fjx-token-usage`, `fjx-stuck-work`) ## Authentication **Deferred.** The MVP ships with no auth in front of the service. Deploy it on a network that is not reachable from the public internet — a Tailscale tailnet or a VPN-only host. The spec records this as an explicit non-goal (`openspec/changes/afk-mode/proposal.md`, "Out of scope: auth design for the mgmt service beyond Tailscale-only"). Revisit when adding mutating endpoints. ## Upgrades The service is stateless. Roll forward with a fresh container; the SQLite index and JSONL shards live in the mounted projects directory and survive the swap. Downtime during the swap is the time to pull and restart — measured in seconds for a warm registry. ## Healthcheck `GET /` returns 200 with HTML once the routes are mounted. A minimal Docker healthcheck: ```sh HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ CMD wget -qO- http://127.0.0.1:8000/ >/dev/null || exit 1 ```