diff --git a/stacks/arbo/.env.example b/stacks/arbo/.env.example new file mode 100644 index 0000000..a88451c --- /dev/null +++ b/stacks/arbo/.env.example @@ -0,0 +1,26 @@ +# Arbo engine — real .env lives on irv-ml1 (gitignored), this is the template. +# Copy to .env on the host and fill the secrets. See README.md for provisioning. + +# Image comfy-dev builds + pushes (pin a tag; bump on code/schema change only): +ARBO_IMAGE=gitea.phasefinal.com/vh/arbo:0.11.0 + +# Published port on irv-ml1 (reachable over WireGuard at 10.100.79.3:): +ARBO_PORT=8200 +ARBO_BIND=0.0.0.0 + +# Run-as ownership — must match /worktank owner so basedir writes land clean: +ARBO_UID=1000 +ARBO_GID=1000 + +# Host path of the comfy-dev catalog checkout (the git pull target, mounted ro): +ARBO_CATALOG_DIR=/worktank/arbo/repo + +# ── Secrets (DO NOT COMMIT REAL VALUES) ────────────────────────────── +# ENGINE_TOKEN: a real bearer token — auth is OPEN until this is set. +# Mint a fresh one: openssl rand -hex 32 +ENGINE_TOKEN= + +# GRANITE_KEY: the LiteLLM virtual key scoped to arbo (granite-only). +# comfy-dev already holds 'arbo-prompt-enhance' (issued 2026-06-09) — +# reuse or rotate it; do NOT use the master sk-corvid key. +GRANITE_KEY= diff --git a/stacks/arbo/README.md b/stacks/arbo/README.md new file mode 100644 index 0000000..3752a6a --- /dev/null +++ b/stacks/arbo/README.md @@ -0,0 +1,72 @@ +# arbo — catalog-driven ComfyUI engine + +Arbo's `comfy_engine` (FastAPI) reads a versioned workflow catalog and drives +**ComfyUI** to generate images/video, serving an SPA + `/workflows` + `/run`. +Owned by **comfy-dev** (engine code + catalog + image build); this stack is the +**infra side** — compose, deploy, secrets, volumes, pull automation. See +`vh/arbo` `docs/adr/0001-engine-production-deployment.md` for the architecture. + +## Placement (ADR-0001) + +- **Host:** irv-ml1, **co-located beside the `comfyui` stack** (D1/D3). +- **engine ↔ ComfyUI:** `http://comfyui:8188` over the shared `traefik-net` + (the "localhost-equivalent" — no scp, no WireGuard hop). File ops use the + **same host dirs** comfyui mounts (`/worktank/comfyui/basedir/{input,output}`), + bound into the engine — so `ARBO_COMFY_SSH_HOST` stays **unset** and the + engine's local-cp path (comfy-dev's D3 follow-on) handles file ops. + +## Deploy + +```bash +# 1. comfy-dev builds + pushes the image; pin the tag in .env (ARBO_IMAGE). +# 2. On irv-ml1, clone the catalog checkout (the git-pull target, D2): +# git clone git@gitea.phasefinal.com:vh/arbo.git /worktank/arbo/repo +# 3. Create stacks/arbo/.env from .env.example on the host, fill secrets. +# 4. From this workspace: +scripts/deploy-stack.sh irv-ml1 arbo # diffs vs live, prompts y/N +``` + +## Q3 — Secrets + +Both injected via the on-host **`.env`** (gitignored; never the althing bus), +the standard fleet pattern: + +- **`ENGINE_TOKEN`** — auth is **OPEN today**. Mint a real bearer + (`openssl rand -hex 32`), put it in `.env`. Closing this is the one security + fix the move buys us. +- **`GRANITE_KEY`** — the LiteLLM **virtual key scoped to arbo** (granite-only). + comfy-dev already holds `arbo-prompt-enhance` (issued 2026-06-09); reuse or + rotate it. Never the master `sk-corvid`. + +## Q4 — Volumes + backup + +| Volume | Holds | Durability | +|---|---|---| +| `arbo_db` (named, local disk) | gallery/history SQLite (`ARBO_DB`) | **backed up** — restic/Backrest daily | +| `arbo_heroes` (named, local disk) | runtime hero images | persisted; regenerable, lower backup priority | +| `/worktank/arbo/repo` (bind, ro) | catalog checkout | reproducible from git — not backed up | + +`ARBO_DB` is a **named volume on local disk, NOT NFS** (the DB-off-NFS rule; +SQLite-on-NFS locking is a foot-gun). The gallery DB is the durable asset → it +joins the restic file backup. (Adds irv-ml1's `arbo_db` path to a Backrest +repo — small; flag if irv-ml1 isn't yet a restic source.) + +## Q5 — Catalog-pull automation + +- **Day 1:** manual `git pull` in `/worktank/arbo/repo` + `deploy-stack.sh + irv-ml1 arbo` restart (or `docker compose restart arbo`). +- **Follow-on (recommended):** mirror the **yt-voice-clipper webhook** already + live on irv-ml1 — gitea webhook → HMAC listener → `git pull` + `compose + restart`, so a comfy-dev catalog push reaches prod in one action (D2's "cheap + to reach prod"). Layered after the stack is up. + +## Items needing comfy-dev's image (jointly owned) + +1. **`ARBO_IMAGE` tag** — the built `gitea.phasefinal.com/vh/arbo:`. +2. **`/healthz`** — the healthcheck assumes it; comfy-dev is adding it. +3. **Catalog in-container path** — compose mounts the checkout's `catalog/` + + `graphs/` to `/app/catalog` + `/app/graphs` (ro). Confirm against the image's + expected layout (or expose a catalog-dir env), so the engine reloads from the + mount on restart. +4. **Run-as UID** — container runs `1000:1000` to write comfyui's basedir + + volumes cleanly; confirm the image tolerates a non-root user. diff --git a/stacks/arbo/compose.yaml b/stacks/arbo/compose.yaml new file mode 100644 index 0000000..1176c6c --- /dev/null +++ b/stacks/arbo/compose.yaml @@ -0,0 +1,86 @@ +# Arbo (catalog) engine — FastAPI sidecar that drives ComfyUI from a +# versioned workflow catalog. Serves the SPA + /workflows + /run. +# +# Co-located on irv-ml1 BESIDE the comfyui stack (ADR-0001 / D3). This +# collapses the engine<->ComfyUI link to the shared traefik-net + the +# local filesystem and ELIMINATES the old SSH/WireGuard dependency: +# - generation : COMFYUI_URL=http://comfyui:8188 (container DNS on +# traefik-net — the "localhost-equivalent"; no scp, no WG hop) +# - file ops : ARBO_COMFY_INPUT_DIR / ARBO_COMFY_OUTPUT_DIR bind the +# SAME host dirs comfyui mounts (/worktank/comfyui/basedir/{input, +# output}), so to-input / upload-input / artifact-wipe are local fs +# ops. ARBO_COMFY_SSH_HOST is left UNSET on purpose → the engine's +# local-cp/shutil path (comfy-dev's D3 follow-on) takes over. +# +# State that MUST survive restarts (named volumes, local disk — NOT NFS, +# per the DB-off-NFS rule; SQLite-on-NFS locking is a known foot-gun): +# - arbo_db -> the gallery/history SQLite (ARBO_DB) +# - arbo_heroes -> runtime hero images (ARBO_HEROES_DIR) +# The catalog is a comfy-dev git checkout bind-mounted read-only (D2): +# a workflow update = `git pull` on the host + restart, no image rebuild. +# +# IMAGE + CATALOG TARGET are owned by comfy-dev (they build the image + +# add /healthz). Two items marked CONFIRM below need their image layout. +# All tunables live in .env — edit that, not this file. + +services: + engine: + image: ${ARBO_IMAGE} # CONFIRM: gitea.phasefinal.com/vh/arbo:, comfy-dev builds + container_name: arbo + restart: unless-stopped + # Run as the host owner of /worktank (1000:1000 = lkraven) so writes + # into comfyui's basedir/input + the named volumes land with the + # right ownership (matches the comfyui stack's WANTED_UID posture). + user: "${ARBO_UID:-1000}:${ARBO_GID:-1000}" + ports: + - "${ARBO_BIND:-0.0.0.0}:${ARBO_PORT:-8200}:8200" + env_file: .env + environment: + - ARBO_HOST=0.0.0.0 + - ARBO_PORT=8200 + - ARBO_DB=/data/gallery.sqlite + - ARBO_HEROES_DIR=/heroes + # engine<->ComfyUI over the shared network (no SSH): + - COMFYUI_URL=http://comfyui:8188 + - ARBO_COMFY_INPUT_DIR=/comfy/input + - ARBO_COMFY_OUTPUT_DIR=/comfy/output + # ARBO_COMFY_SSH_HOST intentionally UNSET -> local-cp path (D3 follow-on) + # LiteLLM gateway for the granite hero/metadata calls: + - GRANITE_ENDPOINT=http://10.250.50.70:4000/v1 + - GRANITE_KEY=${GRANITE_KEY} # arbo-prompt-enhance scoped vkey (.env) + - ENGINE_TOKEN=${ENGINE_TOKEN} # real bearer — closes today's open auth (.env) + volumes: + - arbo_db:/data + - arbo_heroes:/heroes + # comfyui's input/output, shared on the host (rw: upload-input writes + # input, artifact-wipe deletes output): + - /worktank/comfyui/basedir/input:/comfy/input + - /worktank/comfyui/basedir/output:/comfy/output + # catalog checkout (comfy-dev clone on the host), read-only: + - ${ARBO_CATALOG_DIR:-/worktank/arbo/repo}/catalog:/app/catalog:ro # CONFIRM in-container path vs image layout + - ${ARBO_CATALOG_DIR:-/worktank/arbo/repo}/graphs:/app/graphs:ro # CONFIRM in-container path vs image layout + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://localhost:8200/healthz >/dev/null || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + networks: + - tnet + labels: + - homepage.group=AI Systems + - homepage.name=Arbo + - homepage.icon=mdi-image-multiple-outline + - homepage.description=Catalog-driven ComfyUI engine (irv-ml1) + - homepage.href=http://10.100.79.3:${ARBO_PORT:-8200} + +volumes: + arbo_db: + name: arbo_db + arbo_heroes: + name: arbo_heroes + +networks: + tnet: + name: traefik-net + external: true