Files
teamvis-selfhost/docs/NEUKUNDE.md
T
2026-06-25 16:43:22 +02:00

10 KiB

Neukunden-Onboarding (TeamVis-Instanz)

Reproduzierbares Runbook für eine frische TeamVis-Instanz (eigener Mandant: eigene Supabase, eigene Domain). Das Container-Image ist mandanten-neutral — alle Kunden laufen auf demselben Image, unterschieden nur durch ENV + DB + Branding. Dieses Dokument führt von „nichts" bis „live".

Reihenfolge ist verbindlich: DB → ENV → Container → erster Admin → Branding → Smoke-Test.


0. Voraussetzungen

  • Eine Supabase-Instanz (Cloud-Projekt oder self-hosted). Du brauchst:
    • Projekt-URL (https://xxxx.supabase.co bzw. https://supabase.kunde.de)
    • anon-Key (öffentlich)
    • service_role-Key (geheim, nur Server)
  • Eine Domain (z. B. team.kunde.de) mit DNS auf den Docker-Host.
  • Ein Docker-Host mit Zugriff auf die Registry git.zoesch.de/zfx-services/teamvis (Registry-Login vorhanden) und einem Reverse-Proxy (Caddy/Traefik/nginx).
  • Lokal dieses Repo + node (für scripts/-Helfer).

1. Datenbank-Schema einspielen

Alle Tabellen, RLS-Policies, Storage-Buckets (branding-assets und employee-photos) und GRANTs kommen aus supabase/migrations/. Es gibt zwei Wege:

Die Basis-Tabellen employees + admin_users legt seit 0000_base_schema.sql die erste Migration an (create table if not exists). Auf einer leeren DB bootstrappt das Bündel damit vollständig; auf einer bestehenden Instanz ist 0000 ein No-Op.

Variante A — Schema-Bündel (ohne supabase-CLI/psql-Zugang, empfohlen für Erst-Setup)

node scripts/bundle-migrations.mjs > /tmp/teamvis-schema.sql

Den Inhalt von /tmp/teamvis-schema.sql einmal in den Supabase-Studio SQL-Editor einfügen und ausführen. Das Bündel enthält alle Migrationen in korrekter Reihenfolge.

Wichtig: Das Bündel ist für eine leere DB gedacht (Erst-Lauf). Für spätere Schema-Updates auf einer bestehenden Instanz nur die neue Einzel-Migration ausführen, nicht das ganze Bündel.

Variante B — supabase-CLI (wenn DB-Zugang verfügbar)

supabase link --project-ref <ref>
supabase db push

Kontrolle

In Supabase Studio prüfen, dass existieren:

  • Tabellen: employees, admin_users, admin_invites, site_settings, … (Organigramm-, Compliance-, Lead-, License-Tabellen)
  • Storage-Buckets: branding-assets und employee-photos (beide public)
  • site_settings: GRANTs sind spaltenweise (Geheimnis-Spalten nicht für anon, siehe Migration 0043)

2. ENV-Datei vorbereiten

Die App liest zur Laufzeit (kein Build-Time-Inlining → ein Image für alle Mandanten). Lege auf dem Host /opt/<kunde>/app/.env an:

# ── Supabase ───────────────────────────────────────────
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon-key>
SUPABASE_SERVICE_ROLE_KEY=<service-role-key>

# ── Session (Pflicht, >= 32 Zeichen, pro Instanz EINMALIG erzeugen) ──
SESSION_SECRET=<openssl rand -base64 48>

# ── Öffentliche URL (für QR-Codes / vCard-Links) ───────
NEXT_PUBLIC_SITE_URL=https://team.kunde.de

# ── SMTP (optional; fehlt es, landen Magic-Links nur im Server-Log) ──
SMTP_HOST=mail.kunde.de
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=<user>
SMTP_PASS=<pass>
SMTP_FROM_EMAIL=noreply@team.kunde.de
SMTP_FROM_NAME=TeamVis

SESSION_SECRET erzeugen: openssl rand -base64 48. Niemals zwischen Mandanten teilen.

Self-hosted Supabase: next/image lädt Foto-URLs aus /storage/v1/object/public/** jedes HTTPS-Hosts — kein zusätzliches SUPABASE_IMAGE_HOSTS nötig (siehe next.config.ts).


3. Container deployen

/opt/<kunde>/app/docker-compose.yml:

services:
  app:
    image: git.zoesch.de/zfx-services/teamvis:0.11.5   # aktuelle Version pinnen
    container_name: <kunde>-teamvis
    restart: unless-stopped
    env_file: .env
    ports:
      - "127.0.0.1:3001:3000"   # Port pro Instanz eindeutig wählen
    healthcheck:
      test: ["CMD", "node", "-e", "fetch(\"http://127.0.0.1:3000/\").then(r => process.exit(r.status < 500 ? 0 : 1)).catch(() => process.exit(1))"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s
docker compose pull && docker compose up -d
docker compose logs -f app   # auf "Ready" warten

Reverse-Proxy (Caddy-Beispiel) auf den lokalen Port mappen — Caddy holt das TLS-Zertifikat automatisch:

team.kunde.de {
    reverse_proxy 127.0.0.1:3001
}

4. Ersten Admin anlegen

admin_invites setzen einen bestehenden Admin voraus — bei einer frischen Instanz gibt es keinen. Das Bootstrap-Script legt ihn direkt an (bcrypt cost 12, identisch zur App-Logik):

# vom lokalen Repo aus, ENV aus .env.local oder per dotenv-cli:
npx dotenv -e .env.local -- node scripts/create-admin.mjs chef@kunde.de --name "Max Chef"
# Passwort wird verdeckt abgefragt (min. 10 Zeichen)

Alternativ ENV direkt setzen (NEXT_PUBLIC_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY der Kunden-DB) und das Script ohne dotenv aufrufen. Danach Login unter https://team.kunde.de/admin/login. Weitere Admins/Viewer dann bequem über Admin → Einladungen im Backend.


5. Branding & Inhalte

Alles White-Label über das Backend, kein Code/Rebuild nötig:

  1. Admin → Branding — Firmenname, Logo, Favicon, Farben, Footer/Impressum-Link.
  2. Admin → Mitarbeiter — erste Karten anlegen (Foto, Kontaktdaten, Sichtbarkeits-Flags). Import per CSV möglich.
  3. Optional: Organigramm, Compliance-Frameworks, Telefonanlage, Wallet (Apple/Google) je nach Modul-Bedarf — Freischaltung siehe nächster Abschnitt.

5a. Funktionen gestaffelt freischalten

Eine frische Instanz startet bewusst minimal: nur die Visitenkarten-Basis (business_cards) ist aktiv. Alle weiteren Module schaltest du unter Admin → Funktionen nach und nach frei. Empfehlung für die Erstinstallation — erst die Basis abnehmen, dann Phase für Phase, damit bei Problemen die Fehlerquelle eng bleibt:

  1. Phase 1 — Go-Live-Kern: Organigramm, Compliance (keine externe Abhängigkeit).
  2. Phase 2 — Self-Service & Erfassung: Empfang, KI, Card-Scanner, NFC (braucht SMTP für Magic-Links bzw. einen AI-Provider unter Admin → AI-Provider).
  3. Phase 3 — Externe Systeme: Wallet, Telefonanlage, Webhooks (Zertifikate, 3CX, Webhook-Empfänger — höchstes Debug-Risiko, zuletzt).

Jedes Modul ist einzeln schaltbar; bei Bedarf feiner vorgehen. Bei gesetzter Lizenz ist diese die Obergrenze — nicht lizenzierte Module bleiben gesperrt.

Hinweis Bestandsinstanzen: Beim Update auf v0.12.0 muss auf bereits laufenden Instanzen docs/sql/0.12.0-enable-modules.sql vor dem Image-Deploy laufen (SQL-first), sonst verschwinden bislang aktive Module. Neuinstallationen sind nicht betroffen.


6. Smoke-Test (Abnahme)

  • https://team.kunde.de/<slug> zeigt eine Karte (öffentlich, ohne Login)
  • vCard-Download …/api/vcard/<slug> liefert .vcf
  • QR-Code auf der Karte zeigt auf die richtige NEXT_PUBLIC_SITE_URL
  • Admin-Login funktioniert, Mitarbeiterliste lädt
  • Self-Service-Portal: Magic-Link wird versendet (oder steht im Log, falls kein SMTP)
  • Foto-Upload landet im Bucket employee-photos und wird auf der Karte angezeigt
  • Security-Header gesetzt: curl -sI https://team.kunde.de | grep -i x-frame-options
  • Geheimnis-Check: mit dem anon-Key liefert eine Abfrage der Geheimnis-Spalten von site_settings permission denied

7. Updates & Versionsschema

TeamVis folgt Semantic Versioning (MAJOR.MINOR.PATCH):

Sprung Bedeutung Was zu tun ist
Patch (x.y.0 → x.y.1) Bugfix, kein Schema-Change Tag bumpen → docker compose pull && up -d
Minor (x.0 → x.1) Feature, Schema-Change möglich erst neue Migration einspielen, dann Container-Update
Major (1 → 2) Breaking Change Migration-Notes lesen, Backup, dann Update

Patch + Minor sind DB-rückwärtskompatibel (altes Image läuft mit neuer DB). Image-Tag in Production immer pinnen (teamvis:0.11.6), nicht :latest — sonst zieht ein compose pull ungewollt einen Major-Sprung.

Standard-Update:

# 1. Backup (siehe Abschnitt 8)
# 2. Release-Notes / CHANGELOG.md prüfen
# 3. fehlende Migration(en) aus supabase/migrations/ einspielen (nur die neuen!)
# 4. Tag im docker-compose.yml bumpen
docker compose pull && docker compose up -d --force-recreate

Rollback: Image-Tag im Compose zurücksetzen + up -d --force-recreate. Achtung: war eine nicht-rückwärtskompatible Migration dabei, muss zusätzlich der DB-Dump zurückgespielt werden — Daten zwischen Update und Rollback sind dann verloren. Darum der Pre-Update-Dump.


8. Backup & Aufbewahrung

Zwei kritische Bereiche: Postgres-DB (alle Datensätze, Audit-Log) und der Storage-Bucket employee-photos.

  • DB (Supabase Cloud): tägliche Auto-Backups + PITR ab Pro-Plan. Zusätzlich eigener Dump empfohlen:
    pg_dump "$DATABASE_URL" --no-owner --no-acl --format=custom > teamvis-$(date +%Y%m%d).dump
    
  • Storage: npx supabase storage cp "ss:///employee-photos" /backup/photos --recursive (self-hosted: Storage ist ein Verzeichnis → rsync/ZFS-Snapshot).
  • Audit-Log ist GoBD-relevant (10 Jahre Aufbewahrung): monatlich per Verwaltung → Protokoll → Export als CSV ins unveränderbare Langzeit-Archiv.
  • .env wird NICHT mitgesichert — Secrets gehören in einen Passwort-Safe.
  • Restore-Test mindestens jährlich (Dump in Test-DB, Container dagegen starten, Login + Karten prüfen). Ein nie-restored Backup ist kein Backup.

Bekannte Lücken / TODO

  • Kein automatischer Migration-Runner gegen self-hosted DBs ohne PG-Passwort → Schema-Bündel via Studio (Variante A) ist der dokumentierte Weg.
  • Vor aktivem Lizenz-Verkauf (Stripe live) sind Gewerbeanmeldung + USt nötig — privates Impressum trägt nur eine Info-/Showcase-Seite.