agent: local role repoint + compaction 70%/-1; sync memories and managed skills
This commit is contained in:
1 parent
872e86399a
commit
fc049ce7fb
13 files changed
+301
-16
No files matched your search
@@ -0,0 +1,72 @@
|
||||
---
|
||||
name: compose-to-quadlet-cutover
|
||||
description: "Convert a compose-only service to a boot-persistent rootless Quadlet unit on the Dankclaw/Dankserver fleet — checklist plus the traps (CMD-SHELL healthchecks in distroless images, --format docker, EnvironmentFile HOME=/root, loopback publish posture)"
|
||||
---
|
||||
|
||||
# Compose service → boot-persistent Quadlet unit (this fleet)
|
||||
|
||||
Use when a service in `dankclaw/compose.yaml` (or `dankserver/`) must survive a
|
||||
reboot. Precedents to copy: `dankclaw/{caddy,gitea,searxng,open-webui,seerr}/quadlet/`
|
||||
and `dankclaw/observability/quadlet/` (five units, 2026-09-11).
|
||||
|
||||
## Checklist
|
||||
|
||||
1. **Unit file** at `dankclaw/<svc>/quadlet/<name>.container`, mirroring the
|
||||
compose service line for line: `Image=`, `ContainerName=<the compose name>`
|
||||
(Caddy and scrape configs address containers by name), `Network=dankclaw_default`,
|
||||
`User=0:0` when the image's own user cannot write a rootless-owned bind mount,
|
||||
`Volume=` for each mount (`:ro` preserved), `Exec=` for the compose `command:`,
|
||||
`PublishPort=`, `HealthCmd`/`HealthInterval`/`HealthTimeout`/`HealthRetries`/
|
||||
`HealthStartPeriod`, `[Service] Restart=always` + `RestartSec=10`,
|
||||
`[Install] WantedBy=default.target`. Never `systemctl --user enable` the
|
||||
generated unit — `[Install]` plus linger IS the boot persistence.
|
||||
2. **Exposure**: publish on `127.0.0.1` unless something off-host genuinely
|
||||
needs it. Caddy reaches containers by name over the shared network, and the
|
||||
homepage status timer probes loopback, so the 0.0.0.0 publish is almost
|
||||
always gratuitous and is a guard-chain bypass.
|
||||
3. **Handler** in `scripts/deploy-remote.sh`: `install_quadlet <dir> <name>` →
|
||||
`systemctl --user restart <name>.service` → `wait_for_health` on the
|
||||
loopback endpoint. Rebuild locally built images first
|
||||
(`build_compose_image`), and seed any per-service env file (see traps).
|
||||
4. **Planner**: map `dankclaw/<svc>/*` and `shared/<svc>/*` to the service in
|
||||
`scripts/deploy-plan.py`; keep dependencies ordered in `SERVICE_ORDER`.
|
||||
Add a `scripts/test-changed.sh` case *above* the generic
|
||||
`dankclaw/*/quadlet/*.container` fallback.
|
||||
5. **Retire the compose entry** in the same change — two owners for one
|
||||
container name is how a stale duplicate comes back.
|
||||
6. **Seam test** `tests/<svc>_quadlet.bats`: boot persistence, restart policy,
|
||||
no wildcard publish, container name still matching Caddy/scrape references,
|
||||
health command, deployer installs+restarts+waits, never `rm`, never
|
||||
`enable`. Migrate any older assertion that read the compose file.
|
||||
7. **Deploy** per service (`./scripts/deploy.sh dankclaw --service <name>`),
|
||||
then prove: `is-active`/`is-enabled` = active/generated, container health
|
||||
`healthy`, the endpoint answers, `list-dependencies default.target` shows the
|
||||
unit, and off-host probes on the previously published ports now refuse.
|
||||
|
||||
## Traps that cost real time
|
||||
|
||||
- **`HealthCmd` = CMD-SHELL.** podman runs it as `/bin/sh -c`; a distroless
|
||||
image has no shell, so the check fails forever. `--health-cmd` will not take
|
||||
exec form either (a JSON array is stored as one literal argument). Put
|
||||
`HEALTHCHECK ... CMD ["binary","arg"]` in the image and **build with
|
||||
`--format docker`** — the default OCI format has no HEALTHCHECK field and
|
||||
drops it with only a warning.
|
||||
- **Never point a unit's `EnvironmentFile` at the repo root `.env`.** That file
|
||||
is a gluetun env file carrying `HOME=/root`: systemd sets HOME for the
|
||||
service, podman then looks for its config under `/root`, and the unit dies
|
||||
with `stat /root/.config: permission denied`. Use a per-service file under
|
||||
`/home/dank/Containers/<svc>/env`, seeded by the deploy handler with a
|
||||
single-variable filter and validated so a missing secret fails loudly.
|
||||
- **`Network=host` exporters** that a containerized Prometheus scrapes must
|
||||
bind the host's LAN address (containers cannot reach the host loopback) plus
|
||||
`127.0.0.1` for the homepage probe — two `--web.listen-address` flags, never
|
||||
the wildcard.
|
||||
- **alloy's UI** listens on the container's loopback and 1.x refuses the `ui`
|
||||
config block, so the unit must pass `--server.http.listen-addr=0.0.0.0:12345`
|
||||
before its port can be published; without it the service has no reachable
|
||||
health endpoint and its status card reads down forever.
|
||||
- **Locally built images** need `Pull=never` and `Image=localhost/<repo>/<name>:tag`;
|
||||
registry-prefixed refs do not resolve from local storage on these hosts.
|
||||
- Compose-managed containers are replaced in place by `podman run --replace
|
||||
--rm`; verify afterwards that the container no longer carries
|
||||
`io.podman.compose.project`.
|
||||
Reference in new issue
Block a user