Files
minabot/docs/DEPLOYMENT-PLAN.md
2026-09-07 11:50:22 +02:00

110 lines
6.3 KiB
Markdown

# Minabot deployment plan
Target: `https://mina.teppelinlabs.com` on `syntaxbullet@46.62.156.93`.
Prepared September 7, 2026. Executed the same day; see
[production operations](PRODUCTION.md) for the deployed configuration and results.
## Verified baseline
- Ubuntu 26.04.1, x86_64, 3.7 GiB RAM, approximately 34 GiB free disk.
- DNS A resolves to this server; no AAAA answer was returned.
- Docker and Caddy are not installed. Only SSH is publicly listening.
- SSH keys work, password authentication is disabled, UFW and Fail2ban are active.
- Passwordless sudo is installed in `/etc/sudoers.d/90-syntaxbullet` and verified
with `sudo -k` followed by `sudo -n id` in a fresh SSH connection.
- The checkout has uncommitted live Discord image changes and migrations 0009/0010.
Include and validate the intended complete release before tagging it.
## Architecture
Internet → Caddy container on TCP 80/443 → app container on port 3000 → SQLite.
Use Docker Compose with one app process and one Caddy container on a shared bridge
network. The app needs outbound access to Discord. Publish only Caddy's ports;
do not publish app port 3000 or mount the Docker socket into either container.
Keep reminders, backups, and live Discord updates in the existing app process.
Multiple app replicas are unsuitable for the current live-image worker.
Use `/opt/minabot` for deployment configuration and tagged releases,
`/srv/minabot/data` mounted as `/data` for SQLite and snapshots, and persistent
Docker volumes for Caddy `/data` and `/config`. Match data ownership to the app's
non-root container UID. Use `restart: unless-stopped`, bounded container logs,
a health check, and a sufficient startup grace period for migrations/backups.
An unhealthy health check alone does not restart a container; monitor it separately.
## Implementation sequence
1. Add `HOST` configuration to `src/index.ts`, retaining `127.0.0.1` locally and
using `HOST=0.0.0.0` inside Docker. The current hard-coded loopback binding
prevents Caddy from reaching the app across containers.
2. Add a multi-stage Dockerfile, `.dockerignore`, Compose file, Caddyfile, and
deployment environment example. Pin tested Bun/Caddy versions and image digests.
Build Linux amd64 dependencies in the image using the frozen lockfile; never
copy macOS `node_modules`. Exclude real dotenv files, databases, backups, and Git.
3. Preserve the existing startup contract: migrations must succeed before
`scripts/start.ts` loads `dist/index.js`. Include migrations and required script
imports, production dependencies, built assets, and the bundled Instrument Serif
font. Use a glibc-based Bun runtime with a sans-serif font installed, and verify
the native `@napi-rs/canvas` renderer inside that exact image.
4. Install Docker Engine and Compose from Docker's official Ubuntu repository.
Ubuntu 26.04 is supported. Keep Docker's firewall management enabled; published
ports can bypass UFW. Publish only 80/443 and verify exposure externally. Align
the existing Fail2ban nftables action with the chosen Docker firewall backend
(prefer an iptables-compatible SSH action for the standard Docker backend),
then recheck SSH protection. Open TCP 80/443 in UFW and any Hetzner firewall.
5. Put runtime secrets in `/opt/minabot/.env.production` with mode 0600 and inject
through Compose `env_file`. Set `NODE_ENV=production`, `HOST=0.0.0.0`, `PORT=3000`,
`APP_ORIGIN=https://mina.teppelinlabs.com`, and `DATABASE_PATH=/data/minabot.sqlite`.
Supply `DISCORD_CLIENT_ID`, `DISCORD_CLIENT_SECRET`, a stable random
`AUTH_COOKIE_SECRET`, and the bot/channel settings. Do not bake secrets into
images or build arguments. Register this Discord OAuth redirect exactly:
`https://mina.teppelinlabs.com/api/auth/discord/callback`.
6. Default to a fresh production database. If existing local data should be moved,
use a verified SQLite snapshot, not a live database-file copy. Avoid running
two workers against copied live Discord message state. Resolve the data choice
before first production startup.
7. Build and tag the release, start it on the private Docker network, verify its
health, then start Caddy. Caddy obtains and renews TLS certificates and redirects
HTTP to HTTPS. Persist its certificate storage across container replacement.
Proposed Caddyfile:
```caddyfile
mina.teppelinlabs.com {
encode zstd gzip
reverse_proxy app:3000
}
```
## Verification and operations
- Before release: typecheck, tests, build, and disposable production API smoke test.
Validate Compose and Caddy configuration; run migrations, health, static assets,
native PNG rendering, and a restart/persistence test inside the Linux image.
- After launch: verify public HTTPS, HTTP redirect, `/api/health`, Discord sign-in,
secure cookies, habit persistence, and port 3000 being unreachable externally.
Exercise bot workers with stubbed transports; a live Discord post or DM needs
an explicitly selected test destination and authorization to send it.
- Keep daily verified snapshots and seven retained copies using the existing app
backup system. Configure an off-server destination separately; none is provisioned
yet. Test restore to a new database with the matching image before relying on it.
- Configure an independent HTTPS monitor and alert destination. Existing monitor
scripts provide checks but do not provision an external monitoring service.
- Updates: build/test the next immutable image, snapshot the database, stop the
old app, and start the new release. Accept brief downtime; do not overlap workers.
Keep the previous image. If schema compatibility prevents image-only rollback,
stop the app and restore its matching pre-migration snapshot to a new file.
The restore tool revokes sessions and disables reminders; audit restored live
Discord state before starting workers. Never use `docker compose down -v`.
Remaining launch inputs: fresh database versus migration, Discord redirect setup
and production credentials, off-server backup destination, and monitoring alerts.
## References
- [Docker Ubuntu installation](https://docs.docker.com/engine/install/ubuntu/)
- [Docker firewall behavior](https://docs.docker.com/engine/network/packet-filtering-firewalls/)
- [Bun Docker guide](https://bun.sh/guides/ecosystem/docker)
- [Caddy automatic HTTPS](https://caddyserver.com/docs/automatic-https)
- [Existing backup and recovery workflow](OPERATIONS.md)