Complete backend for the Ulti-Suite
Go to file
R3D347HR4Y f7ef89fa82
Some checks are pending
CI / Go tests (push) Waiting to run
CI / Integration tests (push) Waiting to run
CI / DB migrations (push) Waiting to run
feat(authentik): recovery email links to embedded reset-password UI
Custom email template rendered via AUTH_APP_URL, mounted in Authentik,
and gitignored rendered HTML to avoid localhost hardcoding in prod.
2026-06-20 01:21:30 +02:00
.cursor/rules Lots of changes 2026-06-04 00:12:11 +02:00
.github/workflows feat(tests): add integration testing framework and configuration 2026-06-07 19:44:29 +02:00
cmd feat(tests): add integration testing framework and configuration 2026-06-07 19:44:29 +02:00
deploy feat(authentik): recovery email links to embedded reset-password UI 2026-06-20 01:21:30 +02:00
internal feat(auth): implement flow completion and rate limiting for authentication flows 2026-06-20 01:09:42 +02:00
migrations feat(devices): implement mobile device token management and push notifications 2026-06-17 00:11:25 +02:00
project-plan feat(mail): integrate Stalwart hosted mail and migration features 2026-06-13 12:47:08 +02:00
scripts feat(tests): add integration testing framework and configuration 2026-06-07 19:44:29 +02:00
services feat(deploy): enhance Nginx configuration and API integration for UltiAI 2026-06-15 00:22:23 +02:00
.cursorignore Lots of changes 2026-06-04 00:12:11 +02:00
.dockerignore Initialize Ulti Backend project with Docker setup, environment configuration, and core services. Added .dockerignore, .env.example, Dockerfile, and docker-compose files for PostgreSQL, KeyDB, RustFS, Authentik, Nextcloud, Jitsi, and Immich. Implemented main application structure in Go with API handlers and environment variable expansion. Included README for project overview and setup instructions. 2026-05-22 16:02:53 +02:00
.env.example feat(authentik): recovery email links to embedded reset-password UI 2026-06-20 01:21:30 +02:00
.env.test.example feat(tests): add integration testing framework and configuration 2026-06-07 19:44:29 +02:00
.gitignore feat(authentik): recovery email links to embedded reset-password UI 2026-06-20 01:21:30 +02:00
CLAUDE.md feat(config): enhance AI gateway and model management features 2026-06-13 20:38:26 +02:00
Dockerfile Backend starting to get good 2026-05-24 00:03:36 +02:00
go.mod feat(mail): integrate Stalwart hosted mail and migration features 2026-06-13 12:47:08 +02:00
go.sum feat(mail): integrate Stalwart hosted mail and migration features 2026-06-13 12:47:08 +02:00
mailpennylane.txt Lots of changes 2026-06-04 00:12:11 +02:00
Makefile feat(tests): add integration testing framework and configuration 2026-06-07 19:44:29 +02:00
package.json feat(devices): implement mobile device token management and push notifications 2026-06-17 00:11:25 +02:00
README.md fix(docs): update README and Nginx configuration for frontend routes 2026-06-16 11:32:56 +02:00

Ulti Backend

Backend monolithe Go orchestrant la Ulti Suite — alternative souveraine à Google Suite.

Architecture

