OAAP-Hilfe
oaap-docs szenarien/mandant-aus-profil-aufbauen · geprüft gegen 0.1.183 am 2026-10-03 · Original öffnen

Einen Mandanten aus einem Profil aufbauen

Geprüft gegen Referenz 0.1.183 (2026-10-03; die Seiten in Abschnitt 6 gegen 0.1.184 im Test, nicht im Browser; Abschnitt 7 und 8 gegen 0.1.187 nur in Tests, nicht an einem Knoten), auf oaap-test: Mandant, Richtlinie, Gesicht, App aus einem Paket, Wartepunkt, Fortsetzen nach einem hart beendeten Lauf, Rückbau, Portal-Aktion am echten Auftragsspeicher. Noch nicht an einem echten Knoten gemessen: die Schritte idp.provision (Realm), address.ensure und address.wait (echte Außenadresse) — oaap-test hat keinen Außennamen. Spezifikation: RFC-0055. Platzhalter: <kürzel> (Mandant), <profil> (Name der Profildatei ohne .json), <id> (Kennung eines Aufbaus).

Ergebnis: ein Mandant, wie ihn Einen Kunden-Mandanten mit eigenem Anmeldebereich einrichten von Hand baut — aber in einem Aufruf, mit sichtbarem Verlauf je Schritt, wiederholbar und nach einem Abbruch fortsetzbar.

Wann das Profil, wann von Hand?

1. Das Profil

Ein Profil ist eine kleine JSON-Datei in /var/lib/oaap/profiles/. Man legt sie auf dem Knoten ab oder lädt sie im Portal hoch (Abschnitt 8). Sie nennt Parameter (Werte, die beim Start gefragt werden) und eine geordnete Liste von Schritten.

{
  "profile": "oaap.tenant-profile/1",
  "id": "verein-standard",
  "title": "Verein mit eigenem Anmeldedienst",
  "params": {
    "label": {"kind": "label", "required": true},
    "title": {"kind": "text", "required": true, "max": 60},
    "color": {"kind": "color", "default": null}
  },
  "steps": [
    {"id": "tenant",  "type": "tenant.create", "label": "{label}", "title": "{title}"},
    {"id": "address", "type": "address.ensure"},
    {"id": "reachable", "type": "address.wait", "timeout": 120},
    {"id": "realm",   "type": "idp.provision", "connector": "auth",
                       "idp_label": "Mit dem Vereinskonto anmelden"},
    {"id": "policy",  "type": "tenant.policy", "first_login": "role",
                       "default_role": "user", "self_registration": "off"},
    {"id": "face",    "type": "tenant.face", "title": "{title}",
                       "color_primary": "{color}"},
    {"id": "web",     "type": "app.install", "source": "/srv/pakete/website-starter",
                       "name": "webseite", "channel": "test"},
    {"id": "admin",   "type": "manual",
                       "text": "Ersten Verwalter im Realm anlegen, dann im Portal tenant_admin vergeben.",
                       "done_when": "role.tenant_admin"},
    {"id": "backup",  "type": "backup.check"}
  ]
}

Die Schrittarten sind eine feste Liste. Ein Profil kann keinen eigenen Befehl enthalten; mit einer unbekannten Schrittart oder einem unbekannten Argument wird es ganz abgelehnt, bevor etwas läuft.

