docs: env-agnostic deploy (APP_HOSTNAME, inventory, Caddy order)
DEPLOYMENT is host-variable: APP_HOST/APP_HOSTNAME/ports, inventory worksheet, corrected Caddy handle /api/* before frontend. Drop RemBG and output_px 1000 as deploy SoT. AGENTS §9, README, IMPL host wording match. Historical .201 lab is a non-normative callout only.
This commit is contained in:
@@ -144,7 +144,8 @@ Encode TECH-SPEC exactly:
|
||||
|
||||
## 9. Deploy sketch (see DEPLOYMENT.md)
|
||||
|
||||
- Target: `.201`, Docker Compose (`frontend` + `fastapi`) + Caddy → `slab.tyubumini.local` (LAN/Tailscale).
|
||||
- Inventory host ports/Caddy before binding (bolt.diy collision risk).
|
||||
- Health: `GET /api/health`.
|
||||
- Inference is a remote configurable endpoint, not on `.201` by default.
|
||||
- Target: inventoried Docker host. Docker Compose (`frontend` + `fastapi`) + Caddy → `https://${APP_HOSTNAME}` (LAN/Tailscale).
|
||||
- Fill `deploy/INVENTORY.md` before binding ports or merging Caddy (co-resident apps can collide; bolt.diy is one example).
|
||||
- Health: `GET https://${APP_HOSTNAME}/api/health`.
|
||||
- Inference is a remote configurable endpoint, not on the app host by default.
|
||||
- Historical lab IP/hostname examples in older notes are non-normative.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Mobile-first, self-hosted app for publishing one-of-a-one wood slabs to WooCommerce.
|
||||
Phone capture (online) → hybrid measure/crop on device → review → WooCommerce create.
|
||||
|
||||
Owner: Ty (Whidbey). Store: www.whidbeywoodstore.com. Deploy: .201 (Caddy, Docker).
|
||||
Owner: Ty (Whidbey). Store: www.whidbeywoodstore.com. Deploy: Docker Compose + Caddy on an inventoried host (`APP_HOSTNAME`).
|
||||
|
||||
## Status
|
||||
**POC requirements freeze in progress (Gate A, 2026-08-31).** Read `AGENTS.md` before
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# Deploy inventory worksheet
|
||||
|
||||
Fill this at **Gate B** on the designated host before any port bind or Caddy merge.
|
||||
Do not assume a prior lab IP or hostname. See `docs/DEPLOYMENT.md`.
|
||||
|
||||
## Host
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Inventory date | |
|
||||
| Operator | |
|
||||
| Machine name / role | |
|
||||
| `APP_HOST` (LAN or Tailscale IP/name used in DNS) | |
|
||||
| `APP_HOSTNAME` (HTTPS site name, e.g. `slab.example.local`) | |
|
||||
| OS / Docker version | |
|
||||
| Notes | |
|
||||
|
||||
## Ports and collisions
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| `docker ps` summary (names + published ports) | |
|
||||
| Free HTTP port for frontend bind (`FRONTEND_PORT`, default 18080) | |
|
||||
| Free port for fastapi bind (`FASTAPI_PORT`, default 18081) | |
|
||||
| Caddy public HTTPS port in use by other sites | |
|
||||
| Co-resident apps that own ports (example: bolt.diy on 8443-class) | |
|
||||
|
||||
## Caddy
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Caddy config path (host file or container) | |
|
||||
| Existing site blocks that share this host | |
|
||||
| TLS mode (`internal` CA vs other) | |
|
||||
| Phone CA trust verified? (yes/no + how) | |
|
||||
|
||||
## DNS / reachability
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| DNS source (Pi-hole / AdGuard / MagicDNS / hosts) | |
|
||||
| A record: `APP_HOSTNAME` → `APP_HOST` | |
|
||||
| Reachable from phone on LAN? | |
|
||||
| Reachable from phone on Tailscale? | |
|
||||
|
||||
## Volumes and backup
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Docker volume dir convention | |
|
||||
| Backup target path (local) | |
|
||||
| Off-box mirror (if any) | |
|
||||
|
||||
## Smoke after first up
|
||||
|
||||
| Check | Pass? |
|
||||
|---|---|
|
||||
| `curl -k https://${APP_HOSTNAME}/api/health` | |
|
||||
| Compose `frontend` + `fastapi` healthy | |
|
||||
| Caddy `/api/*` reaches fastapi (not frontend) | |
|
||||
|
||||
## Sign-off
|
||||
|
||||
| Role | Name | Date |
|
||||
|---|---|---|
|
||||
| Inventory complete | | |
|
||||
| Ty Gate B (optional) | | |
|
||||
+56
-38
@@ -1,29 +1,34 @@
|
||||
# Deployment Playbook (.201, Docker Compose + Caddy)
|
||||
# Deployment Playbook (Docker Compose + Caddy)
|
||||
|
||||
Status: SPEC (deliverable #4) · 2026-08-24 · SlabUploader
|
||||
Target: TyUBUMini at 192.168.1.201. Domain: `slab.tyubumini.local` (LAN/Tailscale
|
||||
Status: SPEC · env-agnostic · 2026-09-01 · SlabUploader
|
||||
|
||||
> Gate A freeze (2026-08-31): hybrid client owns measure/crop SoT. Do not scaffold a server ruler/pipeline as authoritative. Woo UAT = draft + SLAB-UAT-* on PROD.
|
||||
only — Decision). Inference is NOT on .201; it's a remote configurable endpoint
|
||||
(.202 or elsewhere).
|
||||
**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`.
|
||||
|
||||
> Note: exact Caddyfile/ports to be verified against the live .201 Caddy setup at
|
||||
> implementation time (bolt.diy already runs Caddy TLS on tyubumini.local:8443).
|
||||
> This playbook is the plan; the first implementation task is a .201 inventory pass.
|
||||
> Gate A freeze (2026-08-31): hybrid client owns measure/crop SoT. Do not scaffold a
|
||||
> 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. Do not treat that IP or name as SoT.
|
||||
Use the inventory worksheet.
|
||||
|
||||
## 1. Host inventory (do first — do not assume)
|
||||
|
||||
Before deploying, record the current state (HARD RULE: inventory before execute):
|
||||
1. `docker ps` — existing containers, published ports (avoid collisions; bolt.diy
|
||||
owns 8443/8444-class ports).
|
||||
2. Caddy config location (host file or container) and existing `tyubumini.local`
|
||||
site blocks.
|
||||
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).
|
||||
verify free on **this** host).
|
||||
4. Docker volume dir convention on the host.
|
||||
5. Tailscale: confirm `slab.tyubumini.local` is reachable from the phone's network
|
||||
(same LAN or Tailscale). If the domain isn't in DNS yet, add it (Pi-hole/AdGuard
|
||||
or Tailscale MagicDNS) — one record, A → 192.168.1.201 (LAN) + Tailscale name.
|
||||
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)
|
||||
|
||||
@@ -35,10 +40,11 @@ SlabUploader/
|
||||
├── deploy/
|
||||
│ ├── docker-compose.yml
|
||||
│ ├── Caddyfile.fragment # slab site block (merged into host Caddy)
|
||||
│ ├── .env.example # SLAB_AES_KEY, port vars (no real secrets)
|
||||
│ ├── INVENTORY.md # filled at Gate B on the designated host
|
||||
│ ├── .env.example # SLAB_AES_KEY, APP_*, port vars (no real secrets)
|
||||
│ └── backup.sh # sqlite .backup + images tar, with retention
|
||||
├── frontend/ # SvelteKit app (Phase 1)
|
||||
└── backend/ # FastAPI app (Phase 0)
|
||||
├── frontend/ # SvelteKit app
|
||||
└── backend/ # FastAPI app
|
||||
├── 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
|
||||
@@ -81,6 +87,7 @@ volumes:
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -88,21 +95,27 @@ Conventions (house rules):
|
||||
|
||||
## 4. Caddy (fragment)
|
||||
|
||||
API must win over the frontend catch-all. Put `handle /api/*` **before** the frontend
|
||||
`handle` block:
|
||||
|
||||
```caddyfile
|
||||
slab.tyubumini.local {
|
||||
tls internal # Caddy internal CA (matches bolt.diy pattern)
|
||||
reverse_proxy frontend:80 {
|
||||
header_up X-Real-IP {remote_host}
|
||||
}
|
||||
{$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).
|
||||
- If the host Caddy already serves `*.tyubumini.local`, add this as a site block;
|
||||
verify the internal CA cert is trusted on the phone (same flow as bolt.diy).
|
||||
- LAN/Tailscale only — no public exposure.
|
||||
|
||||
- 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
|
||||
|
||||
@@ -117,9 +130,10 @@ 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 & restore
|
||||
## 6. Backup and restore
|
||||
|
||||
`deploy/backup.sh` (run via cron on the app host, e.g. daily 03:00):
|
||||
|
||||
`deploy/backup.sh` (run via cron on .201, 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 .
|
||||
@@ -127,8 +141,9 @@ provide `python -m app.security.rekey NEW_KEY` (reads old key from env
|
||||
4. retain: 7 daily + 4 weekly; prune older
|
||||
5. log result to /var/log/slab-backup.log; alert (Hermes) on failure
|
||||
```
|
||||
- Backup target: local dir first; mirror to TrueNAS SMB share
|
||||
(`\\truenas.local\…\backups\slab`) for off-box safety (same pattern as other stacks).
|
||||
|
||||
- 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.
|
||||
|
||||
@@ -139,7 +154,7 @@ provide `python -m app.security.rekey NEW_KEY` (reads old key from env
|
||||
| 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://slab.tyubumini.local/api/health` |
|
||||
| Health | `curl -k https://${APP_HOSTNAME}/api/health` |
|
||||
| Rekey AES | §5 |
|
||||
| Rotate Woo creds | Woo UI → new consumer key/secret → Settings UI → `POST /api/settings/test-woo` |
|
||||
| Rotate inference key | Settings UI (if external) |
|
||||
@@ -148,7 +163,8 @@ provide `python -m app.security.rekey NEW_KEY` (reads old key from env
|
||||
| Reset one slab | delete its photos dir + `UPDATE slabs SET status='draft'` (admin script) |
|
||||
|
||||
## 8. Smoke test (after every deploy)
|
||||
1. `GET /api/health` → ok (from phone over HTTPS once CA trust is proven)
|
||||
|
||||
1. `GET https://${APP_HOSTNAME}/api/health` → ok (from phone over HTTPS once CA trust is proven)
|
||||
2. Settings: Woo creds + `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)
|
||||
@@ -156,7 +172,9 @@ provide `python -m app.security.rekey NEW_KEY` (reads old key from env
|
||||
6. Leave or delete the Woo draft UAT product (never customer-visible). Final live publish is a non-UAT SKU after Ty review.
|
||||
|
||||
## 9. Scaling path (not v1)
|
||||
|
||||
- Postgres when SQLite write contention appears (schema already portable).
|
||||
- Redis+arq when in-process queue shows lag.
|
||||
- GPU on .201 only if RemBG latency becomes a bottleneck — otherwise keep it CPU
|
||||
(normalization budget is 3–5s per PRD; U²Net on modern CPU meets that).
|
||||
- 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.
|
||||
|
||||
+4
-4
@@ -64,7 +64,7 @@ Acceptance:
|
||||
|
||||
- Gate B: client (or shared pure) tests green for TECH-SPEC vectors; bdft identity holds
|
||||
(no `/12`); undersized source warns and refuses fake upscale; rounding matches TV-10.
|
||||
- `GET /api/health` up in compose over HTTPS hostname after host inventory + Caddy merge.
|
||||
- `GET https://${APP_HOSTNAME}/api/health` up in compose after `deploy/INVENTORY.md` is filled and Caddy is merged.
|
||||
- Settings AES round-trip; secret fields never appear in GET.
|
||||
- Draft can store client-uploaded originals + PNGs + numbers without server recompute.
|
||||
- Coverage target: deterministic modules ≥ 95% where practical; property checks
|
||||
@@ -249,8 +249,8 @@ Goal: production deploy on the **inventoried** host + online user acceptance tes
|
||||
|
||||
Deliverables:
|
||||
|
||||
- Host inventory pass (DEPLOYMENT §1 / `deploy/INVENTORY.md`) → final compose + Caddyfile.
|
||||
- Deploy to `https://${APP_HOSTNAME}`; backup/restore runbook executed once (restore tested).
|
||||
- Host inventory pass: complete `deploy/INVENTORY.md` per DEPLOYMENT §1 → final compose + Caddyfile.
|
||||
- Deploy to `https://${APP_HOSTNAME}` from inventory; backup/restore runbook executed once (restore tested).
|
||||
- **UAT suite** for the hybrid online path: scripted real slabs (≥3 species, ≥1 cathedral,
|
||||
≥1 manual-width path, ≥1 duplicate-SKU attempt) with expected bdft/price/image-fill
|
||||
asserted. **No** offline capture, OCR, or ruler cases in POC UAT.
|
||||
@@ -292,5 +292,5 @@ authoritative for POC. Do not treat ruler confidence as a POC risk row.
|
||||
- All 6 phases exit-gated by Ty (Phase 2 remains deferred stub unless product reopens it).
|
||||
- Hybrid online acceptance met (measured in Phase 6 UAT).
|
||||
- `pytest` + integration + UAT green; inference-off path green.
|
||||
- Deployed on inventoried host, HTTPS, backed up, restore tested, runbook + key-rotation verified.
|
||||
- Deployed on the inventoried host (`APP_HOSTNAME` from `deploy/INVENTORY.md`), HTTPS, backed up, restore tested, runbook + key-rotation verified.
|
||||
- Repo on gitea-atd (`Ty_Tech/SlabUploader`) is source of truth; docs current.
|
||||
|
||||
Reference in New Issue
Block a user