.claude/ was ignored wholesale, which meant the deploy skill lived only on one machine. Narrow the rule so .claude/skills/ is tracked while settings.local.json stays local. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
70 lines
3.3 KiB
Markdown
70 lines
3.3 KiB
Markdown
---
|
|
name: deploy-drinktracker
|
|
description: Deploy drinktracker to the production LXC and verify it came back healthy. Use when asked to deploy, ship, release, push to prod, or update the running app - and after merging changes that need to go live. Also covers rollback and diagnosing a deploy that did not take effect.
|
|
---
|
|
|
|
# Deploying drinktracker
|
|
|
|
Run `./deploy/deploy.sh` from the repo root. It typechecks, pushes to Gitea, rebuilds the
|
|
image on the LXC, restarts the stack and polls until the app answers. Use `--yes` to skip
|
|
the confirmation prompt when the user has already approved the deploy.
|
|
|
|
`deploy/README.md` has the full topology table, the one-time sudoers setup, and rollback
|
|
commands. Read it before improvising — the layout is unusual in ways that waste time.
|
|
|
|
## Facts that are not discoverable from the repo
|
|
|
|
- **The LXC is `192.168.2.169`.** `drinktracker.tenseconddelay.net` (and `NEXTAUTH_URL`)
|
|
resolve to the reverse proxy at `192.168.2.172`. SSHing there fails with what looks like
|
|
a rejected key but is simply the wrong machine.
|
|
- **The live stack is `/root/drinktracker`.** The checkouts under `/home/drinkadmin/`
|
|
(`drinktracker`, `drinktracker-latest`) are stale June copies. Confirm with
|
|
`docker inspect drinktracker-app-1 --format '{{index .Config.Labels "com.docker.compose.project.working_dir"}}'`.
|
|
- **There is no CI.** No Gitea Actions, no GitHub workflows. `git push` updates the repo
|
|
and nothing else; the image must be built and the stack restarted separately. If someone
|
|
reports "I pushed but nothing changed", this is why.
|
|
- **Access:** `ssh -i ~/.ssh/drinktracker_ed25519 drinkadmin@192.168.2.169`. Root SSH is
|
|
disabled. `drinkadmin` needs the sudoers drop-in from `deploy/README.md` for the script
|
|
to work unattended.
|
|
|
|
## Verifying
|
|
|
|
An unauthenticated request to port 3000 returns **307** (redirect to `/login`) — that is a
|
|
healthy response, not a failure. The script accepts 200, 302 and 307.
|
|
|
|
Each AI call emits one line:
|
|
|
|
```
|
|
sudo docker logs drinktracker-app-1 2>&1 | grep '\[switchboard\]'
|
|
```
|
|
|
|
`FAILOVER` lines are expected and harmless: the gateway's `:batch` model variants fail on
|
|
every request and fall back. `CONTEXT OVERFLOW` is worth investigating — it means a request
|
|
was too large for any model's window and may have been truncated.
|
|
|
|
## Before deploying schema changes
|
|
|
|
The `migrate` service runs `prisma db push --skip-generate --accept-data-loss` on every
|
|
start. Removing a field from `schema.prisma` therefore **drops the column and its data** on
|
|
the next deploy, in both directions — so a rollback past a schema change is destructive too.
|
|
Dump the database first when the schema changed:
|
|
|
|
```
|
|
sudo docker exec drinktracker-db-1 pg_dump -U drinktracker drinktracker > ~/dt-$(date +%F).sql
|
|
```
|
|
|
|
This is also why `UserPreference.defaultProvider` is still in the schema despite being
|
|
unused: deleting it would drop the column and break restoring older backups.
|
|
|
|
## If the app does not come back
|
|
|
|
```
|
|
sudo docker compose -f /root/drinktracker/docker-compose.prod.yml ps
|
|
sudo docker logs drinktracker-app-1 --tail 100
|
|
```
|
|
|
|
Most likely causes, in order: the build failed and compose restarted the previous image;
|
|
`.env.production` is missing a variable the new code reads (compare against `.env.example`);
|
|
or the `migrate` service exited non-zero and `app` never started, which `ps` will show as
|
|
`migrate` in a non-zero exited state.
|