DNS names: flat franksdank.cloud scheme + dnsmasq LAN DNS on dankclaw

Phase 1 of the local-DNS-name work. Users type a name, not a port.

- dnsmasq quadlet on dankclaw (rootless, slirp4netns, port 53 published):
  serves the flat name scheme from addn-hosts, upstream 1.1.1.1/1.0.0.1.
  Image built from the in-repo Containerfile (localhost/dnsmasq:latest).
- Caddyfiles (both hosts): all *.dankclaw.local / *.dankserver.local vhosts
  renamed to flat <service>.franksdank.cloud. Explicit http:// site
  addresses for now; TLS (wildcard LE via DNS-01/Porkbun) lands in phase 2.
- Drift reconciliation (live /etc/caddy/Caddyfile on dankserver differs
  from the repo): added the live-only llms.franksdank.cloud vhost
  (OpenWebUI on :3000). Dropped the legacy search vhost from dankserver
  (the name belongs to dankclaw's gluetun-routed SearXNG; the legacy
  instance stays reachable at 192.168.0.235:8081).
- Homepage service links updated to the new names.
- New test seams (TDD): tests/dnsmasq.bats (quadlet boot-persistence,
  port 53, conf/record validity, Caddyfile<->addn-hosts cross-check on
  the right host IPs); caddyfile.bats naming-convention tests updated.

