# Automatic production deployment The server checks `https://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 repository is public, so the server fetches over HTTPS without credentials. Gitea's SSH transport disconnected during transfer, so this checkout also pushes over HTTPS. Its repository-local credential helper reads the existing `ayau` Tea login only for this exact HTTPS host/repository; it stores no additional token copy. The personal Mac SSH key and Tea token are not copied to the server. If the repository becomes private, provision repository-scoped read credentials before the change. The pipeline fetches into a bare repository and archives an exact commit into `/opt/minabot/releases/`. 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 systemctl stop minabot-deploy.timer # Let any running deployment finish before replacing its controller. sudo systemctl start minabot-deploy.service 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. For first-time installation, omit the first two commands if the units do not exist. `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.