# 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`](./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):

```bash
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:

```bash
docker compose restart swag
# or: docker restart swag
```

Watch the logs until you see Let's Encrypt issue the cert:

```bash
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:

```bash
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):

  ```yaml
  # 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:

```bash
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:

```bash
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`:

```bash
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:

```bash
# 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:

```bash
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.
