docs: rename docs/ -> serverAppConfigs-docs/ per system-wide PROJECTNAME-docs convention

All in-repo path references (AGENTS.md, README, old scripts, report artifacts,
test code paths) rewritten in the same commit; focused-test map extended for
.gitignore, old/scripts/*, dankclaw/comfyui/*, and report/* per this repo's
own convention ('add one before committing'). Obsidian wikilinks are
title-based and unaffected.
This commit is contained in:
Frank
2026-08-28 02:20:06 -07:00
parent 6d4a96a423
commit 5829a0bfe8
140 changed files with 1820 additions and 86 deletions
@@ -0,0 +1,128 @@
---
type: guide
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 dankclaw --service searxng
./scripts/deploy.sh dankserver --service frank-default-small
./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-default-large` | systemd + `systemctl` | Relinks and restarts only the large inference service; waits up to 20 minutes for `/health`. |
| `frank-default-small` | systemd + `systemctl` | Relinks and restarts only the small inference service; waits up to 20 minutes for `/health`. |
| Dankserver media inventory | host-managed bare containers | Compose is inventory only; the scoped deploy does not create duplicate containers. |
The shared chat template maps to the inference service on the selected target.
The inactive `frank-default-npu` unit is relink-only when explicitly selected;
it is not restarted by an unrelated large-model deployment.
## 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]]
- [LLM deployment](../LLM/deployment.md)
- [[Caddy]]
- [[SearXNG]]
@@ -0,0 +1,63 @@
# Network Architecture
One Caddy fronts both hosts. It runs as a container on Dankclaw (192.168.0.143) and terminates TLS for every service on the LAN. Pi-hole on Dankclaw answers the service names for LAN clients; Porkbun answers them for the WAN. The G36 router firewall governs external access. SearXNG outbound traffic exits through the Gluetun VPN container.
## Topology
```mermaid
graph TB
subgraph WAN["G36 router 192.168.0.1"]
EXT[Internet]
end
EXT -->|port forwards| C
subgraph Dankclaw[192.168.0.143]
C[Caddy :80 :443]
D[Pi-hole :53]
C -->|comfyui:8188| F[ComfyUI]
C -->|192.168.0.143:3000| G[Gitea quadlet]
C -->|192.168.0.143:8081| SE[SearXNG quadlet]
C -->|192.168.0.143:3005| O[OpenWebUI quadlet]
SE -->|network_mode service| GL[Gluetun VPN]
GL -->|tunnel| EXT
end
subgraph Dankserver[192.168.0.235]
C -->|192.168.0.235:8085| R[RomM]
C -->|192.168.0.235:3923| P[Copyparty]
C -->|192.168.0.235:8083| W[Calibre-Web]
C -->|192.168.0.235:32400| PL[Plex host service]
end
CLIENT[LAN clients] -->|DHCP DNS| D
```
Dankserver runs no proxy of its own. Its services expose published LAN ports, and the Dankclaw Caddy routes the names to those ports.
## Hostnames
| Scope | Convention | Resolved by |
|-------|------------|-------------|
| LAN | `romm.franksdank.cloud` → 192.168.0.143 | Pi-hole (`addn-hosts`) |
| WAN | `romm.franksdank.cloud` → 71.227.227.172 | Porkbun A record |
Users type a name and nothing else. Caddy maps the name to the upstream. The split between the two resolvers removes the need for router NAT loopback. See [[LAN-DNS]] for the flow and the router hand-off.
## TLS
Caddy holds one Let's Encrypt certificate per vhost, issued and renewed through the DNS-01 challenge via the Porkbun module. No inbound port 80/443 is required. Details in [[Caddy]].
## Gluetun Path (SearXNG only)
SearXNG attaches to the Gluetun network namespace (`network_mode: service:gluetun`). Only SearXNG shares the tunnel. Everything else on Dankclaw uses the normal LAN route. This matches KB Pattern 2 in [[Gluetun]].
## Access Policy
- The **G36 router firewall** governs what the WAN can reach. Port forwards are manual router steps; the repo tracks no router state.
- Caddy adds HTTP basic auth on the homepage and on sensitive services.
- LAN subnet: `192.168.0.0/24`.
## See Also
- [[LAN-DNS]] — name resolution, Pi-hole, router hand-off
- [[Caddy]] — vhost map, TLS, basic auth
- [[Dankserver Stack]], [[Dankclaw Stack]]
- [[Gluetun]]
- Knowledge base: [[Gluetun]], [[SearXNG]]
@@ -0,0 +1,40 @@
# Storage Policy
All persistent data uses **bind mounts**: a host directory mapped directly into the container. No named volumes. This matches the preference of this repo and the existing Gitea stack on Dankclaw.
## Why Bind Mounts
- The exact host location of every byte is visible and controllable.
- Backups are a simple `tar` of known host directories.
- The data survives container recreation and `podman-compose down`.
- Ownership is explicit: everything runs as UID 1000 (`dank`) so host tools can read it.
## Conventions
| Stack | Host root |
|-------|-----------|
| Dankserver | `/opt/<service>/...` |
| Dankclaw | `/opt/<service>/...` (Gitea keeps `/opt/gitea/data/...`) |
## Ownership
Containers run as `PUID=1000` / `PGID=1000`. Host directories must be owned by `dank`:
```bash
sudo chown -R 1000:1000 /opt/<service>
```
## Secrets
Secrets never live in bind-mounted configs that get committed. They are:
1. Set in `.env` files on the host (git-ignored, `.env.example` committed as template), or
2. Injected as environment variables from the deploy script.
The live Gitea database password exists only in `.env` on Dankclaw. A test enforces that it never appears in this repo.
## See Also
- [[Deployment Flow]]
- [[Gitea]] — the existing bind-mount stack being migrated
- [[Backblaze Integration]] — future backup target for these mounts
@@ -0,0 +1,45 @@
# Testing Policy
This repository tests its configuration at defined seams. The tests run with `bats`; run them with `./tests/run.sh` before every push.
## Seams Under Test
| Seam | File | What it asserts |
|------|------|-----------------|
| Compose validity | `tests/compose_validity.bats` | YAML parses, services have images, no named volumes |
| Env consistency | `tests/env_consistency.bats` | every `${VAR}` is defined in `.env.example`; no real secrets; the live gitea password never appears |
| Caddyfile validity | `tests/caddyfile.bats` | per-host Caddyfile, `.local` convention, balanced braces, basic auth present |
| Docs completeness | `tests/docs_completeness.bats` | every compose service has a docs page; index links every page |
| Script syntax | `tests/scripts_syntax.bats` | `bash -n` clean, executable bits, shellcheck when available |
| Deployment flow | `tests/deployment_flow.bats`, `tests/deployment_scoping.bats` | scoped dispatcher exists, targets are known, service plans isolate inference, Caddy ownership is guarded, and SSH reachability is checked (skips offline) |
## Focused pre-commit testing
For normal commits, use the focused wrapper:
```bash
./scripts/test-changed.sh
```
It reads staged paths with `git diff --cached` and runs only the mapped Bats
files. The tracked `.githooks/pre-commit` hook invokes this wrapper. Enable the
repository hook path once per checkout:
```bash
git config core.hooksPath .githooks
```
Use `--list` to inspect the selection without running tests, or `--paths FILE
...` to preview a mapping. Shared test infrastructure and unmapped paths fail
closed rather than silently skipping coverage. Run the full suite with
`./tests/run.sh` when changing shared test helpers, the test runner, or when
focused coverage is not sufficient.
## Red-Green
Tests are written first. A failing test means the seam is not satisfied; the config change that makes it pass is the only acceptable fix. No `--no-verify`, no deleting tests without approval.
## See Also
- [[Deployment Flow]]
- [[index]]