Files
serverAppConfigs/serverAppConfigs-docs/Architecture/Deployment Flow.md
T
Frank 327e78b950 docs(vault): destroy the dead weight — 53 pages and the change diary
The vault was 155 pages / 31,210 lines, most of it measurement ledgers for
payloads that no longer run. A throughput figure for a runtime nobody operates is
not a record: AI moves faster than the vault, so the number is wrong before
anyone reads it, and a wrong number in a runbook gets believed. Per the new
purpose test in `AGENTS.md` → *Documentation Policy*, it is destroyed.

Gone (`.AGENTTRASH/20261019-destruction/`, KILL-LIST.txt is the manifest):
- the `frank-medium` endpoint and its five era pages, `frank-npu`, FastFlowLM,
  the ikllama/kingjones/dealign/frank-redo eras, the ling/swift15/freetoken/ubatch
  campaign pages, qwen3.8 spec-matrix and strix-optimizations, the two
  "Running Qwen3.6 …" pages;
- the whole `Pipeline Parallelism/` folder (no live script passes a pipeline flag)
  and the multi-device/x4-pair/hybrid/offload experiment pages;
- every vLLM page — no vLLM endpoint exists on this fleet;
- `LLM/deployment.md` (`Architecture/Deployment Flow.md` is the test-referenced
  owner) and the external surveys (SGLang, Lemonade, llama.cpp forks/diff,
  self-hosted app census, the TTS survey) that were obsolete by construction;
- `log.md`, 2,380 lines. Git holds the chronology; a causal law belongs to the
  page that owns the thing it governs, never to a diary.

Survivors: 101 pages, 17,542 lines, 0 OKF violations, 0 unresolved links. Eras
tables keep the window, payload, runtime, reason and rollback pointer; the rate
columns are blanked. The live shapes stay verbatim — those numbers are what the
deployer and `models.yml` consume.
2026-10-19 10:56:37 -07:00

130 lines
5.6 KiB
Markdown

---
type: howto
title: Deployment Flow
description: Service-scoped Git deployment for the two homelab hosts.
tags:
- deployment
- operations
- git
timestamp: 2026-09-03
---
# Deployment Flow
Git is the single source of truth. `scripts/deploy.sh` pushes the committed
`main` branch, pulls it on one target, and applies only the runtime services
whose tracked inputs changed. A SearXNG change no longer recreates the
inference service; an LLM change no longer restarts unrelated containers.
## Workflow
```mermaid
graph LR
A[Edit in this repo] --> B[Run tests]
B --> C[Commit main]
C --> D[deploy.sh computes plan]
D --> E[Push main]
E --> F[Target pulls with ff-only]
F --> G[deploy-remote.sh dispatches selected services]
G --> H[Per-service health check]
```
1. Edit files locally in this repository.
2. Run `./tests/run.sh`, then commit the complete change on `main`.
3. Run `./scripts/deploy.sh <target>`.
4. The planner maps changed paths to services. Compose YAML is compared by
service definition so an edit to one service does not restart its peers.
5. The target pulls with `git pull --ff-only origin main` and runs the remote
dispatcher with the explicit plan.
6. The dispatcher updates only selected Quadlet, compose, homepage, Caddy, or
systemd services and records each successful service revision outside Git.
## Commands
```bash
./scripts/deploy.sh dankclaw
./scripts/deploy.sh dankserver
./scripts/deploy.sh frankbook-pro --service qbittorrent # podman-machine target
./scripts/deploy.sh dankclaw --service searxng
./scripts/deploy.sh dankserver --service frank-archon
./scripts/deploy.sh dankclaw --dry-run
```
`--service` is repeatable and is an explicit retry/override. Without it, the
planner compares the local `HEAD` to `origin/main`. A documentation-only
commit produces an empty plan. The remote state directory can also replay a
service whose earlier deployment failed, but it never expands a plan to a
service on the other host.
## Service managers
| Service family | Runtime manager | Scope behavior |
|---|---|---|
| Dankclaw compose services | `podman-compose -f dankclaw/compose.yaml up -d --no-deps NAME` | Recreates only `NAME`; Caddy is handled separately because it fronts both hosts. |
| SearXNG, Gluetun, Valkey, OpenWebUI, Pi-hole, Gitea database/server | user Quadlet + `systemctl --user` | Installs the selected unit and restarts only the required dependency-coupled units. Gitea's web proxy is the canonical Caddy compose service. |
| `frank-dragoon` | systemd + `systemctl` (root unit) | Relinks and restarts only the Dankclaw iGPU inference service; waits up to 20 minutes for `/health`. Shape, gates and boot budget: [[frank-dragoon]]. |
| `frank-zealot` | systemd + `systemctl` (root unit) | Relinks and restarts only the Dankclaw 3090 inference service; waits up to 20 minutes for `/health`. Shape, gates and boot budget: [[frank-zealot]]. |
| `frank-archon` | systemd + `systemctl` (root unit) | `deploy_llm_archon` plans from repo paths and gates on the port; it does NOT see host-side unit drift (see [[frank-archon]] → Deploy / verify). |
| Dankserver media stack (RomM trio, Jellyfin, Calibre-Web, Copyparty) | user Quadlet + `systemctl --user` | Installs `dankserver/<svc>/quadlet/*.container`, restarts the generated unit. The first cutover per service is idempotent: it retires the compose-era bare container, rescues any anonymous volume into the data root, and merge-moves `/opt/<svc>` to `/home/dank/Containers/<svc>` (mapped-uid roots move without chown). Steady state is restart-only. |
The shared chat template maps to the inference service on the selected target.
The `frank-npu` endpoint is retired () and has no deploy handler
an unrelated large-model deployment never touches it.
## Caddy safety
Caddy runs on Dankclaw and fronts both hosts, including Gitea. The former
standalone `gitea-caddy` Quadlet is retired; the canonical compose `caddy`
service is now the sole reverse-proxy owner of ports 80/443. A Caddy reload
failure falls back to a restart of **only** the selected `caddy` container.
Before deploying, verify that no retired proxy still owns either port.
Homepage files, generated `deployed.js`, the status checker, and the rendered
Caddyfile are updated only when their service is selected. A media-only
Dankserver deployment does not touch Dankclaw's proxy or homepage.
## Per-service state
The remote runner writes one revision file per successfully completed service
to:
```text
${XDG_STATE_HOME:-~/.local/state}/serverAppConfigs/deploy-state/
```
The directory is outside the checkout and is never committed. A failed handler
is not marked successful. On the next ordinary deployment, the runner compares
the recorded revision with the new checkout and retries only a still-changed
service. This prevents a failed Caddy or LLM deployment from being silently
forgotten while avoiding a host-wide restart.
## Targets
| Target | Host | Repository checkout |
|---|---|---|
| dankclaw | `dank@dankclaw` (`192.168.0.143`) | `/home/dank/serverAppConfigs` |
| dankserver | `dank@dankserver` (`192.168.0.235`) | `/home/dank/serverAppConfigs` |
SSH uses the existing key pair. No password is stored or documented in this
repository. If sudo is needed on a headless target, `FRANK_SUDO_PW` is read
from the environment and is never written to the repository.
## Tests
Run the full suite before pushing:
```bash
./tests/run.sh
```
The deployment seams are in `tests/deployment_scoping.bats` and
`tests/deployment_flow.bats`. They cover path isolation, compose narrowing,
Caddy safety, inference isolation, per-service state, and legacy wrapper
routing. See [[Testing Policy]].
## See Also
- [[Testing Policy]]
- [[Caddy]]
- [[SearXNG]]