Files
dezky/docs/NEXT-STEPS.md
T
Ronni Baslund adfd9baafe 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
2026-05-23 21:25:11 +02:00

6.3 KiB

Next Steps — After Local Stack Is Running

Once ./scripts/bootstrap.sh completes successfully and all services are reachable, here's the development roadmap.

Phase 1: Verify everything works (day 1) — done

  • https://app.dezky.local shows portal landing page (now the new auth design / post-login home)
  • https://auth.dezky.local shows Authentik login
  • Log into Authentik as admin (still using generated AUTHENTIK_BOOTSTRAP_PASSWORD from .env — rotate before exposing to anyone else)
  • Follow docs/AUTHENTIK-SETUP.md to configure OIDC providers (ocis + dezky-portal)
  • Test OCIS SSO end-to-end (login from https://files.dezky.local)
  • Verify Stalwart admin UI loads at https://mail.dezky.local/login (root path 404s — admin SPA is at /login)

Phase 2: Build portal authentication (week 1) — done

Goal: Users can log in to the portal via Authentik.

  • Add nuxt-oidc-auth to apps/portal (1.0.0-beta.11)
  • Configure Authentik as OIDC provider (generic oidc preset with explicit URLs + discovery)
  • Implement login/logout flows (/auth/oidc/login, /auth/oidc/logout from the module)
  • Display logged-in user info on the portal home (pages/index.vue uses useOidcAuth())
  • Add protected routes (globalMiddlewareEnabled: true; public pages opt out via definePageMeta({ auth: false }))

Where things live

Concern File
OIDC module config apps/portal/nuxt.config.ts (oidc block)
Custom login page apps/portal/pages/auth/login.vue
Error states (expired / disabled) apps/portal/pages/auth/{expired,disabled}.vue
Post-login landing apps/portal/pages/index.vue
Visual shell + tokens apps/portal/components/auth/*, assets/styles/tokens.css
Brand mark apps/portal/components/NodeMark.vue

Dev-mode caveats (clean up before prod)

  • skipAccessTokenParsing: true in the OIDC config — Authentik's access tokens in this setup aren't reliably JWT-parseable; production should re-evaluate
  • openIdConfiguration is pinned to the discovery URL because the generic oidc preset doesn't ship a default — required for id_token JWKS validation
  • docker-compose.yml mounts infrastructure/docker-compose/certs/mkcert-root.pem into the portal at /etc/ssl/mkcert-root.pem and sets NODE_EXTRA_CA_CERTS so Node fetch trusts the mkcert root CA. In prod, replace with real CA-signed certs
  • Traefik has Docker network aliases for auth.dezky.local, app.dezky.local, etc. so container-to-Authentik fetch resolves inside the network without going through host /etc/hosts

Phase 3: Tenant data model (week 1-2)

Goal: MongoDB schema for tenants, users, subscriptions.

  • Define Mongoose schemas in services/provisioning/src/schemas/
  • Tenant schema: id, name, slug, status, plan, billingInfo, domains
  • User schema: id, tenantId, email, name, role, authentikId
  • Subscription schema: tenantId, plan, status, stripeCustomerId
  • Add CRUD endpoints in NestJS

Schema example:

// services/provisioning/src/schemas/tenant.schema.ts
@Schema({ timestamps: true })
export class Tenant {
  @Prop({ required: true, unique: true })
  slug: string

  @Prop({ required: true })
  name: string

  @Prop({ enum: ['pending', 'active', 'suspended', 'deleted'], default: 'pending' })
  status: string

  @Prop({ type: [String], default: [] })
  domains: string[]

  @Prop({ enum: ['mvp', 'pro', 'enterprise'], default: 'mvp' })
  plan: string

  @Prop()
  authentikGroupId?: string

  @Prop()
  ocisSpaceId?: string
}

Phase 4: Provisioning automation (week 2-3)

Goal: Sign up creates tenant resources across all services.

  • Endpoint: POST /tenants — creates tenant in MongoDB
  • Worker: triggers Authentik tenant/group creation via API
  • Worker: configures Stalwart domain + DKIM via admin API
  • Worker: creates OCIS space
  • Worker: emails customer with onboarding info

Authentik API examples:

// Create group (tenant) in Authentik
await authentikClient.coreGroupsCreate({
  name: tenant.slug,
  attributes: { tenantId: tenant.id, plan: tenant.plan },
})

// Create user
await authentikClient.coreUsersCreate({
  username: user.email,
  email: user.email,
  name: user.name,
  groups: [authentikGroupId],
})

Phase 5: Custom webmail (week 3-4)

Goal: Branded webmail client using Stalwart's JMAP API.

  • Add JMAP client library to portal
  • Build inbox view in Nuxt
  • Build compose dialog
  • Build message view with thread support
  • Style to match Dezky branding

JMAP is a modern JSON-RPC protocol — clean to work with.

Phase 6: Production migration prep (week 4+)

When the local stack is solid and you have 2-3 pilot customers interested:

  • Order Hetzner AX41-NVMe
  • Order Storage Box BX11 (Falkenstein)
  • Enable Hetzner Object Storage (bucket: dezky-ocis-prod)
  • Build Terraform module for Hetzner provisioning
  • Build Ansible playbook for bare-metal Stalwart deployment
  • Set up k3s on the cloud server
  • Migrate compose to Helm charts
  • Configure Let's Encrypt via cert-manager
  • Set up Restic backup jobs to Storage Box + B2

Phase 7: Add Zulip and Jitsi (when chat/video needed)

These were excluded from MVP for simplicity. When ready:

  • Create infrastructure/docker-compose/docker-compose.optional.yml
  • Add Zulip stack (server + db + worker)
  • Add Jitsi stack (web + prosody + jicofo + jvb)
  • Configure OIDC integration with Authentik
  • Add to portal launcher

Decisions still open

These need to be made before public launch:

  • Final pricing tiers (MVP, Pro, Enterprise)
  • dezky.com purchase decision ($3,000 via BrandBucket)
  • Final logo design (4 directions explored, need to pick one)
  • Legal entity structure for the new business
  • DPA (databehandleraftale) template
  • Customer support process (ticket system choice)

Long-term architecture goals

  • Multi-region deployment (Hetzner Falkenstein + Helsinki)
  • Disaster recovery: cross-DC Restic copies
  • ISO 27001 certification via Vanta
  • GDPR Article 30 record of processing activities
  • SOC 2 (later, for enterprise customers)
  • Customer-facing status page (Uptime Kuma or cstate)
  • Public documentation site
  • Self-service migration tooling from M365