Commit Graph
47 Commits
Author SHA1 Message Date
Frank 25c93984ad fix(build): build the loki/alloy/comfyui images in docker format
The healthcheck added to the loki image was discarded by the build:
'HEALTHCHECK is not supported for OCI image format and will be ignored'.
podman builds OCI by default, and OCI has no HEALTHCHECK field, so the
container came back with no health at all — worse than the failing check it
replaced, because nothing reports the loss.

The build now passes --format docker, and the seam pins it so a future edit
cannot quietly reintroduce an image with no health field.
2026-09-11 16:02:47 -07:00
Frank fe4eeaa03a fix(observability): seed grafana's OAuth secret into a per-service env file
The first deploy of the grafana unit failed: 'stat /root/.config: permission
denied', exit 1, restart loop. Cause — the repo-root .env that the compose
service used to pick up is a gluetun env file, and it carries HOME=/root (and
the WireGuard keys). Pointing the unit's [Service] EnvironmentFile at it sets
HOME for the service, so podman looks for its configuration under /root.

grafana now reads /home/dank/Containers/grafana/env, the per-service file the
deployer seeds from dankclaw/.env with a single-variable filter — the same
shape caddy, pihole, copyparty, romm and seerr use. The seed is validated, so
a missing secret fails the deploy loudly instead of starting a container whose
OAuth login cannot complete. The value was copied to dankclaw/.env, which is
where dankclaw/.env.example already documented it.
2026-09-11 15:59:15 -07:00
Frank dab81b91a3 feat(observability): make the five Dankclaw observability containers quadlets
prometheus, grafana, loki, alloy and node-exporter ran only from
dankclaw/compose.yaml. Rootless podman does not restart bare containers, so
they did not survive the 2026-09-11 reboot for the amd_iommu=off change and
had to be restored by hand — the same gap that had already cost the fleet
Caddy and Pi-hole, which is why the boot-persistence rule exists.

Each is now a rootless Quadlet unit under dankclaw/observability/quadlet/,
installed and dispatched by deploy_observability in deploy-remote.sh, with
the compose definitions retired so only one owner exists per container name.
Images, mounts, arguments and container names are unchanged, so Caddy's
proxies and Prometheus' own scrape config keep resolving.

Two posture changes, both about the unauthenticated surface:

* The three HTTP UIs published on 0.0.0.0 with no authentication of any kind
  (Prometheus even with the lifecycle endpoint enabled). They now publish on
  127.0.0.1: the browser path is the guarded franksdank.cloud vhost, Caddy
  reaches them by name over dankclaw_default, and the host-side homepage
  status timer probes loopback. Nothing legitimately used the LAN port.
