Add deploy script and skill for the production LXC

There is no CI, so a git push deploys nothing - the image has to be built
and the stack restarted separately. This captures that as one command
rather than a sequence to remember.

The image is built on the LXC and tagged with the registry name the
compose file already references, so compose finds it locally and never
pulls. That means no registry credentials are needed on either machine.

Also records the two things that cost the most time to work out: the
public hostname resolves to the reverse proxy rather than the container
(192.168.2.169), and the live stack runs from /root/drinktracker while
the checkouts under /home/drinkadmin are stale.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
JP
2026-08-08 17:02:20 +00:00
parent a0e1619072
commit 45d9bd12d7
2 changed files with 185 additions and 0 deletions

71
deploy/README.md Normal file
View File

@@ -0,0 +1,71 @@
# Deploying drinktracker
```bash
./deploy/deploy.sh # typecheck, push, build on the LXC, restart, verify
./deploy/deploy.sh --yes # skip the confirmation prompt
./deploy/deploy.sh --push # also publish the image to the Gitea registry
```
## Where things actually live
| | |
|---|---|
| Production LXC | `192.168.2.169` (hostname `drinktracker`) |
| Deploy user | `drinkadmin`, key `~/.ssh/drinktracker_ed25519` |
| Live compose dir | `/root/drinktracker` |
| Image tag | `192.168.2.140:3000/jpscott84/drinktracker:latest` |
| App port | 3000 (host network), unauthenticated requests return 307 → `/login` |
Two traps worth knowing:
**`drinktracker.tenseconddelay.net` is the reverse proxy (192.168.2.172), not the container.** `NEXTAUTH_URL` points at the public name, so resolving it and SSHing there fails confusingly — the key looks rejected because it's a different machine.
**The two checkouts under `/home/drinkadmin/` are stale.** Only `/root/drinktracker` is live; that's what the running container's compose labels point to.
## How it works
The image is built **on the LXC** and tagged with the registry name that `docker-compose.prod.yml` already references. Compose finds it locally and never pulls, so no registry credentials are needed on either machine. `--push` publishes it as well, which is only useful if something else pulls that tag.
There is no CI, so **`git push` on its own deploys nothing** — it only updates the Gitea repo. The build and restart have to happen separately, which is what this script is for.
## One-time setup
The script needs passwordless sudo on the LXC, because the deployment lives under `/root`. On the container as root:
```bash
echo 'drinkadmin ALL=(root) NOPASSWD: ALL' > /etc/sudoers.d/drinkadmin-deploy
chmod 440 /etc/sudoers.d/drinkadmin-deploy
visudo -c
```
This grants less than it appears to: `drinkadmin` is already in the `docker` group, and anyone who can talk to the Docker socket can mount the host filesystem into a privileged container, so they are root-equivalent already. If you'd rather not, drop the sudoers file and run the deploy steps by hand from a root shell.
**Alternative worth considering:** move the deployment out of `/root` into a `drinkadmin`-owned directory and skip sudo entirely. Keep the directory basename `drinktracker` so the compose project name — and therefore the existing container and volume names — stay the same. `.env.production` must be copied across intact or Postgres will reject the app's credentials, since the volume was initialised with the current password.
## Verifying by hand
```bash
ssh -i ~/.ssh/drinktracker_ed25519 drinkadmin@192.168.2.169
sudo docker compose -f /root/drinktracker/docker-compose.prod.yml ps
sudo docker logs drinktracker-app-1 --tail 50
sudo docker logs drinktracker-app-1 2>&1 | grep '\[switchboard\]' # AI routing + cost per call
```
Each AI call logs one `[switchboard] feature=… model=… cost=… latency_ms=…` line. `FAILOVER` warnings are expected — the gateway's `:batch` model variants currently fail on every request and fall back.
## Rollback
```bash
git revert <bad-sha> && ./deploy/deploy.sh
```
Or directly on the LXC, without touching git history:
```bash
sudo git -C /root/drinktracker reset --hard <previous-sha>
cd /root/drinktracker
sudo docker build -t 192.168.2.140:3000/jpscott84/drinktracker:latest .
sudo docker compose -f docker-compose.prod.yml up -d --no-build
```
Note the `migrate` service runs `prisma db push --accept-data-loss` on every start, so a rollback that removes a column will drop it and its data. Take a database dump first if the schema changed.