diff --git a/deploy/Caddyfile.demo b/deploy/Caddyfile.demo new file mode 100644 index 0000000..a425c68 --- /dev/null +++ b/deploy/Caddyfile.demo @@ -0,0 +1,37 @@ +# The live block on cloud-2, kept here so the config is reviewable in the repo. +# Copy of /etc/caddy/Caddyfile's demo section — if you change one, change both. + +# Both names serve the same build from the same root. The prerendered HTML +# carries pointing at the primeintellectgrowth name, so +# the second hostname does not split search ranking between two copies. +demo.primeintellectgrowth.com, demo.lumbridgecorp.com { + # REQUIRED, and its absence is silent. Without it Caddy builds a second + # server on *:443 that has never heard of this hostname, and public traffic + # — which NATs to 10.0.0.2 — falls through to an empty 200 behind a + # perfectly valid certificate. A --resolve check from cloud-2 still passes, + # so the only way to catch it is to curl the real hostname from elsewhere. + bind 10.0.0.2 + import secweb + encode zstd gzip + root * /var/www/demo.primeintellectgrowth.com + + # The whole point of this site is being found and read, so it says so + # explicitly. primeintellectgrowth.com itself serves noindex. + header X-Robots-Tag "index, follow" + + @assets path /assets/* + header @assets Cache-Control "public, max-age=31536000, immutable" + @docs not path /assets/* + header @docs Cache-Control "no-cache" + + # No SPA fallback. Every route is prerendered to its own index.html, so a + # fallback to the app shell would answer 200 for a typo'd URL and let a + # crawler index unlimited copies of the homepage. + try_files {path} {path}/index.html + file_server + + handle_errors { + rewrite * /404.html + file_server + } +} diff --git a/deploy/README.md b/deploy/README.md index a21d70b..cecdf1e 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -10,11 +10,30 @@ pnpm build && bash deploy/deploy.sh | | | |---|---| +| Hostnames | `demo.primeintellectgrowth.com` (canonical) and `demo.lumbridgecorp.com` (alias) | | Host | cloud-2, `ubuntu@100.92.185.76` (tailnet only) | | Root | `/var/www/demo.primeintellectgrowth.com` | | Snapshots | `…-rollbacks/`, last 10, hard-linked | -| DNS | OCI zone `primeintellectgrowth.com` → `170.9.14.61`, explicit A record, no wildcard | -| Caddy | a block appended to `/etc/caddy/Caddyfile` | +| DNS | OCI zone `primeintellectgrowth.com` → `170.9.14.61`, explicit A record, no wildcard. The alias needs no record: `lumbridgecorp.com` has a wildcard | +| Caddy | one block for both names, mirrored in `Caddyfile.demo` | + +## Two hostnames, one root + +Both names serve the same build. The prerendered HTML carries +`` and `og:url` pointing at the **primeintellectgrowth** +name on every route, so the alias does not split search ranking or make a shared +link ambiguous about which site it belongs to. + +⚠️ **`lumbridgecorp.com` resolves on a wildcard.** Every subdomain of it points +at cloud-2 whether or not Caddy has a block for it — so an unconfigured or +mistyped name completes DNS, opens TLS, finds no certificate for that SNI, and +fails the handshake with `ERR_SSL_PROTOCOL_ERROR`. That reads as "the site is +broken" when it means "that is not a site". `primeintellectgrowth.com` has no +wildcard, which is why the canonical name needed an explicit A record. + +`deploy.sh` smoke-tests the alias for exactly this reason: if the block is ever +edited to drop the second name, the failure is a TLS error rather than a 404, +and nothing else would notice. ## The trap that costs an afternoon diff --git a/deploy/deploy.sh b/deploy/deploy.sh index 80ffceb..410ba19 100755 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -14,6 +14,11 @@ HOST="${PIG_DEMO_HOST:-ubuntu@100.92.185.76}" ROOT="/var/www/demo.primeintellectgrowth.com" SNAPS="${ROOT}-rollbacks" URL="https://demo.primeintellectgrowth.com" +# Served from the same root under a second name. Checked because DNS for +# lumbridgecorp.com is a WILDCARD: the alias resolves whether or not Caddy knows +# about it, and an unconfigured name fails the TLS handshake outright rather +# than 404ing — which reads as "the site is down", not "wrong hostname". +ALIAS="https://demo.lumbridgecorp.com" cd "$(dirname "$0")/.." @@ -69,4 +74,8 @@ for path in /sitemap.xml /robots.txt /og/wordle.png; do [ "$code" = "200" ] || { echo "FAILED: $path returned $code"; exit 1; } done +alias_code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 30 "$ALIAS/" || echo 000) +echo " alias $ALIAS -> $alias_code" +[ "$alias_code" = "200" ] || { echo "FAILED: the alias hostname returned $alias_code"; exit 1; } + echo "==> live: $URL"