* node-exporter bound :9100 on every interface; it now binds exactly its two
  consumers, loopback (homepage timer) and the LAN address (Prometheus, which
  runs in a container and cannot reach the host's loopback).

alloy gains a reachable health endpoint: its UI listens on the container's
own loopback and alloy 1.x refuses a config block for it, so the compose
service had nothing to probe — the homepage status card for alloy read 'down'
continuously. The unit moves the UI with a command-line flag and publishes
127.0.0.1:12345.

grafana keeps its Authentik OAuth client secret without receiving the whole
.env: systemd expands the value from a [Service] EnvironmentFile, which never
reaches the container.
2026-09-11 15:56:39 -07:00
Frank 301914b1c0 feat(llm): swap frank-large to halogen-flash-server, retire frank-npu
frank-large moves from the kingjones30/ROCmFPX llama.cpp build to
peonist-ai/halogen-flash-server 0.5.8 serving Qwen3.8-Flash-Next (w4b
.hgn 115.55 GiB + quality overlay). Upstream measures this engine at
roughly 4x the prefill throughput of the llama.cpp family on the same
gfx1151 silicon, and prefill dominates any prompt with real context.

Shape: a rootless Quadlet container (dankclaw/frank-large/quadlet/),
engine on container loopback :8730 (never published), OpenAI front-end
:8731 published as host 7770, HALOGEN_MODEL_ID=frank-large so no client
renamed anything. Context 262144, KV pool 262144 positions (~7.2 GiB,
conservative for a shared 121 GiB host), 4 slots, host reserve 24,
vision tower on, weights pinned, all other flags at vendor defaults.
Weights are fetched by HALOGEN_DOWNLOAD on first start into a writable
/home/dank/models/halogen.

The llama.cpp era (unit, env, serve script) is staged in .AGENTTRASH/
rather than deleted; the STRIX payload and the ROCmFPX build stay on
disk as the rollback target. deploy_llm_large becomes a user-quadlet
handler: it retires the old ROOT unit (moving its checkout symlink into
the home trash, never rm) and gates the first start on the download
rather than reporting a failure for a fetch that is working exactly as
designed.

frank-npu is retired in the same change. The kernel cmdline moves from
iommu=pt to amd_iommu=off for the 13-16% of prefill upstream measured,
and that takes the XDNA2 NPU with it: without PASID/SVA the driver
cannot open /dev/accel/accel0 (err=-19). Its five dependent routes are
repointed (Hermes vision -> frank-large, Hermes title generation ->
frank-small, harness vision -> frank-large, tiny -> frank-small, task ->
frank-large:medium) and its provider blocks are removed from both the
harness config and Hermes's live config. Audio input leaves the fleet
with no replacement; image input survives on halogen's vision tower.

Docs: new halogen-flash-server runbook, new IOMMU decision record, and
frank-npu rewritten as a retirement record. AGENTS.md topology, runtime
sections, benchmarking policy (frank-large is a user unit now) and the
harness model-config section are corrected.
2026-09-11 14:48:28 -07:00
Frank 69f87c8483 feat(llm): rename inference services dropping 'default' from names
frank-default-large -> frank-large (Dankclaw :7770), frank-default-small ->
frank-small (Dankserver :7770), frank-default-sightsound -> frank-npu
(Dankclaw :7771). Units, env files, serve scripts, deploy-plan mappings,
deploy-remote dispatch (stop/disable old units + relink + enable new), and the
test seams move together. Retired frank-default-npu.* artifacts moved to
scripts/.AGENTTRASH/ (tracked-in-history; ignored on disk).

Served aliases become frank-large / frank-small via --alias; the NPU endpoint
serves the whole FLM registry, so its rename changes no served tags.
2026-09-03 16:15:43 -07:00
Frank c694053950 tautulli healthcheck via curl; restore PublishPort; plex gate to 600s
- tautulli.container: HealthCmd switches to the repo's proven curl pattern
  (jellyfin unit) — quadlet's systemd-style value parsing mangles embedded
  double quotes; PublishPort=8181:8181 restored (a mis-aimed line edit
  replaced it; the PublishPort seam test caught the regression)
- deploy_plex readiness gate: 36x5s -> 120x5s; the 2026-09 cutover showed
  the first container boot of a years-old library runs ~10 min while PMS
  pre-binds 32400 and empty-replies; 180s failed the gate twice with a
  healthy, still-booting service
2026-09-01 22:56:19 -07:00
Frank 58aa084d9c quadlet: drop the userns identity model — rootless cannot map uid 997
Evidence from the failed first cutover: podman top shows container root
maps to dank (1000) under UserNS=host rootless (subuid range 100000:65536
cannot express 997), so PUID=997 fell back to root and PMS crash-looped on
write access to its 997:984-owned data root. Fleet model instead: PUID=0/
PGID=0 (container root = dank, same as jellyfin/copyparty data roots) and
deploy_plex chowns /var/lib/plexmediaserver to 1000:1000 inside the
first-cutover block. Zero bytes moved; rollback regains one chown -R
997:984 step, documented in Plex.md. Seam test pins PUID=0/PGID=0 and the
absence of any userns key.
2026-09-01 22:46:00 -07:00
Frank 93f16f68be deployer: route plex and tautulli as dankserver quadlet services
- deploy_plex(): install unit, stop+mask native plexmediaserver.service on
  first cutover (mask, never disable), gate on /identity for 180s
- deploy_tautulli(): install unit, stop+disable snap on first cutover,
  seed config.ini with PMS_Web_Host=plex only if absent
- allow-lists (deploy.sh valid_service, deploy-remote.sh service_is_allowed),
  planner routing (SERVICE_ORDER, DANKSERVER_SERVICES, quadlet_services),
  test-changed cutover case, quadlet seam enumeration + new
  userns/PUID=997/PGID=984 ownership-contract test
2026-09-01 22:31:14 -07:00
Frank 9c593476af anubis: pin v1.27.0, fleet-wide cookie domain, persistent signing key
- Image bump v1.13.0 -> v1.27.0 (deploy_anubis pull+tag updated; JSON
  policy schema unchanged at this release).
- COOKIE_DOMAIN=franksdank.cloud: the challenge cookie now covers all
  guarded *.franksdank.cloud vhosts — solve the PoW once, not per-host.
- ED25519_PRIVATE_KEY_HEX via host env file (0600) with gitea-style
  [Service] EnvironmentFile expansion: restarts no longer invalidate
  everyone's solved challenges.
- Scratch-verified v1.27.0 before pinning: policy loads, challenge/DENY/
  ALLOW all behave, X-Real-Ip now mandatory (chain always sets it).
2026-09-01 17:09:09 -07:00
Frank e513007aa4 deploy-remote: seed qB password under WebUI\\Password_PBKDF2 (qB 4.2+ ignores the legacy key) 2026-09-01 16:42:30 -07:00
Frank d2ef56442d deploy-remote: strip systemd bullet from frankbook unit resolution 2026-09-01 16:18:23 -07:00
Frank 691da478ac deploy-remote: allow authentik-outpost-ldap in service_is_allowed 2026-09-01 15:26:26 -07:00
Frank 4ad85fae70 authentik: add LDAP outpost service for Jellyfin LDAP-Auth
- New authentik-outpost-ldap quadlet: ghcr.io/goauthentik/ldap:2026.8.0,
  publishes host 389/636 -> container 3389/6636 for cross-host binds from
  dankserver's Jellyfin LDAP-Auth plugin; outpost token in host .env.
- deploy-plan.py: register authentik-outpost-ldap in SERVICE_ORDER and
  DANKCLAW_SERVICES; map dankclaw/authentik/quadlet/ unit path.
- deploy-remote.sh: deploy_authentik_outpost_ldap handler + dispatch case.
- deploy.sh: valid-service entry.
2026-09-01 15:25:56 -07:00
Frank f4c9b4f381 seerr: quadlet on dankclaw + guard-chain vhost + deployer wiring
Unit: ghcr.io/seerr-team/seerr:v3.4.1, loopback-only 5055 publish
(homepage probe; users go through Anubis->Authentik->caddy:8017->seerr),
Containers data root, no env secrets. Guard chain: seerr.franksdank.cloud
vhost behind anubis:8923 with post-auth handler on 8017; addn-hosts record;
homepage widget; deploy-remote/deploy-plan/deploy.sh routing; seam test.
2026-09-01 14:40:10 -07:00
Frank 1a257c06fe frankbook: propagate skip-state before restart, PBKDF2 qB seed
The frankbook handler ran port verification even for services the
credential gate skipped, so a parked qbittorrent failed the deploy and
aborted the five arrs. Gate now lives Mac-side (test -s gluetun/env
inside the VM) before restart+verify.

The qB first-boot seed wrote a plaintext WebUI\Password, which qB >= 4.2
ignores (PBKDF2 @ByteArray only), and omitted HostHeaderValidation=false,
which would 401 the LAN forwarder path. Seed now generates the PBKDF2
hash in the VM (python3 hashlib) and sets ServerDomains=* +
HostHeaderValidation=false. Arr categories (lidarr/radarr/tv-sonarr)
pre-created with save_path=/Volumes/COMPLETE/Finished Torrents so any
grab lands in the review dir regardless of wiring details.
2026-09-01 14:22:30 -07:00
Frank 949f68e2b5 frankbook-pro: *arr stack + qbittorrent quadlets inside podman machine
Seven units installed INSIDE the Mac VM (/home/core/.config/containers/
systemd) by a new deploy_frankbook_stack handler: gluetun (ProtonVPN
WireGuard, env-file credentials, publishes qbit's 8080/6881), qbittorrent
(Network=gluetun.container, verbatim /Volumes data roots, first-boot
config seed with fresh machine-local WebUI password and the Finished
Torrents category), and prowlarr/sonarr/radarr/lidarr/readarr (grab-only,
config migrated from dankserver at provisioning, readarr pinned to
amd64-1.0.980). deploy.sh routes the new target through the ssh-config
alias with REMOTE_SHELL=/usr/local/bin/bash (macOS bash 3.2 lacks
associative arrays); the planner learns FRANKBOOK_SERVICES and the
gluetun->qbittorrent netns pairing; new seam test pins the contract.
2026-09-01 14:10:23 -07:00
Frank 26b0d4c357 deploy-remote: privileged rename + deep merge in migrate_data_root
rename() of a directory requires write on the directory itself; mariadb/
redis data roots are owned by the containers' mapped subuids (100998),
so the deploy user cannot move them. Use run_privileged mv (numeric
ownership preserved = rootless mapping contract) and skip the chown pass
for romm via the new keep mode — chowning mapped subuid data to the
deploy uid breaks the in-container mysql/redis users. Collisions with
rescued anonymous-volume stubs now merge one level deep instead of
stranding real data under /opt. Reverse the podman rm order so the
dependent romm container is removed before its backends.
2026-09-01 13:42:31 -07:00
Frank a18bb62d30 deploy-remote: ensure quadlet unit dir exists for all handlers
dankserver's ~/.config/containers/systemd did not exist (no quadlet units
had ever been installed there); the dankserver media handlers cp'd into it
without mkdir -p, failing before touching any container.
2026-09-01 13:40:17 -07:00
Frank cdfaf48191 deploy-remote: migrate_data_root merges into pre-seeded destinations
mv of a data root onto a non-empty directory fails (Directory not empty).
The romm/copyparty cutover creates Containers/<svc>/ before migrating:
the seed env file lands there first, and the romm anonymous-volume rescue
extracts into it before the /opt move. Merge the old root's entries in
without overwriting existing state (mv -n), warn and leave residue if a
collision kept the new side, and keep the plain rename when the
destination is absent or empty.
2026-09-01 13:38:31 -07:00
Frank 9e904d23a3 deploy-remote: daemon-reload after copying hermes units, before restarts
The first deploy after a unit-file edit restarted with the stale generated
unit (generator had not seen the new Volume= paths), crash-looping hermes on
statfs ENOENT for the just-renamed data root. Reload between cp and restart.
2026-09-01 05:16:27 -07:00
Frank b206f477ad dankserver: quadlet units + deploy handlers for romm trio, jellyfin, calibre-web, copyparty
Cutover of the six surviving dankserver services from compose-era bare
containers to boot-persistent Quadlet units (WantedBy=default.target; podman
5.x v1 generator honors it at user-manager start).

Units are faithful to live inspect ground truth (image pins = running tags,
not compose inventory):
- romm trio shares one data root: /home/dank/Containers/romm mounted whole at
  /romm; mariadb datadir and redis data dir live inside it. Aliases mariadb/
  redis preserved on dankserver_appnet via Network=...:alias=. Secrets stay in
  the host-local env file (EnvironmentFile + ${VAR} expansion in the generated
  ExecStart, gitea precedent). copyparty passes its admin account via
  Exec=-a ${COPAY_ACCOUNT}:${COPAY_PASSWORD} (Exec= not Command=: the v1
  generator hard-fails on Command=); entropy stays rw only there.
- jellyfin keeps every creation-time env as Environment= lines (zero drift);
  Device=/dev/dri + GroupAdd=keep-groups preserve hardware transcoding.
- calibre-web pins the exact running image tag (id 986277563e76 =
  0.6.27-ls397), pasta network preserved.

Deployer: first-cutover handlers retire the old container, rescue unshadowed
anonymous-volume content (podman volume export + tar --skip-old-files), mv
data roots on the same fs, then start units in dependency order with health
gates. Re-deploys restart only the selected unit. deploy-plan.py maps compose
keys mariadb/redis to the new unit names; allowlists updated. New seam test
tests/dankserver_quadlet.bats pins boot-persistence + v1-generator contract;
test-changed.sh maps the new dirs to it. Full suite green (209 tests).
2026-09-01 04:14:08 -07:00
Frank 207235420c hermes: CDP browser daemon + MCP client wiring for the agent
Adds a digest-pinned headless-shell Chrome DevTools Protocol daemon as a
boot-persistent quadlet unit (chrome-devtools-protocol.container). The
entrypoint override drops the image's socat front so the CDP endpoint binds
loopback-only on 127.0.0.1:9222 in the host network namespace; only the two
host-network consumers (Signal gateway, webui server) can drive it.

hermes-webui flips Network=dankclaw_default -> Network=host so its agent
process shares the daemon's namespace (0.0.0.0:8787 bind is LAN-facing as
before; no exposure change). deploy_hermes installs the new unit and starts
it if inactive without restarting in-flight browser sessions.
2026-08-31 16:25:28 -07:00
Frank 9ef034e5e6 sightsound: move FLM NPU endpoint port 52626 -> 7771
Env file is the source of truth (unit ExecStart runs the serve script which
sources it); serve-script default and the deployer health-gate URL flipped in
lockstep so the post-restart health gate polls the new port. Docs, fleet table,
and both index bullets updated to match.
2026-08-30 14:06:54 -07:00
Frank 915d49d663 hermes: boot-persistent quadlet units for the gateway, dashboard, and webui
The trio ran as bare compose containers and died silently at the Aug 26
reboot — nothing restarts compose containers at boot. Each now has a
boot-persistent unit (WantedBy=default.target; the v1 quadlet generator on
this host cannot enable generated units). The deploy handler retires the
compose-era containers only while the units are not yet active, then
(re)starts each unit.

