# CannaBuddy build — STATUS / post-compact pickup (2026-05-30 eve) Read together with **`linux.md`** (server conventions) + **`passwords.md`** (all creds) — all in this PRIVATE repo (`git.dahlner.de/hydrobuddy/secrets`). **Goal:** stand up the new VPS `cannabuddy.eu` as a hand-rolled NATIVE stack + Forgejo, **fresh start** (NO cloud data/tenant/user/device/DB migration — members re-register, board re-flashes; **only Forgejo data migrates**) → validate → **v1.0.0** → move the `buddys` tenant + new hardware. ## Box - New: `181.214.99.11` / `2a0f:5707:aaf1:210c::1`, Debian 12, 4 vCPU (Ryzen 9 7950X), 8 GB. - SSH **key-only**: claude `~/.ssh/id_ed25519_cannabuddy`, KJ `~/.ssh/id_ed25519`. Password login OFF. Provider console = fallback. - Old (Netcup): `weed.dahlner.de`, key `~/.ssh/id_ed25519_hydrobuddy` — SHARED box (obico/paperless/livekit/…); only Forgejo migrates; decommission LAST. - Local repos: `C:\Users\veste\Desktop\HydroBuddy\repos\{cloud,docs,ops}` (`ops` = the `secrets` Forgejo repo). ## DONE (verified) - **Hardening:** SSH key-only · `ufw` · `fail2ban` (jail.local **systemd backend** — Debian 12 has no auth.log) · unattended-upgrades. - **CF Origin cert** `/etc/ssl/cannabuddy/{origin.crt,origin.key}` (dir `750 root:caddy`), valid **2041**. CF SSL mode = Full(strict), confirmed working. - **CF-only origin firewall:** 80/443 (tcp+udp) allowed **only from Cloudflare ranges**; **SSH 22 open to all**. Script `/usr/local/sbin/cf-firewall.sh` (= repo `scripts/cf-firewall.sh`) + weekly `cf-firewall.timer` (Sun 04:30). Verified: via-CF→200, direct-origin→blocked, SSH→ok. - **Caddy 2.11** — `/etc/caddy/Caddyfile` (= repo `deploy/Caddyfile`). HTTP/3 + security headers. Routes: `cannabuddy.eu`→landing `/var/www/cannabuddy-landing` · `app.`→`:7502` (+`/mqtt`→`:8083`) · `admin.`→`:7502` · `git.`→`:3000` · `status.`→`:3001`. **LIVE: https://cannabuddy.eu serves the landing.** `app.` = 502 until the app deploys. - **TimescaleDB 16** (2.27.1): DB `cannabuddy`, role `hbcloud`, ext on, **localhost-only**. `DATABASE_URL` in `/etc/cannabuddy/cloud.env` (`640 root:hbapp`) + passwords.md. - **Mosquitto 2.0.11** installed, default instance **STOPPED** (pending go-auth config). - **Node 22 + pm2 + pnpm** (corepack). - **`hbapp`** non-root service user (uid 1000, home `/opt/cannabuddy`, NO sudo) — the app/relay run here. - **DNS** (Cloudflare, proxied): `cannabuddy.eu`, `app.`, (KJ also added `admin.`, `git.`, `status.`, `www`). NS = Cloudflare. - **Decisions locked:** data = **pooled + `tenant_id`** (RLS later); broker = **Mosquitto** + per-device **tokens** validated via `mosquitto-go-auth` Postgres backend; **per-tenant device quota**; auth/sessions/passwords already in Postgres (Better-Auth tables `users/sessions/accounts/verifications` + `user_dashboard_prefs`); **relay/bridge = LAST**. - FW now publishes **~3 s cadence** (clean, oversampled) → on new box store **full-res raw** + Timescale compression (no old noise-decimation throttle needed). ## ✅ Container runtime: Podman (rootless) — KJ CONFIRMED For **Forgejo + CI runner + FW build jobs only**; cloud stack stays **native**. Cloud-deploy job = **host runner** (no container); FW build job = Podman container. Old Forgejo to copy = Docker `forgejo:11` + `forgejo/runner:6`, **SQLite** at `/home/dahlner-git/services/forgejo/data`, compose `/home/dahlner-git/services/forgejo/docker-compose.yml`. ## Build/runtime conventions - **pnpm** (corepack) for install/build (`pnpm install` + `pnpm -r build`); **pm2** runs the built Node entry in **cluster** mode. NOT npm. - **Node 22 LTS** (best-practice = LTS, not bleeding edge). Stack already on latest majors: Astro 5 · Tailwind 4 · DaisyUI 5 · drizzle · Better-Auth 1 · Caddy 2.11 · TimescaleDB 2.27 · Mosquitto 2.0. During deploy run `pnpm update` to latest-compatible + flag any worthwhile major bumps (no blind breaking upgrades). ## Nice-to-have add-ons (TODO — KJ to greenlight) - **Uptime Kuma** → `status.cannabuddy.eu` (already planned). - **GlitchTip** (self-host, Sentry-API compatible) — app error tracking; high value for prod. - **Beszel** — lightweight server+DB metrics dashboard (the "custom dashboard" instead of Netdata). - **Umami** — privacy-friendly analytics (landing + app). - **Healthchecks** (self-host) — alerts if the nightly backup/cron didn't run. - **restic** → R2 for backups (dedup + encryption + retention; nicer than raw pg_dump+rclone). - Later: **Infisical** (self-host secrets manager) to move secrets out of git. ## NEXT (once Podman confirmed) 1. Install Podman (rootless). 2. **Forgejo migrate:** copy `…/forgejo/data` + compose from Netcup → new box; repoint `DOMAIN`/`ROOT_URL` → `git.cannabuddy.eu` + SSH; run; verify clone/push/CI; register runner (host + container labels). 3. Wire the **cloud deploy Action** → builds + `pm2 reload` as `hbapp` (cluster) → **`app.cannabuddy.eu` green** (first deploy = current code on the fresh DB). 4. **New-arch code** (commits → CI ships): `measurements.tenant_id` + Timescale **compression** (≥7 d); `device_credentials` table; `/api/provision` issues per-device opaque tokens + per-tenant quota; **Resend → SendKit** (+ SPF/DKIM/DMARC DNS). 5. **Mosquitto go-auth** (Postgres backend) + WS listener `127.0.0.1:8083` → start mosquitto. 6. **FW relay/bridge** (hbapp/pm2): sub `tele/#` → insert to TimescaleDB stamping `tenant_id`; pub `cmd//ota` + `cmd//factory-reset`. 7. **Uptime Kuma** → `status.cannabuddy.eu`. **R2 backups:** nightly `pg_dump` → R2 **EU endpoint** + tested restore (creds in passwords.md). 8. **Validate E2E** with a test board → tag **v1.0.0** → re-flash the real board + give the 2 members the new link. ## R2 (backups + images) — creds in passwords.md Bucket `cannabuddy`, **EU endpoint** `https://e840f5bb6d09cb2c0294f434f35642a9.eu.r2.cloudflarestorage.com`, custom domain `r2.cannabuddy.eu`.