# 🔁 Roo Handover — CannaManage Docker Deploy on TrueNAS

**Date:** 2026-06-13  
**Written by:** Lumen (Homelab mode, claude-sonnet-4-6)  
**For:** Next Roo session (new model)

---

## ✅ "OOPS" CRASH FIXED — 2026-06-13 (update 3) — verified in a real browser

The previous "RESOLVED" was wrong: login was verified with **curl only**, which cannot
execute client-side JS. In a real browser **every page (including `/login`) crashed on
hydration** with the "Oops! Something went wrong" boundary. Diagnosed by installing a
**Playwright browser probe** (`scripts/debug/dashboard-probe.mjs`) that logs in and
captures real console/network errors + a screenshot. Three root causes, all fixed in
commit **`4be9c4c`** (frontend rebuilt + redeployed):

8. **App-wide intl hydration crash ("Oops" on every page).** Root `app/layout.tsx`
   renders global client components (`PwaInstallPrompt` → `useTranslations`, `Toaster`,
   `Sonner`) as siblings of `{children}` inside `<Providers>`, but only each route-group
   layout wrapped *its own* children in `NextIntlClientProvider`. Those global components
   mounted with **no intl context** → "No intl context found" → hydration crash.
   **Fix:** root layout is now `async` and wraps the body in `NextIntlClientProvider`
   via `getMessages()`. Nested route-group providers remain valid (next-intl nests).

9. **PWA assets intercepted by auth middleware** (manifest.json syntax error + stale cache).
   The middleware `matcher` did not exclude `/manifest.json` or `/sw.js`, so unauthenticated
   browsers got **307 → /login (HTML)** for both → browser parsed HTML as JSON
   (`manifest.json:1 Syntax error`) and an HTML/old service worker kept serving **stale
   bundles** ("website hasn't changed after redeploy"). **Fix:** matcher now excludes
   `manifest.json`, `sw.js`, `icons`, `offline`. Verified: `manifest.json` → 200
   `application/json`, `sw.js` → 200 `application/javascript`.

10. **Service-worker stale cache.** Bumped `CACHE_NAME` `v1`→`v2` in `public/sw.js` so the
    `activate` handler purges old cached bundles from clients that loaded the broken build.

**Browser verification (Playwright):** login `admin@test.de`/`test123` → lands on
`/dashboard`, **no `pageerror`**, dashboard renders. The "Oops" is gone.

### ⚠️ Known follow-up (non-fatal — dashboard renders via mock fallback)
Dashboard API endpoints are **mis-wired**: frontend calls `/api/backend/dashboard/stats`,
`/distributions/recent`, `/consent/check` → proxy rewrites to `/api/v1/dashboard/stats`
etc., but the backend has **no such routes** (real stats route is `/api/v1/clubs/me/stats`
in `ClubController`; `/dashboard` only exists in `PortalController`) → HTTP 500/404.
The dashboard falls back to `mockClubStats`/`mockRecentDistributions` so it still renders.
Frontend `ClubStats` type also differs from backend `ClubStatsResponse`. Fix = align
service paths + DTO shape, or add `/api/v1/dashboard/*` endpoints to the backend.

### 🔧 New tooling installed on the Fedora 44 workstation
- **Node.js 22.22.2 + npm 10.9.7** via `sudo dnf install -y nodejs npm` (was MISSING).
- **`@playwright/mcp` v0.0.76** (run via `npx @playwright/mcp@latest`) + Chromium
  headless-shell (`npx playwright install chromium`). Wired into Roo `mcp_settings.json`
  (server name `playwright`). Chromium launches fine on Fedora 44 despite the
  "OS not officially supported" warning.
- **Probe:** `scripts/debug/dashboard-probe.mjs` — reusable client-side debugger.

---

## ✅ LOGIN WORKING — 2026-06-13 (update 2) — full auth flow verified

After deploy, login showed a client-side "Oops! Something went wrong" error boundary.
Four more root causes, all fixed and verified (`signin → 302 → /dashboard`, session
populated with `role: ADMIN` + `clubId`):

4. **NextAuth `MissingSecret` → "Oops" error boundary** — override env (TrueNAS, not git)
   NextAuth v5 (Auth.js) reads **`AUTH_SECRET`**, not `NEXTAUTH_SECRET`. The runtime env had
   only `NEXTAUTH_SECRET`, so `signIn` threw `MissingSecret` → the React error boundary fired.
   Added `AUTH_SECRET` (+ `AUTH_TRUST_HOST=true`) to the frontend service in the TrueNAS override.

5. **No seed data** — DB had 0 users. Seeded club + admin (`admin@test.de`). The seed comment's
   BCrypt hash was for "password", not "test123" — regenerated a correct hash for `test123`.

6. **Backend HTTP 500 after successful auth: `Illegal base64 character: '-'`** — commit `dac884c`
   `JwtService.getSigningKey()` does `Decoders.BASE64.decode(secret)`. The compose secret
   `docker-dev-secret-key-...-for-hmac` is plaintext with hyphens (not valid base64), so signing
   the JWT threw once credentials passed. Replaced with a real base64 secret (`openssl rand -base64 48`).