┌─────────────────────────────────────────────────────────┐
│  nginx (reverse proxy unique, port 80)                  │
├─────────────────────────────────────────────────────────┤
│  ultid (Go monolithe)                                   │
│  ├─ /api/v1/mail      — Ultimail (IMAP/SMTP custom)    │
│  ├─ /api/v1/drive     — Ultidrive (→ Nextcloud WebDAV) │
│  ├─ /api/v1/calendar  — Agenda (→ Nextcloud CalDAV)    │
│  ├─ /api/v1/contacts  — Contacts (→ Nextcloud CardDAV) │
│  ├─ /api/v1/meet      — Ultimeet (→ Jitsi JWT)        │
│  ├─ /api/v1/photos    — Ultiphotos (→ Immich API)     │
│  ├─ /api/v1/admin     — Administration                 │
│  ├─ /api/v1/search    — Recherche transversale         │
│  └─ /ws               — WebSocket realtime             │
├─────────────────────────────────────────────────────────┤
│  Services tiers (Docker)                                │
│  ├─ PostgreSQL 16     — DB partagée                    │
│  ├─ KeyDB             — Cache/sessions                  │
│  ├─ RustFS            — Object storage S3              │
│  ├─ Authentik         — Auth OIDC/SAML                 │
│  ├─ Nextcloud (nginx+FPM) — Drive/Cal/Contacts (/cloud) │
│  ├─ Jitsi             — Visioconférence                │
│  └─ Immich            — Photos/ML                      │
└─────────────────────────────────────────────────────────┘

Quick Start

# 1. Copy environment file
cp .env.example .env
# Edit secrets once at the top of .env (POSTGRES_PASSWORD, RUSTFS_SECRET_KEY, etc.)
# Defaults use changeme — must match Authentik blueprints (deploy/authentik/blueprints/).

# 2. Start stack (core + modules enabled by flags)
./deploy/compose-up.sh up -d --build

Auto-configured on first start:

  • SQL migrations (ULTID_AUTO_MIGRATE=true, embedded in ultid)
  • Authentik OAuth apps Ultimail (ulti-backend) and Nextcloud via blueprints in deploy/authentik/blueprints/
  • OIDC issuer for ultid via internal nginx: ULTID_OIDC_ISSUER=http://nginx/auth/application/o/ulti/

Frontend suite unifié (gmail-interface-clone — mail + drive + contacts) : .env.local avec NEXT_PUBLIC_APP_URL=http://localhost, puis pnpm devhttp://localhost/mail/ et http://localhost/drive/

Service URL
API / Auth http://localhost
Ultimail http://localhost/mail/
UltiDrive http://localhost/drive/
Grafana http://localhost:3002

Development

# Unit tests (fast, no Docker)
make test
# or: go test ./...

# Integration tests (Docker required — Postgres + MinIO via testcontainers)
cp .env.test.example .env.test   # optional overrides
make test-integration

# Run a single domain or test
make test-integration-mail
go test -tags=integration ./internal/integrationtest/mail/... -run TestMailSettingsCRUD -count=1 -v

# Enable optional suite modules (Nextcloud, Immich, Jitsi)
# ULTI_TEST_NEXTCLOUD=1 ULTI_TEST_NEXTCLOUD_URL=http://localhost:8081 make test-integration

Integration tests use a real PostgreSQL database (ephemeral container by default), miniredis, MinIO, and a test OIDC issuer that signs JWTs. Set ULTI_TEST_INTEGRATION=1 to run them. See .env.test.example for all variables.

# Run locally (needs PG, KeyDB, RustFS, Authentik running; loads .env with {{VAR}} expansion)
go run ./cmd/ultid

# Build
go build -o ultid ./cmd/ultid

# Migrations run automatically on ultid start. To disable: ULTID_AUTO_MIGRATE=false

# Manual migrate (optional)
go run ./cmd/envexpand -in .env -out .env.resolved
source <(grep -v '^#' .env.resolved | sed 's/^/export /')
migrate -path migrations -database "$ULTID_DB_URL" up

Reverse proxy (nginx)

Un seul nginx expose lentrée HTTP (:80) et route :

