5.0 KiB
Minabot production
Live: https://mina.teppelinlabs.com
SSH: ssh syntaxbullet@46.62.156.93 (key authentication; passwordless sudo).
Deployment root: /opt/minabot. Compose manages app, caddy, and monitor.
Docker starts at boot; containers use restart: unless-stopped.
Release and configuration
Current image: minabot:20260907-fonts-1, built on the Linux amd64 server from
commit 1721ee4, including development write protection and bundled share fonts.
Its immutable image ID is recorded by docker image inspect; source is retained
under /opt/minabot/releases/20260907-fonts-1, with source.tar.gz and build
logs in /opt/minabot. Bun 1.3.14 and Caddy images are pinned by digest.
/opt/minabot/.env selects release images. /opt/minabot/.env.production contains
runtime secrets (0600). Discord client and bot credentials were copied from the
effective development environment; production has a separate generated cookie
signing secret. Runtime uses APP_ORIGIN=https://mina.teppelinlabs.com,
TRUST_PROXY=true, HOST=0.0.0.0, and DATABASE_PATH=/data/minabot.sqlite.
TRUST_PROXY only accepts HTTPS from X-Forwarded-Proto, retains the actual
request Host, and must only be enabled behind the private Caddy proxy. Port 3000
is not published. Caddy exposes TCP 80/443 and persists certificate state in
minabot_caddy_data and minabot_caddy_config. SSH remains on 22. Fail2ban uses
the iptables-compatible SSH action alongside Docker/UFW.
The registered Discord callback is
https://mina.teppelinlabs.com/api/auth/discord/callback; localhost remains registered.
Data and backups
/srv/minabot/data is bind-mounted at /data, owned by UID 1000. A verified
VACUUM INTO snapshot copied the development database, including its one account,
four habits, history, reminders, and live Discord state. The transfer hash and
counts are recorded in /opt/minabot/migration-manifest.json. Local development
was stopped before the snapshot so only production manages the existing live cards.
Development now blocks channel writes at both snapshot-send and live-card entry
points. It also skips copied live-card recovery and change scheduling. Preview and
download remain available. Stubbed tests inject their own transport/configuration;
real writes through the environment loader are enabled only in production.
Daily verified snapshots and pre-migration snapshots are stored under
/srv/minabot/data/backups, retaining seven snapshots. The restore rehearsal
passed using a separate file; production data was not replaced by the rehearsal.
Restore revokes sessions and disables reminder opt-ins; see OPERATIONS.md.
Off-server backup replication and delivered uptime alerts are not configured: the operator has no destinations set up yet. The monitor checks public HTTPS every minute in a separate container on this server and writes failure/recovery events to its logs. It cannot report a complete server outage to an external destination.
Common commands
Run after SSH login:
cd /opt/minabot
sudo docker compose ps
sudo docker compose logs --tail=100 app caddy monitor
sudo docker compose exec -T app bun scripts/backup.ts
sudo docker compose exec -T monitor bun scripts/monitor.ts --once
When sending several shell commands over SSH stdin, redirect noninteractive
docker compose exec calls from /dev/null so they do not consume later commands.
For updates, build a new release tag, run the container smoke test, take a backup,
update APP_IMAGE in .env, stop the old app, then run
sudo docker compose up -d --wait --wait-timeout 120. Never run overlapping app
workers or docker compose down -v. Keep the last known-good image and a matching
pre-migration snapshot. The initial -1 image predates the proxy fix and is not
a suitable rollback target for working production sign-in.
Verified September 7, 2026
- Typecheck, build, 197 tests, and production HTTP smoke tests passed.
- The Linux runtime passed migrations, native 1920×1080 PNG rendering, and persistence across a process restart using a disposable database.
- Public HTTPS health returns
{"status":"ok"}; HTTP redirects to HTTPS. - Browser Discord sign-in completed and displayed the migrated account and habits.
- Read-only authenticated production API/PNG checks passed; unauthenticated access and cross-origin writes were rejected. Temporary verification sessions were removed.
- Snapshot integrity, restore rehearsal, and transferred database hash passed.
- Application port 3000 is unpublished and an external HTTP probe timed out.
- Docker and Fail2ban are active; runtime secrets and the SQLite file are mode 0600.
Live Discord test posts/DMs were not sent. Existing configured workers continue their normal production behavior.
Share fonts
Instrument Serif and Noto Sans (400/500/600) are bundled for both browser and native canvas share renderers. Mac/Linux fixture text metrics match exactly; pixel-channel differences are at most 2/255 from native rasterization. Missing native font files fail startup. Renderer version 3 refreshes existing live cards with the new fonts.