7. **NextAuth `CredentialsSignin` — API/frontend contract mismatch** — commit `281adda`
   `authorize()` read `data.member.id/email/clubName/clubId`, but the backend `LoginResponse` is
   **flat**: `{ accessToken, refreshToken, expiresIn, role }` — no `member` object. Accessing
   `data.member.id` on `undefined` threw → `authorize` returned null. Fixed by decoding the JWT
   payload for identity claims (`sub`=userId, `email`, `tenant_id`=clubId) + the flat `role`.

**Login credentials:** `admin@test.de` / `test123` (dev seed).

---

## ✅ RESOLVED — 2026-06-13 — CannaManage live at http://192.168.188.119:3000

All blocking issues fixed. Stack is up on TrueNAS (backend + db healthy, frontend serving).
The frontend `pnpm build` crash was **not** a NextAuth problem — the 6 earlier fixes
targeted the wrong layer. Three real root causes, fixed in order:

1. **Frontend build crash (`ERR_INVALID_URL, input: 'undefined'`)** — commit `f6a7143`
   Root layout `src/app/layout.tsx` evaluated `new URL(process.env.BASE_URL)` at module
   load. `BASE_URL` was never a build-time ENV → `new URL(undefined)` threw. Because it's
   the **root layout**, its `metadata` is collected for *every* route during "Collecting
   page data", explaining why both `/impressum` (marketing) and `/portal-login`
   (non-marketing) failed identically. Fix: `?? "http://localhost:3000"` fallback +
   `BASE_URL` build ENV in Dockerfile. The middleware/force-dynamic fixes were irrelevant
   (metadata is evaluated before middleware ever runs).

2. **Backend crash: `Schema validation: missing table [audit_events]`** — commit `8490da4`
   This is **Spring Boot 4.0.6**, which modularized autoconfiguration.
   `FlywayAutoConfiguration` moved into a dedicated `spring-boot-flyway` module pulled in
   only by `spring-boot-starter-flyway`. The pom had only `flyway-database-postgresql`
   (+ transitive `flyway-core`) but NOT the starter, so `spring.flyway.enabled=true` was
   inert: no migrations ran, no `flyway_schema_history`, Hibernate `ddl-auto=validate`
   failed on the empty schema. Fix: add `spring-boot-starter-flyway`.
   Ref: https://spring.io/blog/2025/10/28/modularizing-spring-boot/

3. **Backend unhealthy (503 on /actuator/health)** — commit `60844ef`
   `spring-boot-starter-mail` registers a mail health indicator that tries `localhost:1025`.
   No SMTP container in this deployment → DOWN → aggregate health DOWN → Docker marks the
   container unhealthy → frontend refused to start. Fix:
   `management.health.mail.enabled=false` in the docker profile.

**Infra note:** Host port 8080 was already taken by `odysseus-searxng-1`. The TrueNAS
override (`/mnt/VM_SSD_Pool/cannamanage/docker-compose.truenas.yml`, not in git) remaps the
backend host port to **8081** using `ports: !reset []` (compose merges list-type keys by
concat otherwise). The internal container port stays 8080, so `BACKEND_URL=http://backend:8080`
is unaffected.

**Verified:** frontend `/login` 200, `/impressum` 200 (was the failing SSG page), `/` 307→login;
backend `/actuator/health` UP; reachable from the workstation over LAN.

---

## What We Were Doing

Deploying the full CannaManage stack (Spring Boot backend + Next.js frontend + PostgreSQL) on TrueNAS.local at `192.168.188.119` so Patrick can test the app in a browser.

---

## Current State: ⚠️ Frontend Build Still Failing

**Backend:** ✅ Built and ready (image exists on TrueNAS)  
**DB:** ✅ Postgres pulled and ready  
**Frontend:** ❌ `pnpm build` failing inside Docker with `ERR_INVALID_URL, input: 'undefined'`

Root cause: NextAuth v5 tries to construct its internal URL at **static page collection time** during `next build`. It reads `AUTH_URL` env var and crashes if it's `undefined`. The marketing pages (`/pricing`, `/impressum`, `/datenschutz`, `/agb`) are the ones triggering it because the middleware runs on them.

---

## All Fixes Already Applied & Pushed to Gitea

These commits are all on `main` at `http://192.168.188.119:30008/pplate/cannamanage`:

| Commit | Fix |
|--------|-----|
| `61707ff` | Added `spring-boot-starter-websocket` to `cannamanage-service/pom.xml` |
| `d0c53a9` | Fixed `DsgvoService.java`: `getMembershipNumber()` + removed `setPhone(null)` |
| `106229e` | Added `NEXTAUTH_URL` as build ARG to frontend Dockerfile |
| `805bc4f` | Added `AUTH_URL` + `AUTH_SECRET` build ARGs (NextAuth v5 uses `AUTH_URL`) |
| `3e4fdee` | Added `export const dynamic = "force-dynamic"` to marketing layout |
| `b57be8a` | Switched Dockerfile to hardcoded build-time placeholder ENVs |
| `d650987` | Guarded `auth.ts` redirect callback against undefined url |
| `9a4df56` | **Latest fix** — excluded marketing routes from NextAuth middleware matcher |

