# 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 - [x] `https://app.dezky.local` shows portal landing page (now the new auth design / post-login home) - [x] `https://auth.dezky.local` shows Authentik login - [x] Log into Authentik as admin *(still using generated `AUTHENTIK_BOOTSTRAP_PASSWORD` from `.env` — rotate before exposing to anyone else)* - [x] Follow `docs/AUTHENTIK-SETUP.md` to configure OIDC providers (ocis + dezky-portal) - [x] Test OCIS SSO end-to-end (login from `https://files.dezky.local`) - [x] 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. - [x] Add `nuxt-oidc-auth` to `apps/portal` (`1.0.0-beta.11`) - [x] Configure Authentik as OIDC provider (generic `oidc` preset with explicit URLs + discovery) - [x] Implement login/logout flows (`/auth/oidc/login`, `/auth/oidc/logout` from the module) - [x] Display logged-in user info on the portal home (`pages/index.vue` uses `useOidcAuth()`) - [x] 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: ```typescript // 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: ```typescript // 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