deploy: automatically test and release pushed main commits

This commit is contained in:
syntaxbullet
2026-09-07 12:57:42 +02:00
parent 5f36358709
commit b20af6d95c
6 changed files with 298 additions and 0 deletions

71
deploy/README.md Normal file
View File

@@ -0,0 +1,71 @@
# Automatic production deployment
The server checks `git@git.ayau.me:syntaxbullet/minabot.git`, branch `main`, every
minute after the last check finishes. A local commit is deployed after it is
pushed to Gitea. Other branches are not deployed. If several commits arrive
between checks, the latest fetched main commit is built.
```sh
git push origin main
```
The server uses a repository-only read key under `/opt/minabot/git-auth`, registered
as `minabot-production-read-only` in Gitea. The personal Mac SSH key is not copied
to the server. Host verification uses the existing trusted Gitea host keys.
The pipeline fetches into a bare repository and archives an exact commit into
`/opt/minabot/releases/<sha>`. It builds a test image, runs typecheck and the full
test suite, builds the runtime image, runs a disposable production smoke test,
and validates Compose/Caddy. Secrets and production SQLite stay on the server;
they are never synced from Git or development during a release.
After checks pass, Caddy and the old app stop briefly, a verified database snapshot
is taken, and the new app starts before Caddy reopens public access. Discord workers
never overlap. A lock prevents concurrent manual/timer deployments. Successful
commits are recorded in `/opt/minabot/deployed-sha` and skipped on later checks.
A build/test failure leaves the old service running. Failed revisions are recorded
in `failed-sha` and skipped until a new commit or an explicit retry. On startup
failure, the old configuration/image is restored only if the schema and migration
fingerprint is unchanged. Otherwise services remain stopped for operator recovery;
the script never overwrites the production database with an old snapshot.
Changes to `.env.production` or Caddy's pinned image remain explicit server operations.
## Server operations
```sh
# Check status and recent deployment output
systemctl status minabot-deploy.timer minabot-deploy.service
journalctl -u minabot-deploy.service -n 100 --no-pager
cat /opt/minabot/deployed-sha
# Run a check now / retry a previously failed revision
sudo systemctl start minabot-deploy.service
/usr/local/bin/minabot-deploy --retry
# Pause / resume automatic deployment
sudo systemctl stop minabot-deploy.timer
sudo systemctl start minabot-deploy.timer
```
Build/test logs are inside each release directory. Retained release images and
Docker build cache need occasional housekeeping; the script refuses a build when
less than 2 GiB is free. Keep the current and previous known-good image and backups.
Failures are logged locally; external alerts are not configured.
To install or update the controller from a reviewed checkout on the server:
```sh
sudo install -m 755 deploy/deploy.sh /usr/local/bin/minabot-deploy
sudo install -m 644 deploy/minabot-deploy.service deploy/minabot-deploy.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now minabot-deploy.timer
```
Controller/service changes require this explicit installation; normal app,
Dockerfile, Compose, and Caddyfile changes deploy automatically. Do not force-reset
`main` to roll back: prefer a revert commit so the pipeline tests the result.
`bun test deploy/deploy.test.ts` verifies success, idempotency, failure before
cutover, safe rollback, and refusal to replace data after a schema change using
isolated fake Git/Docker commands. It never contacts production.