Units run the image under a localhost/hermes-agent:latest alias tag (the
quadlet create path on this host cannot resolve registry-prefixed refs from
local storage); the deploy handler tags first. The gateway's :8090 publish
is dropped — nothing consumes it; the public vhost now routes to the
dashboard's login-gated UI on 9119.
2026-08-30 07:22:20 -07:00
Frank cf0c541cc9 deploy-remote: allow frank-default-sightsound on the dankclaw target
The first deploy attempt pulled c3ed3a6 on the target but the remote runner
rejected the explicit --service selection because service_is_allowed() keeps
its own per-target allowlist; add the new LLM service to it.
2026-08-30 04:40:57 -07:00
Frank c3ed3a6e12 sightsound: new FLM NPU sight+sound service on XDNA2 (gemma4-it:e4b)
New Dankclaw service frank-default-sightsound: FLM 1.0.1 serving the
Gemma4-E4B-IT-NPU2 pack (vision+audio+reasoning, 131072 ctx) on the XDNA2
NPU at port 52626. Model choice rationale: gemma4-it is the only NPU2 model
family with BOTH image and audio towers; the uncensored candidate qwythos:9b
is text-only, so sight+sound wins over uncensored here.

Deployer wiring: planner maps env/unit/serve files to the new service name;
deploy-remote gains deploy_llm_sightsound (relink + daemon-reload + enable +
restart + health gate on /v1/version — FLM has no /health route). Unlike the
retired frank-default-npu relink-only path, this unit is boot-enabled.
2026-08-30 04:37:42 -07:00
Frank 2bccf2f207 authentik: deploy outpost as worker-role container with dial-back
In authentik 2026.8.x the proxy-provider URL space (/outpost.goauthentik.io/*)
is served by the Rust worker binary, not the server — verified live: with a
tenant-matching Host header, /if/flow/... answers 200 on the server while
/auth/caddy still 404s. The outpost instance existed in the DB but no process
ever served it, so Caddy's forward_auth subrequests hit a 404 and browsers got
authentik's own Not-Found page instead of the authentication flow.

The new unit runs the same image as server/worker with Exec=worker and flips
into outpost mode via AUTHENTIK_HOST/AUTHENTIK_INSECURE/AUTHENTIK_TOKEN (the
token is read from the DB into the host .env at deploy time; it never enters
the repo). It dials back to core over websocket and serves forward-auth on its
own 9000. Deploy handler restarts only this unit — the rest of the stack is
untouched.
2026-08-28 19:05:20 -07:00
Frank 21fa38e6a8 anubis: fix image ref typo in deploy pull, health probe exec form, unit alias tag
Three compounding deploy-handler bugs found by live deployment:
- podman image exists/pull used a mixed-case ghcr.io ref that podman's
  reference parser rejects before any network I/O (repository name must be
  lowercase); all refs now lowercase — ghcr resolves case-insensitively.
- the unit runs the image under a localhost/ alias tag: this host's podman
  5.7 quadlet path fails container create with 'image not known' for
  registry-prefixed refs even when present in storage (verified live).
- the health probe was shell-form and the distroless ko-build image has no
  shell; HealthCmd is now a JSON exec-form array, which passes through the
  quadlet generator's argv assembly verbatim.

Tests updated to assert the alias-tag reality plus the pull guard.
2026-08-28 11:18:41 -07:00
Frank b83b706f23 anubis: run the image under a localhost/ alias tag
On this host's podman 5.7 quadlet path, a registry-prefixed Image ref
fails to resolve from local storage at container create time even when
the image is present ('image not known'); the identical argv works from
a shell or transient unit. Verified live: an identical unit with
Image=localhost/techarohq/anubis:v1.13.0 starts immediately. The deploy
handler now tags the alias after the guarded pull; upgrades re-tag
after pulling the new tag.
2026-08-28 10:19:48 -07:00
Frank 2e82fc10b9 anubis: guard the pull with podman image exists
image inspect on an absent reference can exit non-zero for reasons
unrelated to existence in a non-tty deploy context; image exists is
the boolean primitive and cannot misreport. Absent-image behavior
(pull from ghcr) is unchanged.
2026-08-28 08:26:37 -07:00
Frank 91532a3805 anubis: repin the image ref to the lowercase ghcr path
podman's reference parser rejects mixed-case repository refs before any
network I/O ('repository name must be uppercase'); upstream's own install
docs pull lowercase (ghcr resolves case-insensitively). The first deploy
of this chain exit-2'd on that parse error — the image was not yet
pulled, so the guarded vhosts 502 until this deploy lands.
2026-08-28 07:05:24 -07:00
Frank 761b70002b deploy-remote: allow anubis in the dankclaw service inventory
The first deploy run dispatched caddy (hot reload, no restart) but exit-2'd
on the inventory guard before deploy_anubis could run: deploy.sh's
valid_service was extended but the remote runner's per-target inventory
was not. Guarded vhosts 502 until this lands.
2026-08-28 06:45:06 -07:00
Frank 0ae3650414 anubis: bot-challenge proxy in front of the guarded vhosts (two-tier chain)
Bots and AI scrapers hit the SSO-fronted vhosts and eat Authentik auth
work for 403s. Anubis verifies clients first: DENY for known AI scraper
UAs, JS challenge for everyone else; verified traffic resumes the normal
Authentik forward_auth chain on Caddy's internal listener (:8016).

Chain: client -> Caddy (TLS) -> Anubis :8923 (verify) -> Caddy :8016
(auth + routing resumes; Host survives both hops) -> app. Reverse-proxy
through Anubis, not forward_auth: at the pinned tag a CHALLENGE answers
HTTP 200 + HTML, and forward_auth treats 200 as "authenticated" — the
page would pass through unchallenged. Exempt by construction (never
routed through Anubis): plex (plain HTTP legacy), auth.* (IdP login
must be reachable ungated), gitea (git smart-HTTP clients cannot solve
JS challenges), and every app with its own login.

Pinned ghcr.io/TecharoHQ/anubis:v1.13.0 — the tag was source-verified
before pinning: at this tag the policy parser is JSON-only with a
minimal bots[] schema (no imports, no CEL expressions), flags are the
config surface via flagenv (TARGET/POLICY_FNAME map to --target/
--policy-fname), and the image's exec-form HEALTHCHECK is inherited by
the quadlet unit (dodges the v1 generator's HealthCmd space-split quirk).

Rollback nuance: stopping anubis 502s everything behind the chain (the
public listeners proxy through it); a true rollback is reverting the
Caddyfile and redeploying, not just systemctl --user stop anubis.
2026-08-28 06:38:24 -07:00
Frank 09db633dca authentik: fix quadlet v1 generator dialect on this host
Probed the live generator on dankclaw: podman 5.x v1 hard-fails unit
generation on dash-prefixed options (--shm-size killed authentik-server's
unit) and rejects Command= entirely; Exec= is the accepted role selector.
Drop --shm-size (uwsgi uses unix sockets under /run, not /dev/shm; default
64m shm is fine) and drop systemctl --user enable — generated units are
transient and cannot be enabled; WantedBy=default.target in each unit file
is what makes them boot-persistent on this generator.
2026-08-28 03:21:31 -07:00
Frank 914e251ebc authentik: boot-persistent quadlet conversion with deploy handler
The compose-era authentik trio died at every boot (rootless podman does not
auto-start bare containers). Convert the trio to Quadlet units installed by
deploy-remote.sh into ~/.config/containers/systemd/, enabled + WantedBy=
default.target:

- 3 unit files under dankclaw/authentik/quadlet/ (postgres -> server -> worker
  ordering via Wants=/After=; secrets stay in the git-ignored host .env,
  expanded by systemd at start time exactly like the gitea units)
- deploy_authentik() handler: install units, daemon-reload, retire stale
  compose-era containers only while units are inactive, enable once, then
  start in dependency order with podman-health gates between stages
- compose.yaml: authentik service definitions retired to a comment pointing at
  the quadlet dir (same pattern as searxng/open-webui/gitea/caddy)
- tests/authentik.bats rewritten from compose assertions to unit-file
  contract assertions (images, Exec roles, ordering, loopback-only publish,
  pg_isready probe, secret hygiene, dispatch routing)
2026-08-28 03:09:19 -07:00
Frank 868c8cfbca caddy: drop the systemctl enable; the quadlet generator already enables it
podman's quadlet generator creates the default.target.wants symlink from
the unit's [Install] section at every user-manager start (is-enabled
reports 'generated' for all healthy quadlets, pihole included). An
explicit 'systemctl --user enable caddy.service' fails with 'transient or
generated' and aborted the deploy after the unit was already installed.
2026-08-26 20:19:03 -07:00
Frank f3c1ac1823 pihole: also neutralize the /etc/sysctl.conf sysctl override
systemd-sysctl applies /etc/sysctl.conf AFTER all of sysctl.d/, so the
stale ip_unprivileged_port_start=80 line there beat every drop-in and kept
the runtime value at 80 after the first deploy pass. deploy_pihole now
comments out an active line in /etc/sysctl.conf (idempotent) and generalizes
the sysctl.d conflict scan to any file setting a value above 53.
2026-08-26 20:08:30 -07:00
Frank 8660209c35 caddy + pihole: make both boot-persistent and self-healing
Caddy left compose.yaml for a quadlet unit (caddy.service): joins
dankclaw_default for container-name upstreams, Pull=never for the local-only
image, Porkbun credentials via /home/dank/Containers/caddy/env written by
deploy from .env, bare-IP-catch-all health check. deploy_caddy installs,
enables, and starts the unit after retiring the bare container (state is in
host volumes).

Pi-hole: the repo now owns the sysctl drop-in
(dankclaw/pihole/sysctl/99-dnsmasq.conf, ip_unprivileged_port_start=53);
deploy_pihole installs it to /etc/sysctl.d/, stages conflicting later-sorted
drop-ins in the home trash, and applies 'sysctl --system' (the boot code
path). This fixes the 2026-08-26 crash loop (port-53 bind denied, restart
counter 1645) caused by a second drop-in resetting the value to 80.

Tests: new caddy_quadlet.bats seam; caddyfile/deployment_flow assertions
repointed from the removed compose service to the quadlet; pihole.bats pins
the sysctl drop-in and its deployment; test-changed.sh routes dankclaw/caddy/
and the pihole subtree to their seams. Docs: Caddy, Pi-hole, Dankclaw Stack,
work log.
2026-08-26 19:52:57 -07:00
Frank bb0404c310 hermes-webui: add Hermex server as loopback compose service on Dankclaw
- compose: hermes-webui runs bootstrap.py --foreground in the agent image,
  shares /home/dank/hermes-data (same uid mapping as the gateway), publishes
  127.0.0.1:8787 only; password from host .env
- planner/remote deployer: hermes-webui is an isolated Dankclaw service
- tests: deployment integrity seam (loopback, auth, volume, healthcheck,
  planner narrowing, deployer dispatch)
- docs: Hermes-WebUI page with the iOS SSH-tunnel access pattern
2026-08-26 08:17:27 -07:00
Frank 786ef4a5cf Preserve retired proxy state during migration 2026-08-25 22:02:14 -07:00
Frank 29af0c751f Make canonical Caddy recreate safely 2026-08-25 22:01:12 -07:00
Frank 8110884e8e Document canonical Caddy migration 2026-08-25 21:59:59 -07:00
Frank d52bcc15f8 Migrate Gitea proxy to canonical Caddy 2026-08-25 21:56:04 -07:00
Frank bc718f04f4 Fix scoped deployment state and config ownership 2026-08-25 20:24:47 -07:00
Frank 3a56a07883 Guard target-owned deployment handlers 2026-08-25 20:13:33 -07:00
Frank a07b8c9306 Deploy services independently by changed paths 2026-08-25 20:13:33 -07:00
Frank 1ba47e4d22 Deploy services independently by changed paths 2026-08-25 20:13:33 -07:00