Files
karti-ai 1cd1d3bbba
ci / web (push) Successful in 3m34s
ci / python (push) Successful in 3m44s
Serve the site under demo.lumbridgecorp.com as well
One Caddy block, both names, same root. The prerendered HTML already carries
<link rel="canonical"> and og:url pointing at the primeintellectgrowth name on
every route, so the alias does not split search ranking or leave a shared link
ambiguous about which site it belongs to. Verified byte-identical: both
hostnames return the same sha256 for /demos/wordle.

Worth recording why the alias failed before this rather than 404ing.
lumbridgecorp.com resolves on a WILDCARD, so every subdomain of it points at
cloud-2 whether or not Caddy knows the name. DNS completes, TLS opens, Caddy
finds no certificate for that SNI and aborts the handshake — the browser reports
ERR_SSL_PROTOCOL_ERROR, which reads as "the site is down" rather than "wrong
hostname". primeintellectgrowth.com has no wildcard, which is why the canonical
name needed an explicit A record.

deploy.sh now smoke-tests the alias too: if the block is ever edited to drop the
second name, the failure mode is a TLS error, and nothing else would catch it.

The live block is mirrored into deploy/Caddyfile.demo so the config is
reviewable in the repo rather than only on the host.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019mt6sHQHEnEYrJZvoMCJSB
2026-08-28 17:06:30 -07:00

82 lines
3.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# Ship dist/ to cloud-2.
#
# Run from a machine on the tailnet (cloud-2's :22 is tailnet-only, which is
# also why a GitHub-hosted runner cannot do this).
#
# bash deploy/deploy.sh
#
# Keeps the last 10 releases as hard-linked snapshots, so a rollback is a
# directory rename rather than a rebuild.
set -euo pipefail
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")/.."
[ -d dist ] || { echo "no dist/ — run 'pnpm build' first"; exit 1; }
[ -f dist/index.html ] || { echo "dist/index.html missing"; exit 1; }
# The prerender pass is what makes shared links preview correctly. A dist
# without it builds and serves fine, which is exactly why it needs asserting.
[ -f dist/404.html ] || { echo "dist/404.html missing — did prerender run?"; exit 1; }
[ -f dist/sitemap.xml ] || { echo "dist/sitemap.xml missing — run 'pnpm build', not 'vite build'"; exit 1; }
echo "==> preflight on ${HOST}"
ssh "$HOST" "set -e
free=\$(df --output=avail -BG / | tail -1 | tr -dc 0-9)
[ \"\$free\" -ge 3 ] || { echo \"only \${free}G free on /\"; exit 1; }
mkdir -p '$SNAPS'
if [ -d '$ROOT' ] && [ -n \"\$(ls -A '$ROOT' 2>/dev/null)\" ]; then
# Double quotes, not single. Inside single quotes the remote shell never
# expands the command substitution, so every deploy wrote into a single
# directory named after the un-expanded literal, and the SECOND deploy died
# on 'File exists'. It looked like it worked for exactly as long as there
# had only ever been one deploy. Note this comment is inside a
# double-quoted ssh payload: anything it names gets expanded too.
cp -al '$ROOT' \"$SNAPS/\$(date +%Y%m%d-%H%M%S)\"
fi
ls -1dt '$SNAPS'/*/ 2>/dev/null | tail -n +11 | xargs -r rm -rf"
echo "==> rsync"
rsync -az --delete --checksum dist/ "$HOST:$ROOT/"
echo "==> smoke test against the public hostname"
# Against the real name from THIS machine, never --resolve from cloud-2: a
# missing `bind 10.0.0.2` in the Caddy block serves an empty 200 to the
# internet while a local --resolve check still passes.
code=$(curl -sS -o /tmp/pigdemo-smoke.html -w '%{http_code}' --max-time 30 "$URL/")
size=$(wc -c < /tmp/pigdemo-smoke.html)
echo " / -> $code, ${size}b"
[ "$code" = "200" ] || { echo "FAILED: / returned $code"; exit 1; }
[ "$size" -gt 1000 ] || { echo "FAILED: / is ${size}b — almost certainly the empty-200 bind bug"; exit 1; }
grep -q '<div id="root"' /tmp/pigdemo-smoke.html || { echo "FAILED: no app root in the HTML"; exit 1; }
demo=$(curl -sS -o /tmp/pigdemo-demo.html -w '%{http_code}' --max-time 30 "$URL/demos/wordle")
echo " /demos/wordle -> $demo"
[ "$demo" = "200" ] || { echo "FAILED: demo route returned $demo"; exit 1; }
grep -q 'og:title' /tmp/pigdemo-demo.html || { echo "FAILED: demo route has no baked og tags"; exit 1; }
missing=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 30 "$URL/nope-not-a-page")
echo " /nope-not-a-page -> $missing"
[ "$missing" = "404" ] || { echo "FAILED: unknown path returned $missing, expected 404"; exit 1; }
for path in /sitemap.xml /robots.txt /og/wordle.png; do
code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 30 "$URL$path")
echo " $path -> $code"
[ "$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"