Files
serverAppConfigs/dankclaw/Caddyfile
T
Frank c7ced90aca Caddyfile: serve the homepage via handle blocks (fix the /admin/ 401)
Caddy SORTS top-level directives, so a 'rewrite' runs before 'basicauth'.
The rewrite changed /admin/ to /admin.html, and the path-based auth matchers
then saw /admin.html (which matched the family 'not path /admin/*' block, not
the operator '/admin/*' block) — so the operator password was checked against
the family hash and 401'd.

Serve each page from its own 'handle' block (which preserves order, so the
auth is checked on the original path before any file rewrite):
- handle /admin* : operator (frank) only, try_files /admin.html
- handle @data   : shared data + assets, family (dank) OR operator (frank), so
  the admin page's fetches of services.json/status.json reuse the operator
  credential
- handle         : everything else (the user page), family (dank)
2026-08-23 18:33:54 -07:00

231 lines
6.5 KiB
Caddyfile

# Homelab Caddyfile — the single Caddy for both hosts.
#
# Caddy runs as a container on dankclaw (192.168.0.143, this repo's
# compose.yaml service 'caddy') and fronts every service on both hosts:
# - dankclaw services: by container name (compose network) or by the
# published LAN port (quadlet services on isolated networks)
# - dankserver services (192.168.0.235): by published LAN port
#
# Naming: flat franksdank.cloud scheme (see docs/Infrastructure/LAN-DNS.md).
# DNS for these names is served by the dnsmasq quadlet on this host
# (dankclaw/dnsmasq/addn-hosts — every name resolves to 192.168.0.143)
# and handed to LAN clients by the SURFboard G36 router.
#
# The basicauth hash below is a placeholder. deploy.sh renders the real
# hash (CADDY_BASICAUTH_HASH from the host .env, git-ignored) into
# /home/dank/Containers/caddy/Caddyfile, which the container mounts.
#
# TLS: real Let's Encrypt certs. Site addresses carry no http:// prefix,
# so Caddy serves HTTPS (and auto-redirects HTTP). Certs are wildcard
# (dns-01) via the global acme_dns block below, using the Porkbun module
# built into the franksdank/caddy-porkbun image (caddy/Containerfile).
# That needs no inbound ports and works from the LAN, where dnsmasq
# resolves these names to 192.168.0.143.
{
# Admin stays ON: it binds to 127.0.0.1:2019 inside the container and the
# port is not published, so nothing outside the container can reach it.
# It powers the config file watcher, so a rendered Caddyfile change
# hot-reloads without a container restart. 'admin off' once left a broken
# basicauth hash sitting on disk unapplied for hours (2026-08-22).
email admin@franksdank.cloud
# Solve ACME challenges for every site via Porkbun DNS (no port 80/443
# reachability needed). Credentials come from the container environment
# (compose.yaml, from the host .env — never committed).
acme_dns porkbun {
api_key {env.PORKBUN_API_KEY}
api_secret_key {env.PORKBUN_API_SECRET_KEY}
}
}
# --- Bare-IP access (health checks, convenience) ---
:80 {
redir https://dankclaw.franksdank.cloud/ 302
}
# --- Homepage (password protected) ---
dankclaw.franksdank.cloud {
root * /srv/homepage
# Operator-only admin page (Q19). Auth is checked on the original path,
# before any rewrite, so the path matcher is reliable. Caddy sorts a
# top-level 'rewrite' before 'basicauth'; that would change the path and
# break the path-based auth matchers. 'handle' preserves order.
handle /admin* {
basicauth {
frank REPLACE_WITH_ADMIN_HASH
}
try_files /admin.html
file_server
}
# Shared data + assets: the family OR the operator, so both pages can load
# them (the browser reuses the page's credential for these fetches). A
# 'handle' inline matcher is a single token, so the multi-path match is a
# named matcher.
@data path /services.json /status.json /app.js /style.css /deployed.js
handle @data {
basicauth {
dank REPLACE_WITH_HASH
frank REPLACE_WITH_ADMIN_HASH
}
file_server
}
# Everything else (the user page): the family (Q18).
handle {
basicauth {
dank REPLACE_WITH_HASH
}
try_files /index.html
file_server
}
}
# --- Legacy name: the media host has no Caddy of its own anymore ---
dankserver.franksdank.cloud {
redir https://dankclaw.franksdank.cloud/ 302
}
# ═══════════════════════════════════════════════════════════════
# Services on this host (dankclaw, 192.168.0.143)
# ═══════════════════════════════════════════════════════════════
# --- SearXNG (quadlet; behind the Gluetun tunnel, published 8081) ---
search.franksdank.cloud {
basicauth {
dank REPLACE_WITH_HASH
}
reverse_proxy 192.168.0.143:8081
}
# --- ComfyUI ---
comfyui.franksdank.cloud {
reverse_proxy comfyui:8188
}
# --- OpenWebUI (quadlet on slirp4netns; published 3005) ---
chat.franksdank.cloud {
basicauth {
dank REPLACE_WITH_HASH
}
reverse_proxy 192.168.0.143:3005
}
# --- Hermes ---
hermes.franksdank.cloud {
basicauth {
dank REPLACE_WITH_HASH
}
reverse_proxy hermes:8090
}
# --- Gitea (quadlet; published 3000) ---
gitea.franksdank.cloud {
reverse_proxy 192.168.0.143:3000
}
# --- Grafana ---
grafana.franksdank.cloud {
basicauth {
dank REPLACE_WITH_HASH
}
reverse_proxy grafana:3000
}
# --- Prometheus ---
prometheus.franksdank.cloud {
basicauth {
dank REPLACE_WITH_HASH
}
reverse_proxy prometheus:9090
}
# --- Loki ---
loki.franksdank.cloud {
reverse_proxy loki:3100
}
# ═══════════════════════════════════════════════════════════════
# Services on dankserver (192.168.0.235) — published LAN ports
# ═══════════════════════════════════════════════════════════════
# --- Plex (existing, kept) ---
# http:// on purpose: franksdank.casa is not hosted in the same Porkbun
# account as franksdank.cloud, so the DNS-01 provider cannot issue a
# Let's Encrypt cert for it. It stays plain-HTTP legacy; the canonical
# name is plex.franksdank.cloud (HTTPS, below).
http://plex.franksdank.casa {
reverse_proxy 192.168.0.235:32400
}
plex.franksdank.cloud {
reverse_proxy 192.168.0.235:32400
}
# --- Calibre-Web ---
books.franksdank.cloud {
reverse_proxy 192.168.0.235:8083
}
# --- RomM ---
romm.franksdank.cloud {
basicauth {
dank REPLACE_WITH_HASH
}
reverse_proxy 192.168.0.235:8085
}
# --- Copyparty ---
files.franksdank.cloud {
basicauth {
dank REPLACE_WITH_HASH
}
reverse_proxy 192.168.0.235:3923
}
# --- Jellyfin ---
jellyfin.franksdank.cloud {
reverse_proxy 192.168.0.235:8096 {
transport http {
versions h2c 1.1
}
}
}
# --- *arr Stack ---
radarr.franksdank.cloud {
reverse_proxy 192.168.0.235:7878
}
sonarr.franksdank.cloud {
reverse_proxy 192.168.0.235:8989
}
lidarr.franksdank.cloud {
reverse_proxy 192.168.0.235:8686
}
readarr.franksdank.cloud {
reverse_proxy 192.168.0.235:8787
}
prowlarr.franksdank.cloud {
reverse_proxy 192.168.0.235:9696
}
qbittorrent.franksdank.cloud {
reverse_proxy 192.168.0.235:8080
}
# --- Optional: Jackett ---
# jackett.franksdank.cloud {
# reverse_proxy 192.168.0.235:9117
# }
# --- Optional: Whisparr ---
# whisparr.franksdank.cloud {
# reverse_proxy 192.168.0.235:6969
# }