14 KiB
🔁 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):
-
App-wide intl hydration crash ("Oops" on every page). Root
app/layout.tsxrenders global client components (PwaInstallPrompt→useTranslations,Toaster,Sonner) as siblings of{children}inside<Providers>, but only each route-group layout wrapped its own children inNextIntlClientProvider. Those global components mounted with no intl context → "No intl context found" → hydration crash. Fix: root layout is nowasyncand wraps the body inNextIntlClientProviderviagetMessages(). Nested route-group providers remain valid (next-intl nests). -
PWA assets intercepted by auth middleware (manifest.json syntax error + stale cache). The middleware
matcherdid not exclude/manifest.jsonor/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 excludesmanifest.json,sw.js,icons,offline. Verified:manifest.json→ 200application/json,sw.js→ 200application/javascript. -
Service-worker stale cache. Bumped
CACHE_NAMEv1→v2inpublic/sw.jsso theactivatehandler 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/mcpv0.0.76 (run vianpx @playwright/mcp@latest) + Chromium headless-shell (npx playwright install chromium). Wired into Roomcp_settings.json(server nameplaywright). 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):
-
NextAuth
MissingSecret→ "Oops" error boundary — override env (TrueNAS, not git) NextAuth v5 (Auth.js) readsAUTH_SECRET, notNEXTAUTH_SECRET. The runtime env had onlyNEXTAUTH_SECRET, sosignInthrewMissingSecret→ the React error boundary fired. AddedAUTH_SECRET(+AUTH_TRUST_HOST=true) to the frontend service in the TrueNAS override. -
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 fortest123. -
Backend HTTP 500 after successful auth:
Illegal base64 character: '-'— commitdac884cJwtService.getSigningKey()doesDecoders.BASE64.decode(secret). The compose secretdocker-dev-secret-key-...-for-hmacis plaintext with hyphens (not valid base64), so signing the JWT threw once credentials passed. Replaced with a real base64 secret (openssl rand -base64 48). -
NextAuth
CredentialsSignin— API/frontend contract mismatch — commit281addaauthorize()readdata.member.id/email/clubName/clubId, but the backendLoginResponseis flat:{ accessToken, refreshToken, expiresIn, role }— nomemberobject. Accessingdata.member.idonundefinedthrew →authorizereturned null. Fixed by decoding the JWT payload for identity claims (sub=userId,email,tenant_id=clubId) + the flatrole.
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:
-
Frontend build crash (
ERR_INVALID_URL, input: 'undefined') — commitf6a7143Root layoutsrc/app/layout.tsxevaluatednew URL(process.env.BASE_URL)at module load.BASE_URLwas never a build-time ENV →new URL(undefined)threw. Because it's the root layout, itsmetadatais 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_URLbuild ENV in Dockerfile. The middleware/force-dynamic fixes were irrelevant (metadata is evaluated before middleware ever runs). -
Backend crash:
Schema validation: missing table [audit_events]— commit8490da4This is Spring Boot 4.0.6, which modularized autoconfiguration.FlywayAutoConfigurationmoved into a dedicatedspring-boot-flywaymodule pulled in only byspring-boot-starter-flyway. The pom had onlyflyway-database-postgresql(+ transitiveflyway-core) but NOT the starter, sospring.flyway.enabled=truewas inert: no migrations ran, noflyway_schema_history, Hibernateddl-auto=validatefailed on the empty schema. Fix: addspring-boot-starter-flyway. Ref: https://spring.io/blog/2025/10/28/modularizing-spring-boot/ -
Backend unhealthy (503 on /actuator/health) — commit
60844efspring-boot-starter-mailregisters a mail health indicator that trieslocalhost: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=falsein 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
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
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)
ssh truenas.local "docker ps --filter name=cannamanage"
# Wait for cannamanage-backend to show (healthy)
Step 4: Load seed data
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"→ addexport const dynamic = "force-dynamic"src/app/(marketing)/impressum/page.tsxsrc/app/(marketing)/datenschutz/page.tsxsrc/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
# 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)