--- name: dankclaw-expose-service description: "Expose a new service at *.franksdank.cloud on Dankclaw: quadlet container, Caddy site block, Pi-hole LAN record (custom.list), Porkbun public record; includes API vhost/volume gotchas proven on the ntfy deploy." --- # Expose a service on Dankclaw (franksdank.cloud) Proven end-to-end 2026-10-11 with ntfy. Four layers, each with a known trap. ## 0. Survey first ```bash ssh dankclaw 'ls ~/.config/containers/systemd/; grep -nE "franksdank|anubis" /home/dank/Containers/caddy/Caddyfile' ``` - Caddy runs as quadlet `caddy.container`; config at `/home/dank/Containers/caddy/Caddyfile` (NOT `/etc/caddy` on host). - Sites fronted by `anubis:8923` get bot-challenge. **CLI/API publishers (ntfy, webhooks, git) MUST bypass anubis** — use the `gitea`/`ai` pattern (direct `reverse_proxy`). ## 1. Quadlet container (user `dank`) Write `~/.config/containers/systemd/.container`: ```ini [Unit] Description= After=network-online.target Wants=network-online.target [Container] ContainerName= Image= Network=dankclaw_default Volume=-cache:/var/lib/ [Service] Restart=always RestartSec=5 [Install] WantedBy=default.target ``` `Network=dankclaw_default` ⇒ Caddy reaches it as `:`. Then `systemctl --user daemon-reload && systemctl --user start .service`. Rootless podman: bare containers never boot-persist — quadlet is mandatory. ## 2. Caddy route Append site block (wildcard `*.franksdank.cloud` cert already issued via Porkbun DNS-01; no new cert work): ``` .franksdank.cloud { reverse_proxy : } ``` Validate, then reload in-place: ```bash podman exec caddy caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile podman exec caddy caddy reload --config /etc/caddy/Caddyfile ``` ## 3. LAN DNS (Pi-hole v6, container `pihole`) Traps, in order of wasted turns: - API is NOT at `127.0.0.1:8083/api` on the host port path — requests need `-H "Host: pi.hole"`. - `[[dns.records]]` in pihole.toml and `gravity.db` `domain` table: absent/unused on this box. - **Working location:** `/home/dank/Containers/pihole/etc-pihole/hosts/custom.list` (dnsmasq addn-hosts), format `IP FQDN`: ```bash echo "192.168.0.143 .franksdank.cloud" | sudo tee -a /home/dank/Containers/pihole/etc-pihole/hosts/custom.list >/dev/null podman exec pihole pihole restartdns ``` ## 4. Public DNS (Porkbun, for off-LAN clients) Keys: `/home/dank/Containers/caddy/env` (`PORKBUN_API_KEY`, `PORKBUN_API_SECRET_KEY`). ```bash curl -sL -X POST https://api.porkbun.com/api/json3/domain/api/create/franksdank.cloud \ -H "Content-Type: application/json" \ -d "{\"apikey\":\"$K\",\"secretapi\":\"$S\",\"name\":\"\",\"type\":\"A\",\"content\":\"71.227.227.172\",\"ttl\":\"600\"}" ``` - `json3` path + `-L` (302-redirects to v3). Public IP: 71.227.227.172 (router forwards 443 → Dankclaw). - Porkbun edge throws intermittent 503s; retry later or do it in the UI. LAN path works without this record. ## 5. Verify ```bash # from Dankclaw (host): getent hosts .franksdank.cloud → 192.168.0.143 # workstation w/ stale resolved negative cache: use curl --resolve .franksdank.cloud:443:192.168.0.143 # workstation's own `sudo resolvectl flush-caches` needs a terminal (no passwordless sudo) — negative cache expires on its own curl -sf --resolve .franksdank.cloud:443:192.168.0.143 https://.franksdank.cloud/ ``` Publish-style test for ntfy inside container (CLI flags vary by version; raw HTTP is version-proof): ```bash podman exec wget -qO- --header="Title: smoke" --post-data="up" http://localhost:/ ```