feat: add Caddy reverse proxy Salt state for palinks.app (#425) #439
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ldraney/pal-e-platform!439
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "425-caddy-palinks-app"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #425
Summary
Adds
salt/states/caddy/with an init.sls and Caddyfile.j2 template to manage the Caddy reverse proxy on the Hetzner edge-proxy node. Routespalinks.appto its Tailscale backend with TLS SNI and redirectswww.palinks.appto the apex domain.Changes
salt/states/caddy/init.sls— Salt state managing Caddyfile deployment via file.managed and Caddy service lifecycle via service.running with watch on config changessalt/states/caddy/Caddyfile.j2— Jinja2 template with palinks.app reverse_proxy block (TLS SNI to Tailscale backend) and www-to-apex permanent redirectDiscovered Scope
None
Terraform Changes
N/A
Test Plan
salt 'edge-proxy' state.apply caddysalt 'edge-proxy' cmd.run 'cat /etc/caddy/Caddyfile'salt 'edge-proxy' cmd.run 'systemctl status caddy'curl -I https://palinks.app(expect 200)curl -I https://www.palinks.app(expect 301 to apex)Review Checklist
Related Notes
ldraney/pal-e-platform #425— Configure Caddy reverse proxy for palinks.app on edge-proxydns-custom-domain-waves— Three-wave chain: Terraform/DNS, Caddy, app configPR #439 Review
DOMAIN REVIEW
Stack: Salt / Caddy (Infrastructure)
Two new files under
salt/states/caddy/:init.sls-- Salt statefile.managedwith correct ownership (root:root), mode (0644), source, andtemplate: jinja-- follows standard Salt patterns.service.runningwithenable: Trueandwatchon the file state -- correct pattern. Caddy will reload when the Caddyfile changes. (Note: Caddy'sreloadcommand is graceful, andservice.runningwithwatchtriggers a full restart via systemd. This is fine for a config-only change, but for future reference, Caddy supportscaddy reloadfor zero-downtime config changes. Not blocking.)Caddyfile.j2-- Caddy templatetls_server_name palinks.tail5b443a.ts.net) is correctly configured so Caddy validates the upstream Tailscale certificate. Good practice.reverse_proxyto port 443 with explicittransport http { tls_server_name }is the correct Caddy pattern for HTTPS upstream with custom SNI.redir ... permanent(HTTP 301) -- correct.Template without Jinja expressions: The file is named
.j2and rendered withtemplate: jinja, but contains zero Jinja expressions ({{ }},{% %}). This is forward-looking design (ready for pillar data), not a bug, but it means the template engine adds overhead with no current benefit. Not blocking.BLOCKERS
None.
NITS
No Jinja expressions in
.j2template -- Consider either (a) adding a comment explaining the template is intentionally static pending pillar-driven multi-domain support, or (b) pillarizing the domain/backend mapping now so the pattern is established before #434. Low priority.service.runningvscaddy reload-- Salt'swatchtriggers a full service restart via systemd. Caddy supportscaddy reload --config /etc/caddy/Caddyfilefor zero-downtime config application. Acmd.waitstate callingcaddy reloadinstead of a full restart would be more graceful, but this is only relevant under load. Not blocking for the current single-domain setup.Hardcoded Tailscale hostname --
palinks.tail5b443a.ts.netappears twice in the Caddyfile (once inreverse_proxy, once intls_server_name). When adding more domains, extract this to pillar data to keep the template DRY.SOP COMPLIANCE
feat: add Caddy reverse proxy Salt state for palinks.app (#425))PROCESS OBSERVATIONS
VERDICT: APPROVED
PR #439 Review
Title: feat: add Caddy reverse proxy Salt state for palinks.app (#425)
Parent Issue: #425
DOMAIN REVIEW
Tech stack: Salt (SaltStack states + Jinja2 templates), Caddy reverse proxy configuration.
Salt compliance (
salt/states/caddy/init.sls):caddy-caddyfile,caddy-service) -- good.file.managedcorrectly sets ownership (root:root), mode (0644), and usestemplate: jinjafor Jinja2 rendering.service.runningwithenable: Trueandwatchon the file state is the correct pattern -- Caddy will reload on config changes. Confirmed: Caddy supports graceful reload onsystemctl reload, and Salt'sservice.runningwithwatchtriggers a restart (not reload). This is acceptable for a single-site config but see nit below.landscaping-assistant.app(#434) andwestsidekingsandqueens.comare added. Not blocking for this PR.Caddy configuration (
salt/states/caddy/Caddyfile.j2):palinks.tail5b443a.ts.net:443matches the Tailscale tailnet domain defined interraform/variables.tf(tail5b443a.ts.net). The palinks service is exposed via the Tailscale subnet router advertising10.43.0.0/16, making this hostname reachable from the edge-proxy node (which is on the tailnet via cloud-init).tls_server_name palinks.tail5b443a.ts.netin the transport block ensures Caddy validates the upstream TLS certificate against the correct SNI -- essential since the public domain (palinks.app) differs from the upstream hostname. Correct security practice.www.palinks.appredirect withpermanent(301) and{uri}path preservation is correct.tlsdirective needed on the site blocks -- Caddy's automatic HTTPS via ACME (Let's Encrypt) handles TLS for bothpalinks.appandwww.palinks.app. Cloud-init installs Caddy and the Hetzner firewall opens ports 80/443 (confirmed interraform/modules/hetzner-edge/main.tf), so ACME HTTP-01 challenge will succeed.transport httpblock does not settlsexplicitly, which means Caddy will use HTTPS to the upstream (port 443) but with default TLS settings. Thetls_server_namealone is sufficient -- Caddy infers TLS from the port. Correct.Architecture alignment:
docs/hetzner-edge.md: DNS (GoDaddy A record at 178.156.129.142) -> Hetzner edge Caddy -> Tailscale mesh -> k3s palinks pod. DNS records confirmed interraform/dns.tf(godaddy_dns_record.palinks_a).BLOCKERS
None.
tail5b443a.ts.netis not a secret (it is a public-facing MagicDNS domain suffix, also already documented interraform/variables.tfand multiple docs files).NITS
service.runningwatch triggers restart, not reload. Salt's default behavior when a watched state changes is to restart the service. Caddy supports graceful reload (systemctl reload caddy), which avoids dropping in-flight connections. Consider adding- reload: Trueto theservice.runningstate for zero-downtime config updates:Not blocking because this is the first domain and downtime during reload is negligible, but will matter when multiple domains are served.
top.slsnot updated. The newcaddystate is not added tosalt/states/top.sls. Currently,top.slstargetsarchboxandlucass-macbook-air-1-- neither is the edge-proxy. The edge-proxy node is not yet a Salt minion target intop.sls. This means the state cannot be applied viasalt '*' state.highstate-- it requires manual targeting (salt 'edge-proxy' state.apply caddy). This is noted in the test plan, so it appears intentional. If the edge-proxy minion is expected to be added totop.slslater, this is fine. Just flagging for awareness.Hardcoded Tailscale hostname in template.
palinks.tail5b443a.ts.netis hardcoded in the Jinja2 template. When additional domains are added (#434), this template will need refactoring to use pillar data (e.g.,pillar['caddy']['sites']with per-domain upstream config). Not blocking for a single-domain first pass, but the comment "To add a new domain: add a site block below" invites copy-paste rather than pillar-driven rendering. Consider a TODO comment noting the pillar refactor plan.transport httpnaming. The Caddy transport block is namedhttpeven though it speaks HTTPS to the upstream. This is correct Caddy syntax (thehttptransport module handles both HTTP and HTTPS), but a brief inline comment clarifying this would help future readers unfamiliar with Caddy internals.SOP COMPLIANCE
Closes #425present -- issue linkage correctPROCESS OBSERVATIONS
watchdirective ensures config changes propagate to the service.docs/hetzner-edge.mdalready covers the architecture. No docs gap.VERDICT: APPROVED