chore: initial scaffold with running local stack and portal auth
Brings up Dezky's local development environment end-to-end: Infrastructure (docker-compose): - Traefik v3.7 reverse proxy with mkcert TLS (v3.2 couldn't speak Docker API 1.54) - Postgres + Mongo + Redis with healthchecks and init script for per-service users - Authentik 2025.10 (server + worker) as OIDC IdP - Stalwart v0.16 mail server (image renamed from stalwartlabs/mail-server) - OCIS 7.0 with PROXY_TLS=false and OCIS_CONFIG_DIR=/etc/ocis so init writes where the server reads - Collabora office, plus the portal + provisioning service stubs - Docker network aliases on Traefik so containers resolve auth.dezky.local etc. through the network (not host /etc/hosts) - Docker socket mount parameterized for macOS Docker Desktop symlink path Authentik provisioning (done via API after stack boot): - ocis-provider (public client) + OCIS Files application - dezky-portal provider (confidential) + Dezky Portal application - Admin API token bound to akadmin manually since 2025.10's AUTHENTIK_BOOTSTRAP_TOKEN env var doesn't auto-materialize a token row Portal (apps/portal): - Nuxt 3 with nuxt-oidc-auth 1.0.0-beta.11 against generic 'oidc' preset - Global auth middleware; login at /auth/oidc/login redirects to Authentik - Visual implementation of Claude Design 'Auth' canvas: AuthShell, NodeMark, Auth* sub-components, design tokens as CSS custom properties - Pages: auth/login, auth/expired, auth/disabled, index (post-login landing) - mkcert root CA mounted into the portal so Node fetch trusts Authentik's self-signed cert (NODE_EXTRA_CA_CERTS) — dev only Docs: - AUTHENTIK-SETUP.md updated with manual token bind + portal provider scripted alternative - NEXT-STEPS.md: Phase 1 and Phase 2 marked done with file locations and dev-mode caveats Dev-mode shortcuts that need to be revisited before prod: - skipAccessTokenParsing on the OIDC config - NODE_EXTRA_CA_CERTS mkcert mount - Bootstrap password still the generated value in .env - Authentik admin token (dezky-bootstrap-token) is non-expiring
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
# Dezky — Local Development Environment Setup
|
||||
|
||||
> **For Claude Code:** This is a complete handover document for spinning up the Dezky platform locally for development. Everything you need to know is here.
|
||||
|
||||
## Mission for this session
|
||||
|
||||
The user (Ronni) has already:
|
||||
- Selected brand: **Dezky** (dezky.com pending purchase)
|
||||
- Run `mkcert` on macOS to generate local TLS certificates
|
||||
- Decided on production target: Hetzner AX41-NVMe + Object Storage + Storage Box
|
||||
- Defined the full stack (see Stack section)
|
||||
|
||||
Your job: Get the entire Dezky platform running locally via Docker Compose so Ronni can develop against it. The user has solid technical fluency (NestJS, Nuxt, MongoDB, Docker, Kubernetes) — skip basic explanations, but be thorough on Dezky-specific configuration.
|
||||
|
||||
## Stack — what we're spinning up
|
||||
|
||||
All components are Apache 2.0 / MIT licensed for clean commercial multi-tenant hosting.
|
||||
|
||||
| Service | Image | Purpose | Local URL |
|
||||
|---------|-------|---------|-----------|
|
||||
| Traefik | `traefik:v3.2` | Reverse proxy + TLS termination | `https://traefik.dezky.local:8443` |
|
||||
| PostgreSQL | `postgres:16-alpine` | Shared RDBMS for Authentik, OCIS | (internal) |
|
||||
| MongoDB | `mongo:7` | Portal application data | (internal) |
|
||||
| Redis | `redis:7-alpine` | Cache + session store | (internal) |
|
||||
| Authentik | `ghcr.io/goauthentik/server:2025.10` | Identity provider, OIDC/SAML | `https://auth.dezky.local` |
|
||||
| Stalwart Mail | `stalwartlabs/mail-server:latest` | Mail (SMTP/IMAP/JMAP/CalDAV) | `https://mail.dezky.local` |
|
||||
| OCIS | `owncloud/ocis:7.0` | File storage (S3-compatible backend) | `https://files.dezky.local` |
|
||||
| Collabora | `collabora/code:latest` | Office document editor inside OCIS | `https://office.dezky.local` |
|
||||
| Portal stub | (built from `./apps/portal`) | Nuxt 3 customer portal | `https://app.dezky.local` |
|
||||
| Provisioning | (built from `./services/provisioning`) | NestJS provisioning worker | (internal, port 3001) |
|
||||
|
||||
**NOT included in this dev setup** (added in later phases):
|
||||
- Jitsi Meet (4-5 sub-containers — see `docker-compose.optional.yml` when ready)
|
||||
- Zulip (resource-heavy — added separately when chat features are needed)
|
||||
|
||||
## Critical setup invariants
|
||||
|
||||
1. **All code in English** — variable names, comments, schema fields, routes, function names. UI strings may be Danish for end users only.
|
||||
2. **TypeScript everywhere** in apps and services.
|
||||
3. **mkcert TLS certs** — the user has already run `mkcert -install` and generated wildcard certs. They will be placed in `infrastructure/docker-compose/certs/`.
|
||||
4. **All hostnames use `.dezky.local`** in development. `/etc/hosts` entries required (see `scripts/setup-hosts.sh`).
|
||||
5. **PostgreSQL is shared** among Authentik and OCIS. Each gets its own database and user (see `configs/postgres/init.sql`).
|
||||
6. **MongoDB is dedicated** to the portal application data (matches user's existing stack pattern: Målerportal, TurtleLootLine).
|
||||
7. **Secrets are .env-based** for dev — production will use SOPS/sealed-secrets in k3s.
|
||||
|
||||
## Expected user environment
|
||||
|
||||
- macOS (uses `brew` for installs)
|
||||
- Docker Desktop or OrbStack running
|
||||
- mkcert installed and root CA trusted
|
||||
- 16+ GB RAM (stack uses ~10 GB at peak)
|
||||
- `pnpm` for Node workspaces
|
||||
- `git` for version control
|
||||
|
||||
If anything is missing, instruct the user with the exact `brew install` command.
|
||||
|
||||
## File structure
|
||||
|
||||
```
|
||||
dezky/
|
||||
├── CLAUDE.md # This file
|
||||
├── README.md # Human-facing setup guide
|
||||
├── .env.example # All required env vars
|
||||
├── .gitignore
|
||||
├── apps/
|
||||
│ └── portal/ # Nuxt 3 portal (stub for now)
|
||||
├── services/
|
||||
│ └── provisioning/ # NestJS worker (stub for now)
|
||||
├── packages/ # Shared TypeScript packages (empty for now)
|
||||
├── infrastructure/
|
||||
│ └── docker-compose/
|
||||
│ ├── docker-compose.yml # Main stack
|
||||
│ ├── docker-compose.optional.yml # Jitsi + Zulip (later)
|
||||
│ ├── certs/ # mkcert TLS certs go here
|
||||
│ │ ├── dezky.local.pem # User places mkcert output here
|
||||
│ │ └── dezky.local-key.pem
|
||||
│ └── configs/
|
||||
│ ├── traefik/
|
||||
│ │ ├── traefik.yml # Static config
|
||||
│ │ └── dynamic.yml # TLS + middleware
|
||||
│ ├── stalwart/
|
||||
│ │ └── config.toml # Mail server config
|
||||
│ ├── postgres/
|
||||
│ │ └── init.sql # Create DBs/users
|
||||
│ ├── authentik/
|
||||
│ │ └── blueprints/ # (Optional) Pre-configured flows
|
||||
│ └── ocis/
|
||||
│ └── (config files mounted at runtime)
|
||||
├── scripts/
|
||||
│ ├── bootstrap.sh # One-command setup
|
||||
│ ├── setup-hosts.sh # /etc/hosts entries
|
||||
│ ├── setup-certs.sh # Copy mkcert certs to right place
|
||||
│ └── reset.sh # Nuke and start fresh
|
||||
└── docs/
|
||||
├── SERVICES.md # Per-service reference
|
||||
├── AUTHENTIK-SETUP.md # First-time Authentik walkthrough
|
||||
├── TROUBLESHOOTING.md # Common issues
|
||||
└── NEXT-STEPS.md # What to do after setup works
|
||||
```
|
||||
|
||||
## Sequence for Claude Code to execute
|
||||
|
||||
When the user runs `claude` in the project directory, walk through:
|
||||
|
||||
### Phase 1: Verify environment (5 min)
|
||||
|
||||
```bash
|
||||
# Check that required tools are installed
|
||||
docker --version # Need 24.0+
|
||||
docker compose version # Need v2
|
||||
mkcert -version # Need 1.4+
|
||||
pnpm --version # Need 9+
|
||||
node --version # Need 20+
|
||||
```
|
||||
|
||||
If anything is missing, halt and instruct user with brew install commands.
|
||||
|
||||
### Phase 2: Cert setup (2 min)
|
||||
|
||||
The user has already run `mkcert -install`. We need to:
|
||||
|
||||
1. Check if `infrastructure/docker-compose/certs/dezky.local.pem` exists
|
||||
2. If not, run mkcert for the wildcard domain:
|
||||
```bash
|
||||
cd infrastructure/docker-compose/certs
|
||||
mkcert "*.dezky.local" "dezky.local" "localhost" "127.0.0.1" "::1"
|
||||
mv ./_wildcard.dezky.local+4.pem dezky.local.pem
|
||||
mv ./_wildcard.dezky.local+4-key.pem dezky.local-key.pem
|
||||
```
|
||||
|
||||
### Phase 3: DNS setup (2 min)
|
||||
|
||||
Run `scripts/setup-hosts.sh` which adds these to `/etc/hosts` (requires sudo):
|
||||
|
||||
```
|
||||
127.0.0.1 dezky.local app.dezky.local auth.dezky.local
|
||||
127.0.0.1 mail.dezky.local files.dezky.local meet.dezky.local
|
||||
127.0.0.1 chat.dezky.local office.dezky.local
|
||||
127.0.0.1 traefik.dezky.local
|
||||
```
|
||||
|
||||
### Phase 4: Environment variables (2 min)
|
||||
|
||||
Copy `.env.example` to `.env` and generate secure random values:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Generate 64-char hex for each *_SECRET / *_KEY
|
||||
openssl rand -hex 32 # run multiple times, paste into .env
|
||||
```
|
||||
|
||||
### Phase 5: First boot (10 min)
|
||||
|
||||
```bash
|
||||
cd infrastructure/docker-compose
|
||||
docker compose pull # Get all images first
|
||||
docker compose up -d postgres redis mongo
|
||||
# Wait for healthchecks
|
||||
docker compose logs -f postgres
|
||||
# When ready, start identity layer
|
||||
docker compose up -d authentik-server authentik-worker
|
||||
# Wait for Authentik to be ready
|
||||
docker compose logs -f authentik-server
|
||||
# Then start everything else
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Phase 6: Authentik initial setup (15 min)
|
||||
|
||||
Open `https://auth.dezky.local` and walk through:
|
||||
1. Create admin user (akadmin)
|
||||
2. Configure OIDC providers for: ocis, portal, stalwart
|
||||
3. Test SSO end-to-end
|
||||
|
||||
See `docs/AUTHENTIK-SETUP.md` for the exact steps.
|
||||
|
||||
### Phase 7: Verify it all works (5 min)
|
||||
|
||||
- `https://app.dezky.local` — Portal landing page should load
|
||||
- `https://auth.dezky.local` — Authentik login screen
|
||||
- `https://files.dezky.local` — OCIS should redirect to Authentik for SSO
|
||||
- `https://mail.dezky.local` — Stalwart admin UI
|
||||
|
||||
## Important context for working on this project
|
||||
|
||||
### Stack rationale (DO NOT suggest replacing components)
|
||||
|
||||
These choices were made deliberately after extensive license/architecture research:
|
||||
|
||||
- **Stalwart over Mailcow**: Modern Rust, ActiveSync built-in, JMAP support, single binary
|
||||
- **OCIS over Nextcloud**: Apache 2.0 vs AGPL+trademark fees for whitelabel
|
||||
- **Zulip over Element/Mattermost/Rocket.Chat**: Only truly open-core-free chat option
|
||||
- **Authentik over Keycloak**: Better multi-tenancy, MIT license, simpler config
|
||||
- **Hetzner over AWS/GCP**: 100% EU sovereignty pitch — this is core to the business
|
||||
|
||||
### Code conventions
|
||||
|
||||
- **TypeScript strict mode** in apps/services
|
||||
- **English only** in all code identifiers, comments, schema fields
|
||||
- **Conventional Commits** for git messages: `feat:`, `fix:`, `chore:`, `docs:`
|
||||
- **Prefer prose comments** over heavy JSDoc — explain *why*, not *what*
|
||||
- **MongoDB** for portal app data (consistent with Målerportal, TurtleLootLine)
|
||||
- **PostgreSQL** for services that require it (Authentik, OCIS)
|
||||
|
||||
### Production target (for reference, not deploy now)
|
||||
|
||||
Eventually moves to single Hetzner AX41-NVMe (€39/mo) with:
|
||||
- Stalwart on bare-metal (not Docker)
|
||||
- k3s for all other services
|
||||
- Hetzner Object Storage (€5/mo) for OCIS S3 backend
|
||||
- Storage Box BX11 (€3.20/mo) for Restic backups
|
||||
- Storage Box BX11 in Helsinki (€3.20/mo) for DR
|
||||
|
||||
But locally, everything runs in Docker Compose with mkcert TLS.
|
||||
|
||||
## What to do if stuck
|
||||
|
||||
- **TLS not working**: Verify `mkcert -CAROOT` matches what Docker has access to. On macOS, the root CA must be trusted in the system keychain.
|
||||
- **Authentik won't start**: PostgreSQL needs to be fully ready first. Check `docker compose logs postgres` for `database system is ready to accept connections`.
|
||||
- **OCIS OIDC fails**: Authentik issuer URL must be reachable from inside the OCIS container. Use the Docker network hostname, not the public URL.
|
||||
- **Stalwart port 25 conflicts**: macOS may have postfix running. Disable with `sudo launchctl unload /System/Library/LaunchDaemons/org.postfix.master.plist`.
|
||||
- **Cert errors in browser**: Make sure `mkcert -install` has been run AND browser has been restarted.
|
||||
|
||||
See `docs/TROUBLESHOOTING.md` for detailed solutions.
|
||||
|
||||
## After local dev works
|
||||
|
||||
1. Build out the Nuxt portal (`apps/portal`) — start with auth flow via Authentik OIDC
|
||||
2. Build the provisioning service (`services/provisioning`) — first endpoint: create tenant
|
||||
3. Wire portal → provisioning → Authentik/OCIS/Stalwart admin APIs
|
||||
4. Add Zulip + Jitsi when ready (`docker-compose.optional.yml`)
|
||||
5. When portal MVP is solid → migrate to Hetzner AX41 production
|
||||
|
||||
---
|
||||
|
||||
**Last updated:** 2026-05-23
|
||||
Reference in New Issue
Block a user