Fixes images 404'ing under /arrangement/ — Vite's publicDir copies assets to the build root, but the base prefix is only applied to bundled assets (JS/CSS), not to runtime URL strings. assetUrl() helper resolves paths against import.meta.env.BASE_URL so '/images/foo.png' becomes '/arrangement/images/foo.png' in production while staying '/images/foo.png' in dev. - src/demo/shared/assets.ts — assetUrl() helper - providers.ts + DemoNav.tsx — wrap all public asset paths - nginx/parsons-demos.conf — swag site-conf for parsons.tensordesign.com.au (asset cache regex above SPA fallback regex per nginx first-match rule) - docs/reference/client-demo-deploy.md — server runbook (DNS, swag SUBDOMAINS, mount, htpasswd, deploy loop) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5.6 KiB
Client demo deploy — runbook
How to set up parsons.tensordesign.com.au for the first time, and how to push updates after.
Companion to client-demo-hosting-plan.md, which has the why and the architecture. This file is the actionable how.
One-time server setup
You'll do this once. After that, deploys are a single script.
1. DNS
Point parsons.tensordesign.com.au at your home IP (or DDNS hostname). One A-record, same as your other subdomains on tensordesign.com.au.
Verify after propagation (can take minutes):
dig +short parsons.tensordesign.com.au
Should return your home IP.
2. swag — add parsons to SUBDOMAINS
In your swag container's environment (compose file or docker run flags), add parsons to the comma-separated SUBDOMAINS list. Then restart the container:
docker compose restart swag
# or: docker restart swag
Watch the logs until you see Let's Encrypt issue the cert:
docker logs -f swag
# look for: "Certificate for parsons.tensordesign.com.au issued"
3. Document root on the host
Pick where the static files live on the host filesystem. Suggested:
sudo mkdir -p /srv/parsons-demos
sudo chown -R "$USER:$USER" /srv/parsons-demos
Make sure swag has access to it. Either:
-
Mount it into swag at
/config/www/parsons-demos/(preferred — keeps swag's container view tidy):# in your swag compose service: volumes: - /srv/parsons-demos:/config/www/parsons-demos:roRestart swag after editing compose.
-
Or symlink inside the existing swag config volume — works but messier.
4. Drop the nginx config in
The repo has the conf at nginx/parsons-demos.conf. Copy it into swag's site-confs/ directory:
cp nginx/parsons-demos.conf /path/to/swag/config/nginx/site-confs/
docker compose exec swag nginx -t # syntax check
docker compose exec swag nginx -s reload
If nginx -t complains, fix before reloading (a bad config will take swag down).
5. Create the htpasswd
Pick a username (suggestion: client) and a strong shared password:
docker compose exec swag htpasswd -c /config/nginx/.htpasswd-parsons client
# prompts for password
For additional users later (e.g. one credential per client engagement), drop the -c:
docker compose exec swag htpasswd /config/nginx/.htpasswd-parsons another-user
6. Verify the auth + 404
Visit https://parsons.tensordesign.com.au/ in a fresh browser. You should see:
- SSL is valid (no cert warning)
- Browser asks for username + password
- After auth: empty page or 404 (no slices deployed yet — that's fine)
If you see this far, the server is ready.
Per-deploy workflow
Once setup is done, the loop is two commands:
# 1. Build the slice
npm run demo:build -- --mode arrangement
# 2. Push it up
./scripts/deploy-demo.sh arrangement
The deploy script:
- Verifies
dist-demo/arrangement/exists and isn't empty (aborts if not — won't rsync a half-built bundle over a working demo) rsync -az --deleteto the server (removes stale asset hashes)- Prints the URL to visit
Before first deploy: edit scripts/deploy-demo.sh and set:
TARGET_HOST="<your-ssh-user>@tensordesign.com.au"TARGET_BASE="/srv/parsons-demos"(or wherever you put the document root)
Make sure SSH key auth works (ssh "$TARGET_HOST" echo ok should succeed without a password prompt) so rsync doesn't stall.
The script lives in scripts/ which is gitignored, so your server-specific paths won't leak into the repo.
Adding a second slice later
- Build the new app under
src/demo/apps/<new-slice>/(mirrorarrangement/'s structure). npm run demo:build -- --mode <new-slice>../scripts/deploy-demo.sh <new-slice>.
The nginx config catches /<anything>/... automatically — no server changes needed for new slices.
Optional: landing page at /
Until you have one, https://parsons.tensordesign.com.au/ returns 404. To add a tiny index listing available slices:
cat > /srv/parsons-demos/index.html <<'EOF'
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Parsons demos</title></head>
<body style="font-family: system-ui; max-width: 40em; margin: 4em auto; padding: 0 1em">
<h1>Parsons demos</h1>
<ul>
<li><a href="/arrangement/">Arrangement flow</a></li>
</ul>
</body>
</html>
EOF
Add <li> entries as new slices ship.
Troubleshooting
Cert didn't issue. Check swag logs (docker logs swag). Common causes: DNS not propagated yet, port 80 blocked by router, SUBDOMAINS value missing or misspelled.
nginx -t fails after dropping the conf. Most likely a path mismatch — /config/www/parsons-demos doesn't exist inside the container because the bind mount isn't set. Check docker compose config to confirm the mount is in effect.
Auth prompt loops / 401s after correct password. htpasswd file path mismatch between conf and htpasswd -c location. Both must agree.
Demo loads but assets 404. Vite base path mismatch. The build must use --mode <slice> so assets are prefixed with /<slice>/. Re-run the build and check dist-demo/<slice>/index.html — script src should look like /<slice>/assets/index-XXX.js.
rsync stalls or asks for password. SSH key auth not set up. Run ssh-copy-id <TARGET_HOST> once.
Want to roll back a deploy. rsync with --delete is irreversible — there's no built-in undo. Either keep the previous build locally and re-deploy, or rebuild the previous git commit. For demo-grade work this is fine; if you need versioned deploys later, switch to dated subfolders + a symlink swap.