Files
Parsons/docs/reference/client-demo-deploy.md
Richie cd7f99f59d Wire demo for production deploy + add server config
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>
2026-04-20 17:22:40 +10:00

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:ro
    

    Restart 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:

  1. SSL is valid (no cert warning)
  2. Browser asks for username + password
  3. 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 --delete to 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

  1. Build the new app under src/demo/apps/<new-slice>/ (mirror arrangement/'s structure).
  2. npm run demo:build -- --mode <new-slice>.
  3. ./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.