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.
This commit is contained in:
Frank committed 2026-08-26 19:52:57 -07:00
1 parent 66b58a127b
commit 8660209c35
14 files changed
+248 -60

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
# Caddy environment (placeholders only). The deploy writes the live file at
# /home/dank/Containers/caddy/env from the checkout's .env; the quadlet reads
# it via EnvironmentFile. Consumed by the Caddyfile's acme_dns block
# (DNS-01 wildcard cert issuance). PORKBUN_API_SECRET_KEY is the name the
# caddy-dns/porkbun module expects.
PORKBUN_API_KEY=changeme
PORKBUN_API_SECRET_KEY=changeme
+54
View File
@@ -0,0 +1,54 @@
# Quadlet unit: canonical Caddy reverse proxy + ACME (rootless, boot-persistent).
#
# Install path on dankclaw: ~/.config/containers/systemd/caddy.container
# Unit name: caddy.service (container name: caddy)
#
# The homelab's single Caddy: it fronts every service on both hosts and issues
# Let's Encrypt wildcard certs via DNS-01 (Porkbun). It left compose.yaml on
# 2026-08-26 after a reboot exposed that the bare compose container never came
# back: rootless Podman has no boot-time auto-start for bare containers, and
# `restart: unless-stopped` does not survive the explicit stop that shutdown
# performs. See AGENTS.md "Boot Persistence & Self-Healing" and
# docs/Infrastructure/Caddy.md.
#
# It joins the existing compose network (dankclaw_default) so the Caddyfile's
# container-name upstreams (authentik-server, grafana, ...) still resolve; if
# no compose service has created that network yet at boot, quadlet creates it
# first and compose reuses it by name later. The image is built locally from
# dankclaw/caddy/Containerfile and exists only on this host, so Pull=never: a
# boot must not stall on a registry that does not have it. Porkbun credentials
# come from /home/dank/Containers/caddy/env (deploy writes it from the
# checkout's .env; secrets never enter the repo).
[Unit]
Description=Caddy reverse proxy + ACME (Porkbun DNS-01) container
After=network-online.target
Wants=network-online.target
[Container]
ContainerName=caddy
Image=franksdank/caddy-porkbun:latest
Pull=never
Network=dankclaw_default
PublishPort=0.0.0.0:80:80/tcp
PublishPort=0.0.0.0:443:443/tcp
EnvironmentFile=/home/dank/Containers/caddy/env
Volume=/home/dank/Containers/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
Volume=/home/dank/Containers/caddy/data:/data
Volume=/home/dank/Containers/caddy/config:/config
# The homepage: the host's /srv/homepage (deploy-managed, not the repo's
# shared/homepage).
Volume=/srv/homepage:/srv/homepage:ro
# The bare-IP catch-all answers 302 (curl -f passes on <400); this proves
# Caddy itself is serving without touching any upstream.
HealthCmd=curl -sf -o /dev/null http://localhost:80/
HealthInterval=30s
HealthTimeout=10s
HealthRetries=3
[Service]
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
+4 -40
View File
@@ -310,44 +310,8 @@ services:
authentik-postgres:
condition: service_healthy
# --- Reverse proxy + homepage (the homelab's single Caddy) ---------------
# The Caddyfile mounted here is the RENDERED copy (basicauth hash filled in
# from the host .env by scripts/deploy.sh); the repo copy carries the
# REPLACE_WITH_HASH placeholder. Certs/config live under
# Caddy (the homelab's single reverse proxy) left compose on 2026-08-26:
# it is boot-persistent via its Quadlet unit (dankclaw/caddy/quadlet/
# caddy.container), installed by deploy_caddy in scripts/deploy-remote.sh.
# The rendered Caddyfile, certs, and config still live under
# /home/dank/Containers/caddy (rootless-writable).
caddy:
# Locally built: stock Caddy + the Porkbun DNS provider
# (dankclaw/caddy/Containerfile). See that file for build instructions.
image: franksdank/caddy-porkbun:latest
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
environment:
# Consumed by the Caddyfile's acme_dns block (DNS-01 wildcard certs).
PORKBUN_API_KEY: ${PORKBUN_API_KEY}
PORKBUN_API_SECRET_KEY: ${PORKBUN_API_SECRET_KEY}
volumes:
- /home/dank/Containers/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- /home/dank/Containers/caddy/data:/data
- /home/dank/Containers/caddy/config:/config
# The homepage: the host's /srv/homepage. The deploy copies the static
# files there; the status checker writes status.json and the deploy
# writes deployed.js there. (Not the repo's shared/homepage, which
# lacks the generated files.)
- /srv/homepage:/srv/homepage:ro
depends_on:
- authentik-server
- comfyui
- hermes
- grafana
- prometheus
- loki
healthcheck:
# The bare-IP catch-all answers 302 (curl -f passes on <400); this
# proves Caddy itself is serving without touching any upstream.
test: ["CMD", "curl", "-sf", "-o", "/dev/null", "http://localhost:80/"]
interval: 30s
timeout: 10s
retries: 3
+8
View File
@@ -0,0 +1,8 @@
# Rootless DNS must bind port 53: lower the unprivileged-port threshold so a
# rootless container may publish 0.0.0.0:53 (80/443 stay allowed; 53 < 80).
# Single source of truth for net.ipv4.ip_unprivileged_port_start on dankclaw;
# deploy_pihole installs it to /etc/sysctl.d/ and applies it with
# `sysctl --system` (the same code path boot uses via systemd-sysctl).
# A later-sorted drop-in that reset this to 80 took LAN DNS down after the
# 2026-08-26 reboot — deploy_pihole neutralizes such conflicts.
net.ipv4.ip_unprivileged_port_start=53