Per user direction, Caddy runs only on dankclaw. It fronts every service on both hosts: local services by container name, quadlet services by published LAN port, dankserver services by published LAN port (192.168.0.235:PORT). - dankclaw/Caddyfile: merged vhost list (was: two Caddyfiles, one per host). gitea's single vhost moves in from the retired gitea-caddy quadlet (option B). Upstreams fixed to live reality: gitea :3000, search :8081 (gluetun), chat :3005 (open-webui quadlet), media services via 192.168.0.235. Retired: llms (open-webui on dankserver is removed; chat is its successor), the dankserver homepage (302 to the shared one). - dankserver/Caddyfile, dankclaw/gitea/Caddyfile: deleted (git rm). - dnsmasq addn-hosts: every name -> 192.168.0.143 (the one Caddy host); no .235 records, no llms. - compose.yaml: removed the openwebui/postgres/gitea services (they are quadlets now); caddy service mounts the rendered Caddyfile from /home/dank/Containers/caddy, healthcheck probes the bare-IP catch-all (302). - deploy.sh: per-target logic (dankserver = pull-only, health via the shared Caddy at 192.168.0.143); renders CADDY_BASICAUTH_HASH from the host .env into the mounted Caddyfile. - .env.example: CADDY_BASICAUTH_HASH on dankclaw; Porkbun block dropped from dankserver (its Caddy is gone); SEARXNG_BASE_URL to the flat name; stale OPENAI_* block dropped (quadlet is self-contained). - tests: caddyfile.bats now asserts the single-Caddyfile invariant; dnsmasq.bats cross-checks vhosts -> .143 and the retired names.
115 lines
3.7 KiB
Bash
115 lines
3.7 KiB
Bash
#!/usr/bin/env bats
|
|
# Seam: LAN DNS (dnsmasq) on dankclaw.
|
|
#
|
|
# The dnsmasq quadlet is the LAN's authoritative DNS for the flat
|
|
# franksdank.cloud / franksdank.casa name scheme. Every Caddy vhost name
|
|
# must have an A record pointing at the host that runs it, records must
|
|
# stay inside the LAN, and the quadlet must be boot-persistent.
|
|
|
|
load helpers
|
|
|
|
DNSMASQ_DIR="$REPO_ROOT/dankclaw/dnsmasq"
|
|
QUADLET="$DNSMASQ_DIR/dnsmasq.container"
|
|
CONF="$DNSMASQ_DIR/dnsmasq.conf"
|
|
HOSTS="$DNSMASQ_DIR/addn-hosts"
|
|
|
|
@test "the dnsmasq quadlet container file exists" {
|
|
[ -f "$QUADLET" ]
|
|
}
|
|
|
|
@test "the dnsmasq quadlet is installed for boot" {
|
|
grep -q 'WantedBy=default.target' "$QUADLET"
|
|
}
|
|
|
|
@test "the dnsmasq quadlet publishes port 53 tcp and udp" {
|
|
grep -qE '^PublishPort=53:53/tcp$' "$QUADLET"
|
|
grep -qE '^PublishPort=53:53/udp$' "$QUADLET"
|
|
}
|
|
|
|
@test "the dnsmasq quadlet restarts on failure" {
|
|
grep -q '^Restart=always' "$QUADLET"
|
|
}
|
|
|
|
@test "dnsmasq.conf exists and pins upstream resolvers" {
|
|
[ -f "$CONF" ]
|
|
grep -q '^no-resolv' "$CONF"
|
|
grep -qE '^server=[0-9.]+$' "$CONF"
|
|
}
|
|
|
|
@test "dnsmasq.conf loads the addn-hosts record file" {
|
|
grep -q '^addn-hosts=' "$CONF"
|
|
}
|
|
|
|
@test "every addn-hosts line is a valid IPv4 address plus hostnames" {
|
|
local bad=0 line
|
|
while IFS= read -r line; do
|
|
[ -z "$line" ] && continue
|
|
case "$line" in \#*) continue ;; esac
|
|
if ! echo "$line" | grep -qE '^([0-9]{1,3}\.){3}[0-9]{1,3}[[:space:]]+[a-z0-9.-]+([[:space:]]+[a-z0-9.-]+)*$'; then
|
|
echo "BAD LINE: $line"
|
|
bad=1
|
|
fi
|
|
done < "$HOSTS"
|
|
[ "$bad" -eq 0 ]
|
|
}
|
|
|
|
@test "every addn-hosts IP is inside the homelab LAN" {
|
|
! awk '!/^[[:space:]]*#/ && NF {print $1}' "$HOSTS" \
|
|
| grep -vE '^(192\.168\.0\.[0-9]{1,3})$' \
|
|
| grep -q .
|
|
}
|
|
|
|
@test "every addn-hosts name is under franksdank.cloud or franksdank.casa" {
|
|
local bad=0 line ip n
|
|
while IFS= read -r line; do
|
|
[ -z "$line" ] && continue
|
|
case "$line" in \#*) continue ;; esac
|
|
ip="$(echo "$line" | awk '{print $1}')"
|
|
for n in $(echo "$line" | cut -d' ' -f2-); do
|
|
echo "$n" | grep -qE '\.(franksdank\.(cloud|casa))$' \
|
|
|| { echo "UNEXPECTED TLD on $ip: $n"; bad=1; }
|
|
done
|
|
done < "$HOSTS"
|
|
[ "$bad" -eq 0 ]
|
|
}
|
|
|
|
# vhost_names <caddyfile> — print site-block names under the flat scheme
|
|
vhost_names() {
|
|
grep -oE '^(http://)?[a-z0-9.-]+\.franksdank\.(cloud|casa)[[:space:]]*\{' "$1" \
|
|
| sed -E 's#^http://##; s/[[:space:]]*\{$//'
|
|
}
|
|
|
|
# record_ip_for <name> — print the IP a name maps to in addn-hosts
|
|
record_ip_for() {
|
|
awk -v n="$1" '{ip=$1; for (i=2; i<=NF; i++) if ($i==n) print ip}' "$HOSTS" | head -1
|
|
}
|
|
|
|
# Caddy is consolidated on dankclaw (192.168.0.143): it fronts every service
|
|
# on both hosts, so every vhost name resolves to that one IP.
|
|
|
|
@test "every Caddyfile vhost has an A record on 192.168.0.143" {
|
|
local names n ip
|
|
names="$(vhost_names "$REPO_ROOT/dankclaw/Caddyfile")"
|
|
[ -n "$names" ] || { echo "no .franksdank vhosts found in dankclaw/Caddyfile"; return 1; }
|
|
while IFS= read -r n; do
|
|
[ -z "$n" ] && continue
|
|
ip="$(record_ip_for "$n")"
|
|
[ "$ip" = "192.168.0.143" ] || { echo "$n -> '$ip' (want 192.168.0.143)"; return 1; }
|
|
done <<< "$names"
|
|
}
|
|
|
|
@test "no addn-hosts records point at the old per-host split (192.168.0.235)" {
|
|
! awk '!/^[[:space:]]*#/ && NF {print $1}' "$HOSTS" | grep -q '^192\.168\.0\.235$'
|
|
}
|
|
|
|
@test "the retired llms name has no A record" {
|
|
! grep -qE '\billms\.franksdank\.cloud\b' "$HOSTS"
|
|
}
|
|
|
|
@test "no secret material in the dnsmasq files" {
|
|
local f
|
|
for f in "$DNSMASQ_DIR"/*; do
|
|
has_no_leaked_secret "$f"
|
|
done
|
|
}
|