110 lines
6.3 KiB
Markdown
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)
|