feat(nh3-svos): credential-free SVOS offline shell before Basic auth; contract static routes; backup drill results

svos-dev: the service worker fetches its hash-pinned INVENTORY with credentials omitted. Exactly those files plus /sw.js are now served before basic_auth (GET/HEAD, exact files). The list is generated from the release's sw.js by svos-front-inventory, which hash-checks each file and validates before reload. Gated static routes follow the web/README hosting contract: missing assets 404 instead of index.html, other document requests get index.html, everything else 404, unversioned files no-cache. acceptance-matrix.sh: 35/35 live; 16 FAIL against the previous config (positive control). git added to the nh3-svos playbook (kernel's remote check). Live backup drill: quiesced backup ~1-2 s outage; a refused freeze does NOT abort vzdump (unfrozen backup, job reported successful); both images restore-mounted read-only and verified.
This commit is contained in:
vh
2026-10-05 19:58:19 -07:00
parent 7c3eee877a
commit e369b1c769
6 changed files with 186 additions and 7 deletions
+39 -2
View File
@@ -33,6 +33,21 @@ https://svos.phasefinal.com:443 {
request_header -Forwarded
request_header -X-SVOS-Peer
# 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
}
basic_auth argon2id SVOS {
import /etc/caddy/svos-basic-auth.accounts
}
@@ -46,10 +61,32 @@ https://svos.phasefinal.com:443 {
handle @connector {
reverse_proxy 127.0.0.1:8403
}
handle {
# 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
try_files {path} /index.html
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
}
}
}