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:
71
deploy/README.md
Normal file
71
deploy/README.md
Normal 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.
|
||||
Reference in New Issue
Block a user