LAN clients get this DNS from the SURFboard G36 (DNS Relay -> statically
configured -> 192.168.0.143); the router change is a pending user step.
This commit is contained in:
Frank
2026-08-21 16:31:42 -07:00
parent 833f2365f1
commit 55d062ea7b
10 changed files with 274 additions and 48 deletions
+18 -10
View File
@@ -2,14 +2,22 @@
# Reverse proxy for the Dankclaw stack. Caddy runs as a container on the # Reverse proxy for the Dankclaw stack. Caddy runs as a container on the
# podman network; upstreams are container names. Homepage is password # podman network; upstreams are container names. Homepage is password
# protected. Replace the bcrypt hashes below (caddy hash-password) at deploy. # protected. Replace the bcrypt hashes below (caddy hash-password) at deploy.
#
# Naming: flat franksdank.cloud scheme (see docs/Infrastructure/Domain-DNS.md).
# DNS for these names is served by the dnsmasq quadlet on this host
# (dankclaw/dnsmasq/addn-hosts) and handed to LAN clients by the SURFboard
# G36 router (DNS Relay -> statically configured -> 192.168.0.143).
#
# Phase 1 uses explicit http:// site addresses (no TLS yet). Phase 2 drops
# the prefix and adds a wildcard Let's Encrypt cert via DNS-01 (Porkbun).
{ {
admin off admin off
email admin@dankclaw.local email admin@franksdank.cloud
} }
# --- Homepage (password protected) --- # --- Homepage (password protected) ---
dankclaw.local { http://dankclaw.franksdank.cloud {
basicauth { basicauth {
dank $2a$14$REPLACE_WITH_HASH dank $2a$14$REPLACE_WITH_HASH
} }
@@ -18,7 +26,7 @@ dankclaw.local {
} }
# --- SearXNG (behind the Gluetun tunnel) --- # --- SearXNG (behind the Gluetun tunnel) ---
search.dankclaw.local { http://search.franksdank.cloud {
basicauth { basicauth {
dank $2a$14$REPLACE_WITH_HASH dank $2a$14$REPLACE_WITH_HASH
} }
@@ -26,14 +34,14 @@ search.dankclaw.local {
} }
# --- ComfyUI --- # --- ComfyUI ---
comfyui.dankclaw.local { http://comfyui.franksdank.cloud {
reverse_proxy comfyui:8188 reverse_proxy comfyui:8188
} }
# --- OpenWebUI --- # --- OpenWebUI ---
# open-webui is a quadlet container on slirp4netns (isolated), so Caddy cannot # open-webui is a quadlet container on slirp4netns (isolated), so Caddy cannot
# reach it by container name. Proxy to its published LAN port instead. # reach it by container name. Proxy to its published LAN port instead.
chat.dankclaw.local { http://chat.franksdank.cloud {
basicauth { basicauth {
dank $2a$14$REPLACE_WITH_HASH dank $2a$14$REPLACE_WITH_HASH
} }
@@ -41,7 +49,7 @@ chat.dankclaw.local {
} }
# --- Hermes --- # --- Hermes ---
hermes.dankclaw.local { http://hermes.franksdank.cloud {
basicauth { basicauth {
dank $2a$14$REPLACE_WITH_HASH dank $2a$14$REPLACE_WITH_HASH
} }
@@ -49,12 +57,12 @@ hermes.dankclaw.local {
} }
# --- Gitea (migrated) --- # --- Gitea (migrated) ---
gitea.dankclaw.local { http://gitea.franksdank.cloud {
reverse_proxy gitea-server:3000 reverse_proxy gitea-server:3000
} }
# --- Grafana --- # --- Grafana ---
grafana.dankclaw.local { http://grafana.franksdank.cloud {
basicauth { basicauth {
dank $2a$14$REPLACE_WITH_HASH dank $2a$14$REPLACE_WITH_HASH
} }
@@ -62,7 +70,7 @@ grafana.dankclaw.local {
} }
# --- Prometheus --- # --- Prometheus ---
prometheus.dankclaw.local { http://prometheus.franksdank.cloud {
basicauth { basicauth {
dank $2a$14$REPLACE_WITH_HASH dank $2a$14$REPLACE_WITH_HASH
} }
@@ -70,7 +78,7 @@ prometheus.dankclaw.local {
} }
# --- Loki --- # --- Loki ---
loki.dankclaw.local { http://loki.franksdank.cloud {
basicauth { basicauth {
dank $2a$14$REPLACE_WITH_HASH dank $2a$14$REPLACE_WITH_HASH
} }
+4
View File
@@ -0,0 +1,4 @@
# Minimal dnsmasq image for the LAN DNS quadlet (dankclaw).
# Build on dankclaw: podman build -t localhost/dnsmasq:latest -f Containerfile .
FROM alpine:3.20
RUN apk add --no-cache dnsmasq
+34
View File
@@ -0,0 +1,34 @@
# A records for the flat franksdank.name scheme (dnsmasq addn-hosts format:
# <IP> <name> [<name> ...]). Every Caddy vhost name has exactly one record,
# pointing at the host that runs it. Keep in sync with the Caddyfiles
# (tests/dnsmasq.bats cross-checks this).
#
# 192.168.0.235 = dankserver (media/library stack)
# 192.168.0.143 = dankclaw (AI/search/dev stack)
# --- dankserver services ---
192.168.0.235 dankserver.franksdank.cloud
192.168.0.235 plex.franksdank.cloud
192.168.0.235 plex.franksdank.casa
192.168.0.235 books.franksdank.cloud
192.168.0.235 llms.franksdank.cloud
192.168.0.235 romm.franksdank.cloud
192.168.0.235 files.franksdank.cloud
192.168.0.235 jellyfin.franksdank.cloud
192.168.0.235 radarr.franksdank.cloud
192.168.0.235 sonarr.franksdank.cloud
192.168.0.235 lidarr.franksdank.cloud
192.168.0.235 readarr.franksdank.cloud
192.168.0.235 prowlarr.franksdank.cloud
192.168.0.235 qbittorrent.franksdank.cloud
# --- dankclaw services ---
192.168.0.143 dankclaw.franksdank.cloud
192.168.0.143 search.franksdank.cloud
192.168.0.143 comfyui.franksdank.cloud
192.168.0.143 chat.franksdank.cloud
192.168.0.143 hermes.franksdank.cloud
192.168.0.143 gitea.franksdank.cloud
192.168.0.143 grafana.franksdank.cloud
192.168.0.143 prometheus.franksdank.cloud
192.168.0.143 loki.franksdank.cloud
+24
View File
@@ -0,0 +1,24 @@
# dnsmasq — LAN DNS for the homelab.
#
# Runs in a rootless podman quadlet on dankclaw (192.168.0.143) with port 53
# published to the LAN. Serves the flat franksdank.cloud / franksdank.casa
# name scheme from addn-hosts (one A record per Caddy vhost); everything else
# goes to the pinned upstream resolvers. The SURFboard G36 router hands this
# server out to DHCP clients, so LAN devices resolve service names without
# per-device config. See docs/Infrastructure/LAN-DNS.md.
# The slirp netns has no useful /etc/resolv.conf; pin upstreams instead.
no-resolv
server=1.1.1.1
server=1.0.0.1
# Local service names (must stay in sync with the Caddyfile vhosts).
addn-hosts=/etc/dnsmasq/addn-hosts
# Never resolve single-label names or private/bogus ranges as public names.
domain-needed
bogus-priv
# Cache.
cache-size=1000
neg-ttl=300
+32
View File
@@ -0,0 +1,32 @@
# Quadlet unit: dnsmasq LAN DNS (rootless, boot-persistent).
#
# Install path on dankclaw: ~/.config/containers/systemd/dnsmasq.container
# Unit name: dnsmasq.service (container name: dnsmasq)
#
# The LAN's authoritative DNS for the flat franksdank.cloud / franksdank.casa
# name scheme. It serves A records from addn-hosts (one per Caddy vhost) and
# forwards everything else to pinned upstream resolvers. The SURFboard G36
# router hands this server out to DHCP clients (DNS Relay -> statically
# configured -> 192.168.0.143), so no client needs per-device config.
#
# Runs on slirp4netns with port 53 published to the LAN (the same pattern as
# open-webui). Config mounts from /opt/dnsmasq. See docs/Infrastructure/LAN-DNS.md.
[Unit]
Description=dnsmasq LAN DNS container
After=network.target
[Container]
ContainerName=dnsmasq
Network=slirp4netns
Image=localhost/dnsmasq:latest
PublishPort=53:53/tcp
PublishPort=53:53/udp
Volume=/opt/dnsmasq/dnsmasq.conf:/etc/dnsmasq.conf:ro
Volume=/opt/dnsmasq/addn-hosts:/etc/dnsmasq/addn-hosts:ro
[Service]
Restart=always
[Install]
WantedBy=default.target
+28 -23
View File
@@ -1,14 +1,22 @@
# Dankserver Caddyfile # Dankserver Caddyfile
# Deploy: copy to /etc/caddy/Caddyfile on dankserver # Deploy: copy to /etc/caddy/Caddyfile on dankserver (host Caddy service)
# See ../docs/Dankserver/ for service documentation. # See ../docs/Dankserver/ for service documentation.
#
# Naming: flat franksdank.cloud scheme (see docs/Infrastructure/Domain-DNS.md).
# DNS for these names is served by the dnsmasq quadlet on dankclaw
# (dankclaw/dnsmasq/addn-hosts) and handed to LAN clients by the SURFboard
# G36 router (DNS Relay -> statically configured -> 192.168.0.143).
#
# Phase 1 uses explicit http:// site addresses (no TLS yet). Phase 2 drops
# the prefix and adds a wildcard Let's Encrypt cert via DNS-01 (Porkbun).
{ {
admin off admin off
email admin@dankserver.local email admin@franksdank.cloud
} }
# --- Homepage (password protected) --- # --- Homepage (password protected) ---
dankserver.local { http://dankserver.franksdank.cloud {
basicauth { basicauth {
dank $2a$14$REPLACE_WITH_HASH dank $2a$14$REPLACE_WITH_HASH
} }
@@ -17,29 +25,26 @@ dankserver.local {
} }
# --- Plex (existing, kept) --- # --- Plex (existing, kept) ---
plex.franksdank.casa { http://plex.franksdank.casa {
reverse_proxy localhost:32400 reverse_proxy localhost:32400
} }
plex.franksdank.cloud { http://plex.franksdank.cloud {
reverse_proxy localhost:32400
}
plex.dankserver.local {
reverse_proxy localhost:32400 reverse_proxy localhost:32400
} }
# --- Calibre-Web (existing, kept) --- # --- Calibre-Web (existing, kept) ---
books.franksdank.cloud { http://books.franksdank.cloud {
reverse_proxy localhost:8083 reverse_proxy localhost:8083
} }
books.dankserver.local { # --- OpenWebUI (live name from the host Caddyfile; LLM chat UI on this host) ---
reverse_proxy localhost:8083 http://llms.franksdank.cloud {
reverse_proxy localhost:3000
} }
# --- RomM --- # --- RomM ---
romm.dankserver.local { http://romm.franksdank.cloud {
basicauth { basicauth {
dank $2a$14$REPLACE_WITH_HASH dank $2a$14$REPLACE_WITH_HASH
} }
@@ -47,7 +52,7 @@ romm.dankserver.local {
} }
# --- Copyparty --- # --- Copyparty ---
files.dankserver.local { http://files.franksdank.cloud {
basicauth { basicauth {
dank $2a$14$REPLACE_WITH_HASH dank $2a$14$REPLACE_WITH_HASH
} }
@@ -55,7 +60,7 @@ files.dankserver.local {
} }
# --- Jellyfin --- # --- Jellyfin ---
jellyfin.dankserver.local { http://jellyfin.franksdank.cloud {
reverse_proxy 127.0.0.1:8096 reverse_proxy 127.0.0.1:8096
transport http { transport http {
@@ -64,36 +69,36 @@ jellyfin.dankserver.local {
} }
# --- *arr Stack --- # --- *arr Stack ---
radarr.dankserver.local { http://radarr.franksdank.cloud {
reverse_proxy localhost:7878 reverse_proxy localhost:7878
} }
sonarr.dankserver.local { http://sonarr.franksdank.cloud {
reverse_proxy localhost:8989 reverse_proxy localhost:8989
} }
lidarr.dankserver.local { http://lidarr.franksdank.cloud {
reverse_proxy localhost:8686 reverse_proxy localhost:8686
} }
readarr.dankserver.local { http://readarr.franksdank.cloud {
reverse_proxy localhost:8787 reverse_proxy localhost:8787
} }
prowlarr.dankserver.local { http://prowlarr.franksdank.cloud {
reverse_proxy localhost:9696 reverse_proxy localhost:9696
} }
qbittorrent.dankserver.local { http://qbittorrent.franksdank.cloud {
reverse_proxy localhost:8080 reverse_proxy localhost:8080
} }
# --- Optional: Jackett --- # --- Optional: Jackett ---
# jackett.dankserver.local { # http://jackett.franksdank.cloud {
# reverse_proxy localhost:9117 # reverse_proxy localhost:9117
# } # }
# --- Optional: Whisparr --- # --- Optional: Whisparr ---
# whisparr.dankserver.local { # http://whisparr.franksdank.cloud {
# reverse_proxy localhost:6969 # reverse_proxy localhost:6969
# } # }
+4 -4
View File
@@ -9,25 +9,25 @@
}, },
{ {
"name": "RomM", "name": "RomM",
"url": "http://romm.dankserver.local", "url": "http://romm.franksdank.cloud",
"description": "Retro game manager and player for the ROM collection.", "description": "Retro game manager and player for the ROM collection.",
"docs": "docs/Dankserver/RomM.md" "docs": "docs/Dankserver/RomM.md"
}, },
{ {
"name": "Copyparty", "name": "Copyparty",
"url": "http://files.dankserver.local", "url": "http://files.franksdank.cloud",
"description": "High-speed file sharing and streaming server.", "description": "High-speed file sharing and streaming server.",
"docs": "docs/Dankserver/Copyparty.md" "docs": "docs/Dankserver/Copyparty.md"
}, },
{ {
"name": "Calibre-Web", "name": "Calibre-Web",
"url": "http://books.dankserver.local", "url": "http://books.franksdank.cloud",
"description": "E-book library browser and reader.", "description": "E-book library browser and reader.",
"docs": "docs/Dankserver/Calibre-Web.md" "docs": "docs/Dankserver/Calibre-Web.md"
}, },
{ {
"name": "Plex", "name": "Plex",
"url": "http://plex.dankserver.local", "url": "http://plex.franksdank.cloud",
"description": "Media server (existing host service, proxied).", "description": "Media server (existing host service, proxied).",
"docs": "docs/Dankserver/Dankserver Stack.md" "docs": "docs/Dankserver/Dankserver Stack.md"
} }
+8 -8
View File
@@ -9,49 +9,49 @@
}, },
{ {
"name": "SearXNG", "name": "SearXNG",
"url": "http://search.dankclaw.local", "url": "http://search.franksdank.cloud",
"description": "Privacy-respecting metasearch engine (through Gluetun VPN).", "description": "Privacy-respecting metasearch engine (through Gluetun VPN).",
"docs": "docs/Dankclaw/SearXNG.md" "docs": "docs/Dankclaw/SearXNG.md"
}, },
{ {
"name": "ComfyUI", "name": "ComfyUI",
"url": "http://comfyui.dankclaw.local", "url": "http://comfyui.franksdank.cloud",
"description": "Node-based AI image generation (CPU backend on Strix Halo).", "description": "Node-based AI image generation (CPU backend on Strix Halo).",
"docs": "docs/Dankclaw/ComfyUI.md" "docs": "docs/Dankclaw/ComfyUI.md"
}, },
{ {
"name": "OpenWebUI", "name": "OpenWebUI",
"url": "http://chat.dankclaw.local", "url": "http://chat.franksdank.cloud",
"description": "Chat interface for local LLMs (llama.cpp on :7770).", "description": "Chat interface for local LLMs (llama.cpp on :7770).",
"docs": "docs/Dankclaw/OpenWebUI.md" "docs": "docs/Dankclaw/OpenWebUI.md"
}, },
{ {
"name": "Hermes", "name": "Hermes",
"url": "http://hermes.dankclaw.local", "url": "http://hermes.franksdank.cloud",
"description": "Self-improving AI agent (Nous Research).", "description": "Self-improving AI agent (Nous Research).",
"docs": "docs/Dankclaw/Hermes.md" "docs": "docs/Dankclaw/Hermes.md"
}, },
{ {
"name": "Gitea", "name": "Gitea",
"url": "http://gitea.dankclaw.local", "url": "http://gitea.franksdank.cloud",
"description": "Self-hosted Git service with issues and PRs.", "description": "Self-hosted Git service with issues and PRs.",
"docs": "docs/Dankclaw/Gitea.md" "docs": "docs/Dankclaw/Gitea.md"
}, },
{ {
"name": "Grafana", "name": "Grafana",
"url": "http://grafana.dankclaw.local", "url": "http://grafana.franksdank.cloud",
"description": "Dashboards for metrics and logs.", "description": "Dashboards for metrics and logs.",
"docs": "docs/Infrastructure/Grafana.md" "docs": "docs/Infrastructure/Grafana.md"
}, },
{ {
"name": "Prometheus", "name": "Prometheus",
"url": "http://prometheus.dankclaw.local", "url": "http://prometheus.franksdank.cloud",
"description": "Metrics collection for the whole stack.", "description": "Metrics collection for the whole stack.",
"docs": "docs/Infrastructure/Prometheus.md" "docs": "docs/Infrastructure/Prometheus.md"
}, },
{ {
"name": "Loki", "name": "Loki",
"url": "http://loki.dankclaw.local", "url": "http://loki.franksdank.cloud",
"description": "Log storage and query backend.", "description": "Log storage and query backend.",
"docs": "docs/Infrastructure/Loki.md" "docs": "docs/Infrastructure/Loki.md"
} }
+8 -3
View File
@@ -12,9 +12,14 @@ load helpers
[ -f "$REPO_ROOT/dankclaw/Caddyfile" ] [ -f "$REPO_ROOT/dankclaw/Caddyfile" ]
} }
@test "Caddyfiles use the .local hostname convention" { @test "Caddyfiles use the flat franksdank.cloud hostname convention" {
grep -qE '\.dankserver\.local' "$REPO_ROOT/dankserver/Caddyfile" grep -qE '\.franksdank\.cloud' "$REPO_ROOT/dankserver/Caddyfile"
grep -qE '\.dankclaw\.local' "$REPO_ROOT/dankclaw/Caddyfile" grep -qE '\.franksdank\.cloud' "$REPO_ROOT/dankclaw/Caddyfile"
}
@test "no .local vhost names remain in either Caddyfile" {
! grep -qE '^([a-z0-9.-]+\.)?(dankserver|dankclaw)\.local[[:space:]]*\{' \
"$REPO_ROOT/dankserver/Caddyfile" "$REPO_ROOT/dankclaw/Caddyfile"
} }
@test "Caddyfiles have balanced braces" { @test "Caddyfiles have balanced braces" {
+114
View File
@@ -0,0 +1,114 @@
#!/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
}
@test "every dankserver Caddyfile vhost has an A record on 192.168.0.235" {
local names n ip
names="$(vhost_names "$REPO_ROOT/dankserver/Caddyfile")"
[ -n "$names" ] || { echo "no .franksdank vhosts found in dankserver/Caddyfile"; return 1; }
while IFS= read -r n; do
[ -z "$n" ] && continue
ip="$(record_ip_for "$n")"
[ "$ip" = "192.168.0.235" ] || { echo "$n -> '$ip' (want 192.168.0.235)"; return 1; }
done <<< "$names"
}
@test "every dankclaw 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 secret material in the dnsmasq files" {
local f
for f in "$DNSMASQ_DIR"/*; do
has_no_leaked_secret "$f"
done
}