From fb74b284047c4c1189064b718ce6d7e1eb895c8c Mon Sep 17 00:00:00 2001 From: Ty Date: Fri, 11 Sep 2026 13:47:15 +0000 Subject: [PATCH] docs: remove live DEPLOYMENT.md --- docs/DEPLOYMENT.md | 199 --------------------------------------------- 1 file changed, 199 deletions(-) delete mode 100644 docs/DEPLOYMENT.md diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md deleted file mode 100644 index ab891f4..0000000 --- a/docs/DEPLOYMENT.md +++ /dev/null @@ -1,199 +0,0 @@ -# Deployment Playbook (Docker Compose + Caddy) - -Status: SPEC · env-agnostic · 2026-09-01 · SlabUploader - -**Target:** any inventoried Docker host. Fill `deploy/INVENTORY.md` before bind. -Variables: `APP_HOST`, `APP_HOSTNAME`, `FRONTEND_PORT`, `FASTAPI_PORT`. -Health URL: `https://${APP_HOSTNAME}/api/health`. - -> Gate A freeze (2026-08-31): hybrid client owns measure/crop SoT. Server-side measure/crop scaffolding is not done. -> server ruler/pipeline as authoritative. Woo UAT = draft + `SLAB-UAT-*` on PROD. -> Inference is **not** on the app host by default. It is a remote configurable endpoint. - -**Historical lab example (non-normative):** TyUBUMini at `192.168.1.201` with hostname -`slab.tyubumini.local` was one early candidate. The inventory worksheet determines the host. -Use the inventory worksheet. - -## 1. Host inventory (required before deployment) - -Before deploying, fill `deploy/INVENTORY.md` (hard rule: inventory before execute): - -1. `docker ps` — existing containers, published ports (avoid collisions with - co-resident apps; bolt.diy is one known example of 8443-class port use). -2. Caddy config location (host file or container) and existing site blocks. -3. Free port choice for the compose stack (proposal: `18080` http, `18443` https — - verify free on **this** host). -4. Docker volume dir convention on the host. -5. Reachability: confirm `${APP_HOSTNAME}` is reachable from the phone network - (same LAN or Tailscale). Add DNS (Pi-hole/AdGuard or Tailscale MagicDNS) if needed: - one A record → `${APP_HOST}` (LAN and/or Tailscale name). - -Write the chosen values into `deploy/INVENTORY.md` and the host `.env` before compose up. - -## 2. Repository layout (at scaffold) - -``` -SlabUploader/ -├── AGENTS.md -├── README.md -├── docs/ # this plan set -├── deploy/ -│ ├── docker-compose.yml # checked in; build contexts point at Phase 0 deliverables -│ ├── Caddyfile.fragment # slab site block (merged into host Caddy) -│ ├── INVENTORY.md # filled at Gate B on the designated host -│ ├── .env.example # Phase 0 deliverable (SLAB_AES_KEY, APP_*, WOO_BASE_URL) -│ └── backup.sh # Phase 0/6 deliverable (sqlite .backup + images tar) -├── frontend/ # Phase 0 deliverable — SvelteKit app (not yet in tree on docs-only main) -└── backend/ # Phase 0 deliverable — FastAPI app (not yet in tree on docs-only main) - ├── app/ - │ ├── main.py - │ ├── (no happy-path pipeline SoT here) # hybrid: TECH-SPEC modules live in frontend/ or shared pure TS; backend = store/Woo/inference/U2Net stub - │ ├── woo/ # Woo client, taxonomy, publish - │ ├── inference/ # vision + content wrappers (toggleable) - │ ├── prompts/ # call1.txt, call2.txt (shipped defaults) - │ ├── api/ # routers (slabs, photos, pricing, taxonomy, settings) - │ ├── db/ # schema.sql, models, migrations (alembic) - │ └── security/ # AES-GCM settings encryption - ├── tests/ # pytest — unit (deterministic) + integration (woo) - └── Dockerfile -``` - -The layout above is the **scaffold target**. On docs-only `main`, `frontend/`, `backend/`, `deploy/.env.example`, and `deploy/backup.sh` are not yet in the tree. Compose still names those build contexts so Phase 0 does not invent paths. - -## 3. docker-compose.yml (checked in) - -Canonical file: `deploy/docker-compose.yml`. Placeholders: `deploy/.env.example` (Phase 0 deliverable). -Compose `build.dockerfile` paths are **Phase 0 deliverable targets**, not proof the Dockerfiles exist yet. -From **repo root** (after Phase 0 scaffolds exist): - -``` -cp deploy/.env.example deploy/.env && chmod 600 deploy/.env -# fill SLAB_AES_KEY, APP_HOSTNAME, WOO_BASE_URL (https:// only) -docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d -``` - -Caddy site block: `deploy/Caddyfile.fragment` (merge into host Caddy). Compose -service names `frontend` and `fastapi` must match that fragment. - -**Store URL (`WOO_BASE_URL`):** - -- Set in `deploy/.env` and injected into the FastAPI container at start. -- **HTTPS only.** FastAPI must refuse missing / `http://` values at startup. -- Not a Settings input and **not** a SQLite settings row. -- Settings UI shows the URL as **read-only** text (which store this deploy hits). -- Change store = edit `.env` + recreate the fastapi container. - -Woo Application Password stays out of compose. Enter it in Settings; FastAPI stores it AES-GCM encrypted. - -Conventions (house rules): - -- `mem_limit` on services (not host-level tuning). -- Services bind to 127.0.0.1 only; Caddy is the only public surface. -- No `latest` tags on base images — pin versions. -- `restart: unless-stopped`; compose is the supervisor (no systemd units). - -## 4. Caddy (fragment) - -Canonical file: `deploy/Caddyfile.fragment`. API must win over the frontend -catch-all. Put `handle /api/*` **before** the frontend `handle` block: - -```caddyfile -{$APP_HOSTNAME} { - tls internal # Caddy internal CA (or host-appropriate TLS) - handle /api/* { - reverse_proxy fastapi:8000 - } - handle { - reverse_proxy frontend:80 { - header_up X-Real-IP {remote_host} - } - } -} -``` - -- HTTPS enforced (http→https redirect automatic when using Caddy TLS). -- If the host Caddy already serves a wildcard for this domain family, add this as a - site block. Verify the cert is trusted on the phone. -- LAN/Tailscale only — no public exposure for POC. - -## 5. Secrets - -| Secret | Where | Notes | -|---|---|---| -| `SLAB_AES_KEY` | `deploy/.env` (mode 600) → compose env | 32-byte base64; NEVER in the repo | -| `WOO_BASE_URL` | `deploy/.env` → compose → FastAPI env | `https://` only; Settings **read-only** | -| Woo WP username + application password | SQLite `settings` (AES-GCM) via Settings UI | dedicated low-priv WP user; not consumer keys | -| Inference API key | SQLite `settings` (AES-GCM) via Settings UI | usually empty for local llama.cpp | -| `.env.example` | repo | placeholders only, committed | - -**Woo credential setup (once per store):** - -1. Store must already be on **HTTPS**. WordPress will not offer Application - Passwords over plain HTTP. -2. Create a dedicated low-privilege WordPress user (shop manager, or custom role - with product create/edit + media upload). Admin logins are not reused. -3. Sign in as that user → **Users → Profile → Application Passwords** → generate - a password for "SlabUploader". -4. Enter the username + application password in the app Settings UI. FastAPI - stores them AES-GCM encrypted. Browser never keeps them. -5. Rotate by revoking the old application password in the WP profile and - re-entering a new one in Settings, then `test-woo`. - -Key rotation (AES): `SLAB_AES_KEY` change requires re-encrypting settings — -provide `python -m app.security.rekey NEW_KEY` (reads old key from env -`SLAB_AES_KEY_OLD`, decrypts, re-encrypts, updates). Documented in playbook §7. - -## 6. Backup and restore - -`deploy/backup.sh` (run via cron on the app host, e.g. daily 03:00): - -``` -1. sqlite3 /data/slab.db ".backup /backups/slab-YYYYMMDD.db" # WAL-safe -2. tar czf /backups/slab-images-YYYYMMDD.tgz -C /images . -3. retain: 7 daily + 4 weekly; prune older -4. log result to /var/log/slab-backup.log; alert (Hermes) on failure -``` - -Prompt files live in the git repo (`backend/app/prompts/`) and are version-controlled; -they do not need a separate backup. The `reset-to-default` script restores shipped -defaults from the app bundle. - -- Backup target: local dir first; mirror to an off-box share for safety (same pattern - as other stacks on the host). -- **Restore**: stop compose → restore DB file + untar images → start compose. - Tested at least once during Phase 6 (UAT) before declaring backup done. - -## 7. Operations runbook - -| Task | Command / how | -|---|---| -| Deploy (first) | inventory §1 → `docker compose -f deploy/docker-compose.yml build` → `up -d` → Caddy reload → smoke §8 | -| Update | `git pull` → `docker compose build fastapi frontend` → `up -d` (DB migrations run on start via alembic) | -| Logs | `docker compose logs -f fastapi` / `frontend` | -| Health | `curl -k https://${APP_HOSTNAME}/api/health` | -| Rekey AES | §5 | -| Rotate Woo creds | WP user profile → revoke old application password → new one → Settings UI → `POST /api/v1/settings/test-woo` | -| Rotate inference key | Settings UI (if external) | -| Edit a prompt | Edit the text file on host, no restart needed (read fresh each time) | -| Rollback image | `git checkout ` → rebuild → up -d; DB down-migrations provided for schema changes | -| Reset one slab | delete its photos dir + `UPDATE slabs SET status='draft'` (admin script) | - -## 8. Smoke test (after every deploy) - -1. `GET https://${APP_HOSTNAME}/api/health` → ok (from phone over HTTPS once CA trust is proven) -2. Settings: WP application password + `test-woo` ok; `woo_create_status=draft`; inference OFF for early gates -3. Create a slab → upload test photos (fixture set) → poll to `ready` -4. Verify bdft/price match the fixture's expected values (golden test; bdft = sqft × thickness) -5. Publish test slab with SKU `SLAB-UAT-…` → slab may show local `published`; Woo product must be **draft** -6. Leave or delete the Woo draft UAT product (never customer-visible). Final live publish is a non-UAT SKU after Ty review. - -For the full human-run walk (host check → App Password → one `SLAB-UAT-*` prove → confirm → -cleanup), see `docs/UAT-PRACTICE.md`. - -## 9. Scaling path (not v1) - -- Postgres when SQLite write contention appears (schema already portable). -- Redis+arq when in-process queue shows lag. -- GPU on the app host only if on-demand U2Net latency becomes a bottleneck. Keep CPU - first. Never lower `output_px_min` below 1600; undersized source means retake, not a - smaller min. RemBG is not part of the hybrid happy path.