GAIOS WebDeploy — Fact Sheet

The one true path to publish a website on a new *.goldnetgroup.com.au subdomain. Self-service from any minister seat. Stamped 2026-06-06 · canon:skill-publish-subdomain-2026-06-06

1 · TL;DR — one curl

Build your site into a directory, tar it, POST it. The API creates the Cloudflare DNS record, the Caddy vhost, reloads, provisions HTTPS, and verifies — then returns the live URL.
cd /path/to/your/built/site
tar czf /tmp/site.tar.gz .
curl -s -X POST https://ai.goldnetgroup.com.au/deploy/deploy \
  -H "x-api-key: $YOUR_FLEET_KEY" \
  -H "Content-Type: application/octet-stream" \
  -H "X-Subdomain: mysite" \
  --data-binary @/tmp/site.tar.gz
Success = {"success":true,"url":"https://mysite.goldnetgroup.com.au","verified":true,"http_status":200}. If verified is not true, you are NOT done.

2 · How it works, step by step

Build your static site locally (HTML/CSS/JS, any assets). Single index.html at the tar root is the minimum.
Tar it from inside the dir (tar czf /tmp/site.tar.gz .) so index.html sits at the archive root — not nested under a folder.
POST the tarball to https://ai.goldnetgroup.com.au/deploy/deploy with your fleet key and X-Subdomain. This host is already in every minister's egress allowlist.
The gateway authenticates your fleet key, injects the deploy secret server-side, and forwards to the Zeus Deploy API over a private tunnel.
Zeus: creates the Cloudflare A record (DNS-only) → extracts files to the webroot → writes the Caddy vhost → reloads Caddy → Caddy provisions a Let's Encrypt cert → HTTPS HEAD verify.
You get JSON back. Confirm verified:true and http_status:200, then report the url in your tracker.
JSON variant (if your files are already on the Zeus webroot path): POST {"subdomain":"mysite","local_path":"/var/www/mysite"} with Content-Type: application/json. Tarball (octet-stream) is the normal path for a minister.

3 · Architecture — what each host does

minister container
   |  curl https://ai.goldnetgroup.com.au/deploy/  (x-api-key: fleet key)
   v
GAIOS nginx  -- fleet-key gate · GET/POST only · injects Bearer secret · sets X-Client-ID --+
   |                                                                                        |
   v  127.0.0.1:29001                                                                       |
zeus-deploy-tunnel.service  -- ssh -L (restricted nologin 'tunnel' user, port-forward only)-+
   v  zeus 127.0.0.1:19001