Chemin Service
/api/* ultid
/ws ultid (WebSocket)
/auth/* Authentik
/meet/* Jitsi (si JITSI_ENABLED=true)
/cloud/* Nextcloud nginx+FPM (si NEXTCLOUD_ENABLED=true)
/mail/*, /drive/*, /contacts, /agenda, /account, /settings, /admin/* Suite frontend (MAIL_FRONTEND_UPSTREAM, défaut host.docker.internal:3004 ; Docker : suite-frontend:3000)

Nextcloud : FPM + nginx dédié ; ultid appelle NEXTCLOUD_URL en interne (http://nextcloud:80).
Caddy retiré : un seul proxy évite la double couche ; TLS plus tard (certbot, Traefik, ou listen 443 nginx).

Centralized secrets

Set passwords and keys once in the Secrets section at the top of .env. Derived values reference them with {{POSTGRES_PASSWORD}}, {{RUSTFS_SECRET_KEY}}, etc. Expansion runs via:

  • ./deploy/compose-up.sh — writes .env.resolved for Docker Compose
  • go run ./cmd/envexpand -in .env -out .env.resolved — manual export for migrate/scripts
  • go run ./cmd/ultid — expands .env in-process before reading config

Runtime secret files are also supported with *_FILE variables (example: ULTID_OIDC_CLIENT_SECRET_FILE=/run/secrets/oidc_client_secret).

Mail credentials are encrypted at rest with AES-GCM using MAIL_CREDENTIAL_KEYS (key_id:base64key,...) and MAIL_ACTIVE_CREDENTIAL_KEY_ID. Secret rotation policy is enforced through:

  • SECRET_ROTATION_MAX_AGE
  • ULTID_OIDC_CLIENT_SECRET_ROTATED_AT
  • MAIL_CREDENTIAL_KEY_ROTATED_AT
  • MAIL_WEBHOOK_SHARED_SECRET_ROTATED_AT

Observability (Prometheus / Grafana)

ultid exposes Prometheus metrics at /metrics (see internal/observability/metrics.go). The core Docker Compose stack includes Prometheus and Grafana with configs under deploy/observability/:

File Purpose
deploy/observability/prometheus/prometheus.yml Scrape ultid + self; loads alert rules
deploy/observability/prometheus/alerts.yml Alert rules: IMAP sync stalled, outbox backlog, HTTP 5xx rate
deploy/observability/grafana/ultid-baseline.json Baseline dashboard (HTTP latency/errors, IMAP sync, outbox, webhooks)
deploy/observability/grafana/provisioning/ Grafana datasource + dashboard auto-load

Start with the rest of the stack (./deploy/compose-up.sh up -d), then open:

Service URL Notes
Prometheus http://localhost:9090 Targets: ultid, prometheus
Grafana http://localhost:3002 Login from .env (GRAFANA_ADMIN_USER / GRAFANA_ADMIN_PASSWORD, default admin / admin); dashboard Ultid Baseline under folder Ultid

Alertmanager — not included in compose; route labels service=ultid and severity (critical, warning) to your on-call channels when you add it.

Stack

Component Technology
Backend Go 1.25+ (chi, pgx, go-imap, go-smtp)
Database PostgreSQL 16
Cache KeyDB (Redis-compatible, multi-threaded)
Object Storage RustFS (S3-compatible, Apache 2.0)
Auth Authentik (OIDC/SAML)
Files/Cal/Contacts Nextcloud headless (WebDAV/CalDAV/CardDAV)
Video Jitsi Meet (JWT auth)
Photos Immich (ML classification)
Reverse Proxy nginx (TLS à ajouter via certbot ou autre)
Search PostgreSQL tsvector + GIN

Calendar API (mini doc)

Base path: /api/v1/calendar (module actif seulement si NEXTCLOUD_ENABLED=true).

Endpoints principaux

Endpoint Méthode Usage
/ GET Lister calendriers
/{calID}/events GET Lister événements d'un calendrier
/{calID}/events POST Créer événement
/events/* PUT Modifier événement avec If-Match
/events/* DELETE Supprimer événement
/events/response/* POST Répondre invitation participant
/freebusy POST Obtenir disponibilités (free/busy)
/events/meet-link/* POST Générer lien Meet et l'injecter dans l'événement

* représente un chemin CalDAV absolu, ex: /remote.php/dav/calendars/{user}/{calendar}/{uid}.ics

Event payload (create / update)

{
  "uid": "team-sync-2026-05-23",
  "summary": "Team sync",
  "description": "Weekly project update",
  "location": "Paris office",
  "start": "20260523T090000Z",
  "end": "20260523T100000Z",
  "organizer": "alice@example.com",
  "attendees": [
    { "email": "bob@example.com", "name": "Bob", "status": "NEEDS-ACTION" },
    { "email": "carol@example.com", "name": "Carol", "status": "ACCEPTED" }
  ]
}

Update avec ETag / If-Match

Requête:

PUT /api/v1/calendar/events/remote.php/dav/calendars/alice/work/team-sync-2026-05-23.ics
If-Match: "68031b8f4d18f"
Content-Type: application/json

Réponse succès:

{ "etag": "\"68031b8f5f901\"" }

Si ETag obsolète, API renvoie 412 avec code etag_mismatch.

Réponse invitation participant

POST /api/v1/calendar/events/response/remote.php/dav/calendars/alice/work/team-sync-2026-05-23.ics
{
  "email": "bob@example.com",
  "response": "accepted",
  "if_match": "\"68031b8f5f901\""
}

response autorise: accepted, declined, tentative.

Free/Busy

POST /api/v1/calendar/freebusy
{
  "start": "2026-05-23T08:00:00Z",
  "end": "2026-05-23T18:00:00Z",
  "attendees": ["bob@example.com", "carol@example.com"]
}

Réponse (exemple):

{
  "attendees": [
    {
      "email": "bob@example.com",
      "periods": [
        { "start": "20260523T090000Z", "end": "20260523T100000Z", "type": "BUSY" }
      ]
    }
  ]
}

Création lien Meet depuis event

POST /api/v1/calendar/events/meet-link/remote.php/dav/calendars/alice/work/team-sync-2026-05-23.ics
{
  "if_match": "\"68031b8f5f901\""
}

Réponse:

{
  "meet_url": "https://your-domain/meet/team-sync-2026-05-23?jwt=...",
  "etag": "\"68031b8f8b102\""
}

Si module Meet désactivé (JITSI_ENABLED=false), API renvoie 409 avec code meet_disabled.

Erreurs API Calendar

HTTP code Cas
400 invalid_request_body JSON invalide, champ manquant, email participant invalide, format date invalide
400 request_body_too_large Payload dépasse limite handler (256KB)
401 auth.* Token manquant/invalide
403 auth.forbidden Permission calendrier insuffisante (read/write)
404 attendee_not_found Email donné absent des ATTENDEE de l'événement
409 meet_disabled Endpoint meet-link appelé alors que Jitsi désactivé
412 etag_mismatch If-Match obsolète sur update event/response/meet-link
500 internal_error Erreur Nextcloud/CalDAV ou erreur interne backend

Format standard:

{
  "code": "etag_mismatch",
  "message": "etag does not match current resource version",
  "trace_id": "req_01HZ..."
}

Project Structure

├── cmd/ultid/          — Entry point
├── internal/
│   ├── api/            — HTTP handlers (mail, admin, drive, calendar, contacts, meet, photos)
│   ├── mail/           — IMAP sync, SMTP send, rules engine, webhooks
│   ├── nextcloud/      — WebDAV/CalDAV/CardDAV client
│   ├── meet/           — Jitsi JWT generation
│   ├── photos/         — Immich API client
│   ├── auth/           — OIDC verification
│   ├── search/         — Full-text search
│   ├── realtime/       — WebSocket hub
│   └── config/         — Environment config
├── migrations/         — SQL migrations
├── deploy/             — Docker Compose configs
│   ├── docker-compose.yml  — Core stack
│   ├── observability/      — Prometheus alerts + Grafana dashboard
│   ├── nginx/
│   ├── nextcloud/
│   ├── jitsi/
│   └── immich/
├── Dockerfile          — Multi-stage build
└── .env.example