The **latest fix** (`9a4df56`) excludes `/pricing`, `/impressum`, `/datenschutz`, `/agb` from the middleware matcher pattern so NextAuth never runs on those routes during SSG.

---

## How to Resume

### Step 1: Pull latest on TrueNAS and rebuild frontend
```bash
ssh truenas.local "cd /mnt/VM_SSD_Pool/cannamanage && git pull && docker compose -f docker-compose.yml -f docker-compose.truenas.yml build --no-cache frontend 2>&1 | tail -20"
```

### Step 2: If build succeeds, start the stack
```bash
ssh truenas.local "cd /mnt/VM_SSD_Pool/cannamanage && docker compose -f docker-compose.yml -f docker-compose.truenas.yml up -d"
```

### Step 3: Wait for backend to be healthy (Flyway runs migrations on first start)
```bash
ssh truenas.local "docker ps --filter name=cannamanage"
# Wait for cannamanage-backend to show (healthy)
```

### Step 4: Load seed data
```bash
ssh truenas.local "docker exec -i cannamanage-db psql -U cannamanage -d cannamanage < /mnt/VM_SSD_Pool/cannamanage/scripts/seed/init.sql"
```

### Step 5: Open the app
- Frontend: **http://192.168.188.119:3000**
- Login: `admin@test.de` / `test123`
- Backend health: http://192.168.188.119:8080/actuator/health

---

## If Frontend Build Still Fails

The build error is always:
```
[Error: Failed to collect page data for /pricing]
ERR_INVALID_URL, input: 'undefined'
    at chunk 7624.js (NextAuth internal URL construction)
```

This is NextAuth v5 initializing its session URL. The last fix excluded the marketing routes from the middleware — if it still fails, the problem is that NextAuth's `auth.ts` is being imported somewhere in the page tree even without middleware running.

**Nuclear option:** Add `SKIP_ENV_VALIDATION=1` to the frontend builder ENV in the Dockerfile, or better: create a `src/auth-server.ts` that lazy-initializes auth only at runtime (not build time) and update the pages that import it.

**Simpler workaround:** Add `export const dynamic = "force-dynamic"` to each individual marketing page file:
- `src/app/(marketing)/pricing/page.tsx` — line 1 after `"use client"` → add `export const dynamic = "force-dynamic"` 
- `src/app/(marketing)/impressum/page.tsx`
- `src/app/(marketing)/datenschutz/page.tsx`
- `src/app/(marketing)/agb/page.tsx`

Note: `"use client"` pages can't export `dynamic`, so it would need to go in the layout instead — which was already tried and didn't work. The real fix is the middleware matcher fix in commit `9a4df56`.

---

## Files Modified (all in `/home/pplate/IdeaProjects/cannamanage/`)

| File | Change |
|------|--------|
| `cannamanage-service/pom.xml` | +websocket dep |
| `cannamanage-service/src/.../DsgvoService.java` | method name fix |
| `cannamanage-frontend/Dockerfile` | build-time ENV placeholders |
| `cannamanage-frontend/src/lib/auth.ts` | null guard in redirect callback |
| `cannamanage-frontend/src/app/(marketing)/layout.tsx` | force-dynamic export |
| `cannamanage-frontend/src/middleware.ts` | marketing routes excluded from matcher |

---

## TrueNAS Deploy Location

```
/mnt/VM_SSD_Pool/cannamanage/
├── docker-compose.yml           (base)
├── docker-compose.truenas.yml   (override: NEXTAUTH_URL=http://192.168.188.119:3000)
├── Dockerfile.backend
├── cannamanage-frontend/
│   └── Dockerfile
└── scripts/seed/init.sql        (seed data)
```

---

## Seed Data Summary (loaded into DB after first boot)

- **Club:** Grüner Daumen e.V., Berlin, max 500 members
- **Admin:** `admin@test.de` / `test123` (`ROLE_ADMIN`)
- **Members:** 5 (Max, Anna, Jonas[under-21], Lisa, Tom)
- **Strains:** Northern Lights (18.5% THC), Amnesia Haze (22% THC), CBD Critical Mass (5% THC)
- **Batches:** 3 available batches (500g, 300g, 200g)
- **Tour guide:** `docs/TOURGUIDE.md`

---

## Container Management Commands

```bash
# Status
ssh truenas.local "docker ps --filter name=cannamanage"

# Logs
ssh truenas.local "docker logs cannamanage-backend -f --tail=50"
ssh truenas.local "docker logs cannamanage-frontend -f --tail=50"

# Rebuild specific service
ssh truenas.local "cd /mnt/VM_SSD_Pool/cannamanage && git pull && docker compose -f docker-compose.yml -f docker-compose.truenas.yml up -d --build frontend"

# Stop all
ssh truenas.local "cd /mnt/VM_SSD_Pool/cannamanage && docker compose -f docker-compose.yml -f docker-compose.truenas.yml down"
```

---

*Generated 2026-06-13 by Lumen (Homelab mode)*