Zeus Deploy API v2.0 (/opt/zeus-deploy/deploy_api.py)
   +- Cloudflare API --> A record  mysite -> 116.203.22.173  (proxied=False, DNS-only)
   +- /var/www/mysite/  (webroot, tar-slip-safe extraction)
   +- /etc/caddy/sites/mysite.caddy  (vhost; Caddy provisions Let's Encrypt cert)
   +- HTTPS HEAD verify --> {success, url, verified, http_status}
GOLDEN RULE — one gate for everyone (Sabour, 2026-06-06): every deploy, whether by a minister or the supervisor, goes through the SAME public gate with a per-agent suffixed fleet key. The tunnel-direct path is emergency-operator-only (nginx down); routine use of it bypasses attribution and is a discipline violation.
Why the minister never holds a secret: the only credential they use is their own fleet key (already issued). The deploy secret lives in a root-only nginx include on GAIOS and in the Zeus unit env — injected at the edge, never in transit to the container. The X-Client-ID is set by the gateway from the authenticated key, so attribution can't be forged.

4 · Rules

RuleDetail
HostZeus only (116.203.22.173) — the canonical web host for ALL new GoldNet sites. Never GAIOS, PC, or anywhere else. canon:web-host-rule
Subdomain nameMust match ^[a-z0-9][a-z0-9-]{0,40}$. Reserved names (www, ai, api, vault, fleet, grotto, relay, mail…) are rejected.
OwnershipFirst successful deployer owns the subdomain (recorded from X-Client-ID). Overwriting someone else's site needs header X-Overwrite: true + a posted justification.
Tarball≤50 MB. Absolute paths and symlinks are stripped server-side (tar-slip-safe). Exclude .git, secrets, env files, node_modules.
ReportA site claim in any tracker MUST include the deploy JSON (verified:true + http_status:200). No JSON = NOT DONE.
Share HTTPS onlyNever share an http:// URL. The returned url is always HTTPS. canon:deploy-postflight Rule 0
MethodsThe public gate allows GET + POST only. Site deletion (DELETE) is operator-only and not exposed to the fleet.

5 · DNS & HTTPS — the trap that cost hours

The Cloudflare A record MUST be proxied=False (grey cloud / DNS-only). The API hardcodes this — do not change it.

Why: the zone SSL mode is Flexible, but Caddy on Zeus terminates real TLS via Let's Encrypt. If the record is proxied=ON (orange cloud), Cloudflare's Flexible edge talks HTTP to an HTTPS origin and you get an infinite HTTP 308 redirect loop (this is exactly what broke unswai on 2026-06-02). DNS-only lets Caddy own the cert end-to-end. canon:rule-zeus-caddy-cf-record-dns-only-2026-06-02
Cert timing: first HTTPS hit may lag a few seconds while Caddy completes the ACME handshake. The API retries verification; if it returns verified:true the cert is live. If you see a TLS error immediately after a 200 from the API, wait ~10s and re-fetch.

6 · Render gate — HTTP 200 is not "done" for JS sites

A 200 means the server returned the HTML. It does not mean the page rendered. Three structural failures have shipped behind a green 200: a backslash-escaped </script> swallowing the body, a module-scope JS error aborting every loader, and a JSON-schema rename that silently unbound every chart.

For any JS/data-driven site, run the two-tier render gate before declaring done: Level-A (static checks for forbidden patterns) and Level-B (headless Playwright on Zeus — load the live URL, assert zero console errors and that expected content/charts actually painted). canon:rule-web-deliverable-render-gate-v2

7 · Failure modes (history-proven)

SymptomCause / cure
http:000 / site never resolvesDeploy never reached the API — you bypassed the gate or built manually. Use the curl in §1; the path through ai.goldnetgroup.com.au/deploy/ is the only one that works from a container.
Infinite 308 redirect loopCF record was proxied=ON. The API now forces DNS-only; if you see this on an old site, flip the record to grey cloud. §5
401 from the gateMissing/invalid fleet key in x-api-key. Use your per-agent key — the suffix must exist in the gateway map (ministers + -COWORK for the supervisor, added 2026-06-06).
400 invalid subdomain / 403 reservedName fails the regex or is reserved. §4
409 owned by anotherSomeone else owns it. Pass X-Overwrite: true + justify, or pick a new name.
200 but page blankJS render failure — 200 ≠ rendered. Run the Level-B render gate. §6
"never succeeded adding a subdomain"CLOSED 2026-06-06. The deploy tool existed since April but was localhost-only on Zeus and undocumented; ministers had no CF creds/ssh and a 2-host egress allowlist. Now exposed + taught. canon:skill-publish-subdomain-2026-06-06

8 · Ops & files

WhatWhere
Public entryhttps://ai.goldnetgroup.com.au/deploy/ (nginx claude-hq, fleet-key gate, secret include /etc/nginx/snippets/deploy-secret.conf)
TunnelGAIOS zeus-deploy-tunnel.service → 127.0.0.1:29001 → zeus 127.0.0.1:19001 (restricted 'tunnel' user, port-forward only)
Deploy APIZeus zeus-deploy.service · /opt/zeus-deploy/deploy_api.py (v2.0, hardened 2026-06-06)
Webroots / vhosts/var/www/<name>/ · /etc/caddy/sites/<name>.caddy
Secretvault file /opt/gaios/secrets/zeus-deploy-secret (rotated 2026-06-06)
EndpointsPOST /deploy · GET /sites · GET /verify/<name> (all fleet-gated) · DELETE operator-only
Canonskill-publish-subdomain-2026-06-06 · web-host-rule · rule-zeus-caddy-cf-record-dns-only-2026-06-02 · deploy-postflight · rule-web-deliverable-render-gate-v2 · fact-zeus-subdomain-api-cloudflare-https