SchrittartTutGilt als erledigt, wenn …
tenant.createoaap tenant createder Mandant da ist und von diesem Aufbau angelegt wurde
address.ensureveröffentlicht die Adresse im Gatewaydie Gateway-Seiten den Mandantennamen nennen (ohne Außennamen: nichts zu tun)
address.waitwartet, bis https://<kürzel>.<knoten>/ antwortet200, 302 oder 303
idp.provisionRealm und Client im Anmeldedienst anlegen (oaap idp provision)der Mandant einen Anbieter eingetragen hat
tenant.policyoaap tenant policydie Richtlinie wie gewünscht gespeichert ist
tenant.faceoaap tenant face (Titel, Farben)die gespeicherten Werte stimmen
app.installoaap app install … — mit source (Pfad oder Adresse) oder app (ID aus einer Katalogquelle)die Instanz im Mandanten existiert
idp.admin_groupoaap idp admin-group <anbindung> --tenant <kürzel> (ab 0.1.233)die Gruppe oaap-verwalter steht mit ihren Rollen im Realm
manualnichts — ein Menschdie Lesung done_when stimmt (siehe 3.)
backup.checknichtsder Mandant im Knotenarchiv enthalten ist (nicht ausgenommen)

Ein Schritt gilt erst als erledigt, wenn seine Prüfung es bestätigt — nicht schon, weil der Befehl ohne Fehler zurückkam.

