# SVOS front door on nh3-svos (eshpfi configs/svos-front/), 2026-10-05. Prime: mesh-only, svos.phasefinal.com. # - Listens ONLY on the tailnet address (100.64.0.9:443). There is no LAN or internet listener and no :80 redirect server. # - Cert: Let's Encrypt via Cloudflare DNS-01 with a phasefinal.com-only token (env CF_API_TOKEN, /etc/caddy/env). # - Identity: HTTP Basic auth (Prime 2026-10-05: "Basic auth is fine, saved in browser is fine"; mesh-only because we # don't host externally). One account, `Vuong` (Prime is how SVOS addresses him). argon2id hash in # /etc/caddy/svos-basic-auth.accounts (root:caddy 0640, NOT in git); plaintext vaulted nh3-svos/basic-auth-vuong. # The WhoIs/forward_auth gate that preceded this was retired the same day (every mesh node is login `pfi`). # - Client-supplied identity/forwarding headers are deleted BEFORE auth. After auth, Tailscale-User-Login is set to # the authenticated user ({http.auth.user.id}) and Authorization is deleted: the kernel's human path (INV-U1-06) # refuses any request that carries an Authorization header, and the app has no business seeing the password. # reverse_proxy sets X-Forwarded-Proto=https and X-Forwarded-For itself; Host passes through; Origin untouched. # - App side (svos-dev): kernel 127.0.0.1:8400 (/api/* with the prefix stripped, /api/health is the kernel's), # connector 127.0.0.1:8403 (/health, /verbs/ping|pins|pin|unpin, /openapi.json), static UI /opt/svos/current/static. { email lkraven@lkraven.com auto_https disable_redirects admin localhost:2019 # Hosting bounds (svos-dev 2026-10-05, svoperatingsystem web/README.md Hosting contract): request headers within 5 s, # the whole request body within 15 s. NO write/response deadline: /api/stream (SSE) must stay open. Measured on a # replica 2026-10-05 before deploy: SSE 25 s and a 20 s slow response survive on h1 and h2; a stalled body is cut at # 15 s (h1 504, h2 502); unfinished headers are closed at 5 s. servers { timeouts { read_header 5s read_body 15s } } } https://svos.phasefinal.com:443 { bind 100.64.0.9 tls { dns cloudflare {env.CF_API_TOKEN} resolvers 1.1.1.1 8.8.8.8 } route { # Whole families by prefix (Caddy header-delete wildcard; tested 2026-10-05: case-insensitive, and # X-Forwardedness / Tailscale-Other survive), plus the named strays outside them. request_header -Tailscale-User-* request_header -X-Forwarded-* request_header -Tailscale-Headers-Info request_header -X-Real-IP request_header -Forwarded request_header -X-SVOS-Peer # Request bodies capped at exactly 1 MiB (1,048,576 B, = the kernel dispatcher's own cap) on every route; # only /api/* and the connector read bodies at all. Over the cap -> 413. Responses are not limited. request_body { max_size 1MiB } # Credential-free offline shell (svos-dev 2026-10-05): the service worker fetches its hash-pinned INVENTORY # with credentials:'omit', so exactly those files plus /sw.js are served BEFORE Basic auth, GET/HEAD only, # exact files (no fallback). Public build bytes only; no API or connector path. The path list is GENERATED # from the live release's sw.js by /usr/local/sbin/svos-front-inventory, which must run after every release. @public_shell { method GET HEAD import /etc/caddy/svos-public-inventory } handle @public_shell { @revalidate path /sw.js /index.html /manifest.webmanifest /favicon.svg /icons/* header @revalidate Cache-Control no-cache root * /opt/svos/current/static file_server } # Previous release's hashed assets across a rollout (svos-dev 2026-10-05): open workers restore evicted inventory # credential-free. Generated by the same tool: a matcher + handle rooted in THAT release's static dir, or # comments only when there is no previous release. import /etc/caddy/svos-public-previous basic_auth argon2id SVOS { import /etc/caddy/svos-basic-auth.accounts } request_header Tailscale-User-Login {http.auth.user.id} request_header -Authorization handle_path /api/* { reverse_proxy 127.0.0.1:8400 } @connector path /health /verbs/ping /verbs/pins /verbs/pin /verbs/unpin /openapi.json handle @connector { reverse_proxy 127.0.0.1:8403 } # Static routes per svoperatingsystem web/README.md "Hosting contract" (gated): # / and /status -> index.html; the static route space -> exact files, a missing file is 404 (never index.html); # other HTML document requests -> index.html (the app renders Page not found); everything else -> 404. @shell_doc path / /status handle @shell_doc { header Cache-Control no-cache root * /opt/svos/current/static rewrite * /index.html file_server } @static path /assets/* /icons/* /favicon.svg /manifest.webmanifest /index.html /sw.js handle @static { @revalidate path /sw.js /index.html /manifest.webmanifest /favicon.svg /icons/* header @revalidate Cache-Control no-cache root * /opt/svos/current/static file_server } @other_doc expression header({'Sec-Fetch-Dest': 'document'}) || header({'Accept': '*text/html*'}) handle @other_doc { header Cache-Control no-cache root * /opt/svos/current/static rewrite * /index.html file_server } handle { respond 404 } } }