Files
command-center/agent/managed-skills/dankclaw-expose-service/SKILL.md
T

3.6 KiB

name, description
name description
dankclaw-expose-service 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

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/<name>.container:

[Unit]
Description=<name>
After=network-online.target
Wants=network-online.target
[Container]
ContainerName=<name>
Image=<image>
Network=dankclaw_default
Volume=<name>-cache:/var/lib/<name>
[Service]
Restart=always
RestartSec=5
[Install]
WantedBy=default.target

Network=dankclaw_default ⇒ Caddy reaches it as <name>:<port>. Then systemctl --user daemon-reload && systemctl --user start <name>.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):

<name>.franksdank.cloud {
	reverse_proxy <name>:<port>
}

Validate, then reload in-place:

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:
echo "192.168.0.143 <name>.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).

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\":\"<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

# from Dankclaw (host):        getent hosts <name>.franksdank.cloud   → 192.168.0.143
# workstation w/ stale resolved negative cache: use curl --resolve <name>.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 <name>.franksdank.cloud:443:192.168.0.143 https://<name>.franksdank.cloud/

Publish-style test for ntfy inside container (CLI flags vary by version; raw HTTP is version-proof):

podman exec <name> wget -qO- --header="Title: smoke" --post-data="up" http://localhost:<port>/<topic>