Werte sind Werte. {label} wird durch den Parameter ersetzt, nachdem er nach seiner Art geprüft wurde (label mit den Kürzel-Regeln, color als #rrggbb, text mit Längengrenze und ohne Steuerzeichen). Nichts davon wird je als Befehl gedeutet.

Apps wählbar machen. Ein Parameter der Art bool ist beim Start ein Haken ("mit_web": {"kind": "bool", "default": true, "label": "Mit Webseite"}). Ein Schritt mit "when": "mit_web" läuft nur, wenn der Haken gesetzt ist; sonst steht er im Verlauf als „übersprungen“ und braucht auch keinen seiner Parameter. So steuert ein Profil, welche Apps ein Mandant bekommt:

{"id": "web", "type": "app.install", "app": "webseite",
 "name": "webseite", "when": "mit_web"}

Der Mandantenname wird dem Instanznamen immer vorangestellt (hier entsteht <kürzel>-webseite); ihn noch einmal in name zu schreiben ergäbe <kürzel>-<kürzel>-webseite (gemessen 2026-10-18).

Eine App, die Rechte verwaltet (z. B. „Rollen & Rechte“, authorization.administer: true), braucht die Zusage des Server-Admins. Im Profil ist das derselbe Haken wie when:

"mit_rechte": {"kind": "bool", "default": false, "label": "Mit Rollen & Rechte"},
...
{"id": "rechte", "type": "app.install", "app": "rollen-rechte",
 "name": "rechte", "when": "mit_rechte", "confirm_administer": "{mit_rechte}"}

Der Haken beim Start installiert die App und ist die Zusage; im Formular steht daneben, was ihr Schlüssel kann und nie kann. Ein festes "true" lehnt der Knoten ab (ein Profil sagt nie im Voraus zu). Im Antragsformular für Interessenten erscheint der Haken nicht; nach einer Freigabe installiert der Server-Admin die App bei Bedarf aus dem Katalog (gemessen 2026-11-06, ab 0.1.232).

app ist die ID einer App aus einer eingerichteten Katalogquelle (Portal → Katalog). Der Knoten löst sie selbst auf, so wie der Ein-Klick- Einbau des Katalogs. Eine Quelle, die eine Bestätigung verlangt („ungeprüft“), bedient ein Profil nie: dort ist niemand, der bestätigt; der Schritt scheitert mit diesem Grund, und die App wird auf der Katalogseite eingebaut.

Apps erst beim Start auswählen (ab 0.1.236, RFC-0055 §15). Statt jede App fest ins Profil zu schreiben, wählt der Server-Admin sie beim Start per Haken aus dem Katalog. Dafür ein Parameter der Art apps und ein app.install, dessen app genau dieser Parameter ist; {app} steht darin für die jeweilige App:

"apps":      {"kind": "apps", "default": [], "label": "Apps aus dem Store"},
"with_test": {"kind": "bool", "default": true, "label": "Auch eine Test-Instanz"},
...
{"id": "test", "type": "app.install", "app": "{apps}", "name": "{app}-test",
 "channel": "test", "when": "with_test"},
{"id": "prod", "type": "app.install", "app": "{apps}", "name": "{app}",
 "channel": "production"}

Mit n8n und forgejo angehakt entstehen daraus vier Schritte (test-n8n, test-forgejo, prod-n8n, prod-forgejo) und die Instanzen <kürzel>-n8n-test, <kürzel>-n8n usw. Ohne choices bietet das Formular den ganzen Katalog des Knotens an (ohne Archiviertes); mit "choices": ["n8n", "forgejo", "vaultwarden"] nur diese. max begrenzt die Zahl (Standard 12). An der Kommandozeile: --param apps=n8n,forgejo.

Eine fertige Vorlage dafür: profile/kunde-standard.json (Mandant, optional eigener Realm im auth samt Verwaltergruppe, Apps nach Auswahl als Test und Produktiv, optional „Rollen & Rechte“, Wartepunkt erster Verwalter, Sicherung).

2. Aufbau starten

sudo oaap tenant build profiles
sudo oaap tenant build start <profil> --param label=<kürzel> \
     --param title="Klarname des Kunden" --dry-run
sudo oaap tenant build start <profil> --param label=<kürzel> \
     --param title="Klarname des Kunden"

Die Ausgabe zeigt jeden Schritt mit Zustand und einem Satz; sie endet mit DONE, WAITING oder FAILED.

sudo oaap tenant build show             # alle Aufbauten
sudo oaap tenant build show <id>        # einer, mit allen Schritten
sudo oaap tenant build show <id> --json # maschinenlesbar

Der Zustand liegt in data/tenant-builds/<id>.json (Modus 0600, ohne Geheimnisse). Profil und Werte sind mit einer Prüfsumme festgehalten.

3. Der Wartepunkt: der erste Verwalter

OAAP legt nie eine Person im Anmeldedienst des Kunden an. Den ersten Verwalter legt ein Mensch an (siehe Kunden-Mandanten einrichten, Abschnitt 4). Im Profil steht dafür ein Schritt manual; der Aufbau hält dort an (WAITING) und geht weiter, wenn die Lesung stimmt:

done_whenStimmt, sobald …
user.existses irgendein Konto in diesem Mandanten gibt
role.tenant_adminein Konto des Mandanten tenant_admin trägt
confirmedein Mensch es bestätigt hat
sudo oaap tenant build continue <id>                      # liest neu, geht weiter
sudo oaap tenant build confirm <id> --step <schritt>      # nur bei done_when "confirmed"

4. Fehler, Abbruch, Fortsetzen

Fortsetzen ist der Standard. Scheitert ein Schritt, steht sein Grund im Verlauf. Ursache beheben, dann:

sudo oaap tenant build continue <id>

Fertige Schritte werden nur neu gelesen, nicht wiederholt. Gemessen: ein Aufbau, dessen Prozess hart beendet wurde (kill -9), blieb im Zustand running und wurde ohne doppelte Arbeit fortgesetzt.

BefundWas Sie tun
Ein Schritt ist failedUrsache beheben, continue
continue sagt „the profile file changed … digest differs“die Datei wiederherstellen — oder den Aufbau zurückbauen und neu starten
ein fertiger Schritt ist „no longer in place“jemand hat das Ergebnis entfernt; der Aufbau hält an, statt es still neu zu machen. Entscheiden, dann zurückbauen oder von Hand ergänzen
„this build is being driven by another process“es läuft wirklich einer; ein beendeter Prozess gibt die Sperre sofort frei
der Aufbau für das Kürzel ist „not finished“continue oder rollback des offenen Aufbaus, dann neu starten

5. Zurückbauen

sudo oaap tenant build rollback <id>          # nennt nur, was geschähe
sudo oaap tenant build rollback <id> --yes    # tut es

Zurückgebaut wird nur, was dieser Aufbau selbst angelegt hat, in umgekehrter Reihenfolge:

Ein Rückbau, der nicht fertig wird, bleibt FAILED mit dem Rest; continue macht ihn zu Ende.

6. Aus dem Portal und über die API

Dieselbe Mechanik steht als Portal-Aktion und als Betreiber-API bereit: /api/v1/operator/tenant-profiles, …/tenant-builds, …/tenant-builds/<id> (lesen), POST …/tenant-builds mit {"profile": "…", "params": {…}} und POST …/tenant-builds/<id>/continue|confirm|rollback.

Wer einen Mandanten beantragen soll, bekommt von dir einen Einladungslink und füllt ein Formular aus. Daraus entsteht ein Antrag; gebaut wird erst, wenn du ihn freigibst (Referenz 0.1.186, RFC-0055 Stufe 4).

  1. Portal → Aufbau → Einladungen und Anträge → Neue Einladung: Profil wählen, eine Notiz nur für dich („Handballverein Nord“), Gültigkeit in Tagen (1 bis 60, vorgegeben 14). Die Antwort zeigt den Link ein einziges Mal. Der Knoten speichert nur einen Prüfwert davon; später kann man den Link nicht noch einmal ansehen, nur eine neue Einladung ausstellen.
  2. Den Link nur an diese eine Person geben, in einem Kanal, den ihr beide lest. Er ist einmal benutzbar, läuft ab und lässt sich unter Einladungen mit Widerrufen sofort ungültig machen.
  3. Der Interessent öffnet den Link (ohne Anmeldung) und gibt alle Parameter des Profils an (Kürzel, Name, Farbe …) und eine E-Mail-Adresse. Das Kürzel wird Teil der Internetadressen und ist öffentlich; die Seite sagt es ihm. Danach ist der Link verbraucht.
  4. Der Antrag steht bei dir unter Anträge mit seinen Angaben und der Adresse. Freigeben und aufbauen startet den Aufbau wie unter „Aufbau starten“, mit deiner Anmeldung; Ablehnen (mit Grund, wenn du willst) löscht die Adresse sofort. Geht die Freigabe nicht (das Kürzel ist inzwischen vergeben, das Profil fehlt), bleibt der Antrag offen und die Meldung sagt warum.
  5. Den ersten Verwalter richtest du wie sonst im Wartepunkt ein (Abschnitt 3); die Adresse des Antrags steht dafür weiter in der Liste.

Was nicht geschieht: der Interessent bekommt keine E-Mail von OAAP, und er erfährt auf der Seite nicht, ob sein Kürzel schon vergeben ist — das sagt dir die Meldung bei der Freigabe, und du antwortest ihm. Entschiedene Anträge verschwinden nach 30 Tagen (mit der Adresse darin); eine Einladung, die nicht mehr offen ist, nach 7 Tagen.

Gut zu wissen: der Link hat die Form …/anfrage?t=<Zeichenfolge>. Das Zugriffsprotokoll des Gateways schneidet bei jeder Anfrage alles ab dem ? ab, der Link bleibt dort also nicht stehen; in der Verlaufsliste des Browsers des Interessenten steht er schon. Nach der ersten Benutzung ist er wertlos; einen noch offenen, den jemand verloren hat, widerrufst du unter Einladungen.

8. Profile im Portal hochladen, herunterladen, löschen

Portal → Aufbau (Referenz 0.1.187, nur server_admin am Knoten selbst):

Jede Änderung steht im Mandantenprotokoll des Standard-Mandanten als tenant.profile.put / tenant.profile.delete.

Protokoll

Jeder Schritt steht im Mandantenprotokoll (oaap tenant log <kürzel>) und im Protokoll des Standard-Mandanten; ein Kunde sieht dort, was in seinem Namen getan wurde. Zeilen: tenant.build.step, tenant.build.done, tenant.build.rollback; abgelehnte Anfragen des Portals als tenant.build.denied; abgelehnte Aufträge zu Einladungen und Anträgen als tenant.request.denied.

Prüfliste