28766b80c258d92a3a6c3beac3ea6a3d866b90c0
Phase 4 from docs/NEXT-STEPS.md. POST /tenants now writes Mongo AND drives external service provisioning. A new POST /tenants/:slug/reconcile endpoint retries the orchestration — useful when an upstream was down at create time or external state drifted out of band. Integration clients (services/provisioning/src/integrations/): - AuthentikClient: real implementation. ensureGroup() is idempotent — looks up the group by name, creates if missing, returns either way. Group attributes record the tenant slug + Mongo id so we can trace back - StalwartClient: stubbed. v0.16 removed the REST management API in favor of JMAP, which is significantly more work to wrap. TODO comment points to https://stalw.art/docs/api/management/overview for the follow-up - OcisClient: stubbed. Needs libregraph /drives endpoint with service-to- service auth via OIDC client_credentials Orchestration (provisioning.service.ts): - Each step runs independently; one failure doesn't roll back the others - Per-step state recorded on Tenant.provisioningStatus (ok/skipped/error/ pending) plus error message on Tenant.provisioningErrors - Steps return their own terminal state — 'skipped' for stubs, void defaults to 'ok' for real integrations - Mongoose markModified() required for nested subdoc mutations to persist - Tenant auto-flips status: pending → active when all steps are ok|skipped Portal proxy routes (apps/portal/server/api/tenants/): - POST /api/tenants and POST /api/tenants/:slug/reconcile forward the signed-in user's access token to the provisioning service. Lets the browser drive provisioning without minting tokens by hand. Will be replaced by a real "create workspace" flow with UI later docker-compose: AUTHENTIK_API_URL/STALWART_API_URL/OCIS_API_URL now point at the public Traefik-routed hostnames (with mkcert CA mounted into the provisioning container so Node fetch trusts them). Previously these pointed at internal Docker hostnames which doesn't work for Authentik because of TLS issuer mismatch against the JWT.
Dezky
Sovereign workspace platform for European businesses. Mail, files, calendar, video meetings — all EU-hosted, all open source.
Quick start (local development)
# 1. Clone and enter
git clone <repo-url> dezky
cd dezky
# 2. Run bootstrap (handles everything)
./scripts/bootstrap.sh
# 3. Open the portal
open https://app.dezky.local
The bootstrap script:
- Checks prerequisites (Docker, mkcert, openssl)
- Generates wildcard TLS certificate via mkcert
- Adds /etc/hosts entries (with your permission)
- Generates secure random secrets in
.env - Pulls Docker images
- Starts all services in correct order
- Prints next-step instructions
Service URLs (local development)
| Service | URL | Purpose |
|---|---|---|
| Portal | https://app.dezky.local | Customer-facing landing & launcher |
| Authentik | https://auth.dezky.local | Identity provider (OIDC/SAML) |
| Files (OCIS) | https://files.dezky.local | File storage & sharing |
| Mail (Stalwart) | https://mail.dezky.local | Mail server admin UI |
| Office | https://office.dezky.local | Collabora Online editor |
| Traefik | https://traefik.dezky.local | Reverse proxy dashboard |
What's in this repo
dezky/
├── apps/portal/ Nuxt 3 customer portal
├── services/provisioning/ NestJS provisioning worker
├── packages/ Shared TypeScript libraries
├── infrastructure/
│ └── docker-compose/ Local development stack
├── scripts/ Setup, reset, helpers
└── docs/ Service references & guides
Prerequisites
- macOS or Linux (Windows users: use WSL2)
- Docker Desktop 24+ or OrbStack
- mkcert (
brew install mkcert) - pnpm 9+ (
brew install pnpm) - Node.js 20+
- 16 GB RAM recommended
Common commands
# Start everything
docker compose -f infrastructure/docker-compose/docker-compose.yml up -d
# View logs
docker compose -f infrastructure/docker-compose/docker-compose.yml logs -f [service]
# Stop everything (keeps data)
docker compose -f infrastructure/docker-compose/docker-compose.yml down
# Nuke and restart (DESTROYS DATA)
./scripts/reset.sh
Architecture
This is a multi-tenant SaaS platform. Each tenant gets:
- Isolated Authentik OIDC tenant
- Custom subdomain (e.g.
customer-name.dezky.local) - Mail domain in Stalwart with auto-generated DKIM
- Dedicated OCIS space hierarchy
- Branded launcher in the portal
All components are Apache 2.0 / MIT licensed — no per-seat fees, full whitelabel rights.
Production
The production target is a single Hetzner AX41-NVMe server (€39/mo) with:
- Stalwart on bare-metal
- k3s for all other services
- Hetzner Object Storage (€5/mo) as OCIS S3 backend
- Storage Box BX11 (€3.20/mo) for Restic backups
- Storage Box BX11 in Helsinki (€3.20/mo) for DR
See docs/PRODUCTION-DEPLOYMENT.md (TBD) for migration plan.
Stack rationale
These choices are deliberate after extensive license/architecture research. See CLAUDE.md for the full reasoning.
| Component | License | Why this one |
|---|---|---|
| Stalwart Mail | Apache 2.0 | Modern Rust, ActiveSync built-in, JMAP support |
| OCIS | Apache 2.0 | Cleaner license than Nextcloud (AGPL+trademark) |
| Zulip | Apache 2.0 | Only truly open-core-free chat option |
| Authentik | MIT | Better multi-tenancy than Keycloak |
| Hetzner | N/A | 100% EU sovereignty — core to business |
License
Application code: MIT (own code) Third-party services: see individual service licenses in stack.
Description
Languages
Vue
60.5%
TypeScript
37.7%
Shell
0.9%
CSS
0.5%
JavaScript
0.4%