secrets/linux.md

5.3 KiB

CannaBuddy server runbook (cannabuddy.eu / 181.214.99.11)

Operational guide for the new production VPS. Secrets live in passwords.md (this private repo) — this file is the how, not the what. Decision: start the new cloud from scratch — NO migration of DB/users/devices/measurements. Members re-register, the board re-flashes. Only Forgejo + its data migrates from the old Netcup box.


Access (SSH is key-only — password login disabled)

  • Claude (automation): ssh -i ~/.ssh/id_ed25519_cannabuddy root@181.214.99.11
  • KJ desktop: ssh root@181.214.99.11 (default key ~/.ssh/id_ed25519)
  • KJ Termius: import ~/.ssh/id_ed25519, host 181.214.99.11 user root
  • Fallback if locked out: the VPS provider's web console (password still set, but SSH password auth is OFF).
  • Pipe local scripts to the box without quoting pain: ssh … 'bash -s' < script.sh

The box

  • Debian 12 (bookworm), 4 vCPU (Ryzen 9 7950X), 8 GB RAM, ~99 GB disk.
  • IPv4 181.214.99.11, IPv6 2a0f:5707:aaf1:210c::1. Domain in Cloudflare (proxied = WAF).

Users / who runs what (proper-Linux: nothing app-level as root)

Service runs as unit
Caddy (reverse proxy/TLS) caddy systemctl … caddy
PostgreSQL/TimescaleDB postgres systemctl … postgresql
Mosquitto (MQTT) mosquitto systemctl … mosquitto
The app (Astro SSR + bridge) hbapp (uid 1000, home /opt/cannabuddy, login shell for setup) pm2 (to become a systemd unit via pm2 startup systemd -u hbapp)
  • Run app commands as the service user: sudo -u hbapp -H bash -lc '…'.
  • App code will live in /opt/cannabuddy/cloud (the cloud repo checkout).

Installed + where

  • Caddy 2.11 (apt, cloudsmith repo) — config /etc/caddy/Caddyfile (mirror in deploy/Caddyfile here). HTTP/1+2+3, security headers, static CF Origin cert.
  • Node 22 + pm2 + pnpm (NodeSource apt; corepack enable gives pnpm).
  • TimescaleDB 16 (2.27.1) on PostgreSQL 16 (PGDG apt). DB cannabuddy, role hbcloud. Tuned by timescaledb-tune. Listens localhost only.
  • Mosquitto 2.0.11 (apt) — stopped until mosquitto-go-auth (Postgres backend) is configured (localhost WS listener :8083, plain :1883 for the local bridge).
  • Hardening: ufw (allow 22, 80, 443 tcp + 443 udp; default-deny), fail2ban (sshd), unattended-upgrades.

TLS

  • Cloudflare Origin Certificate at /etc/ssl/cannabuddy/origin.{crt,key} — dir 750 root:caddy, key 640, cert 644. Valid to 2041, no renewal.
  • Cloudflare SSL/TLS mode must be Full (strict).
  • Gotcha learned: the cert dir must be group-caddy + traversable (750), not just the files.

Domains / Caddy routes (all behind Cloudflare proxy)

Host →
cannabuddy.eu, www static landing /var/www/cannabuddy-landing
app.cannabuddy.eu Astro SSR 127.0.0.1:7502 (+ /mqtt → Mosquitto WS :8083)
admin.cannabuddy.eu same app :7502 (lock behind Cloudflare Access later)
git.cannabuddy.eu Forgejo :3000
status.cannabuddy.eu Uptime Kuma :3001
  • Tenants are resolved by login (pooled DB + tenant_id), NOT by subdomain.

Secrets / config

  • /etc/cannabuddy/cloud.env (root:hbapp 640) — holds DATABASE_URL (+ to add: BETTER_AUTH_SECRET, SendKit key, R2 creds, MQTT auth secret). pm2 ecosystem loads it.
  • All canonical secret values: passwords.md (this repo).

Current status (2026-05-30)

Live: hardening · Caddy (HTTPS, landing at https://cannabuddy.eu) · TimescaleDB DB+role · Mosquitto installed · hbapp user. app. 502s until the app deploys.

NEXT steps (resume here)

  1. Deploy app as hbapp: clone cloud repo → /opt/cannabuddy/cloud, pnpm install, write env (DATABASE_URL + openssl rand -hex 32 for BETTER_AUTH_SECRET + SendKit), pnpm build, run drizzle migrations, pm2 start (cluster) + pm2 startup systemd -u hbapp + pm2 save. → app.cannabuddy.eu green.
  2. New-architecture code (in the repo): tenant_id on measurements + Timescale compression policy (≥7d); device_credentials table; /api/provision issues per-device opaque tokens + per-tenant device quota; swap Resend → SendKit (add SPF/DKIM/DMARC DNS).
  3. Mosquitto auth: install/build mosquitto-go-auth, Postgres backend against device_credentials, ACL tele/<mac>/# pub + cmd/<mac>/# sub; enable WS listener 127.0.0.1:8083; start mosquitto.
  4. FW relay/bridge (as hbapp, pm2): subscribe tele/# → insert into TimescaleDB stamping tenant_id; publish cmd/<mac>/ota + cmd/<mac>/factory-reset.
  5. Forgejo: install on box → copy data dir + DB from Netcup → git.cannabuddy.eu; verify; then decommission old.
  6. Uptime Kuma → status.cannabuddy.eu. Backups: nightly pg_dump → R2 (EU endpoint) + tested restore.
  7. Validate E2E with a test board → tag v1.0.0 → re-flash the real board + give the 2 members the new link.

Handy commands

# service status
systemctl status caddy postgresql mosquitto --no-pager
# reload caddy after editing /etc/caddy/Caddyfile
caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile && systemctl reload caddy
# psql
sudo -u postgres psql -d cannabuddy
# run as the app user
sudo -u hbapp -H bash -lc 'cd /opt/cannabuddy/cloud && pm2 ls'
# firewall
ufw status verbose