# Eine App für die GenAI-Plattform bauen So baust du heute ein App-Paket, das die Plattform selbst ausrollen und verdrahten kann: ein Git-Repository mit einem Terraform-Modul und einem Ordner `genai-cli/`. Darin steht, was die App braucht (Redis, LiteLLM, Datenbank, Admin-Zugang, Logo) – den Rest erledigt der Deployment-Workflow. Stand: 13.10.2026 > [!NOTE] Was dieses Dokument ist > Die Anleitung beschreibt das Format, das die Plattform **heute** einliest. Eine Einreich-API gibt es noch nicht; neue Apps kommen über das Plattform-Team in den Katalog ([Übergabe](#uebergabe)). Ein einfacheres Paketformat ist in Arbeit – wenn es kommt, wird diese Seite fortgeschrieben, nicht ersetzt. > [!TIP] Für KI-Assistenten > Diese Seite gibt es auch als reines Markdown ([app-entwicklung.md](app-entwicklung.md)) und als Einstieg [llms.txt](llms.txt). Die harten Regeln stehen kompakt im Abschnitt [Regeln für KI-Assistenten](#ki-regeln). Eine lauffähige Vorlage liegt unter [vorlage/](vorlage/). ## Auf einen Blick {#ueberblick} Eine App ist heute ein **Modul-Repository** in der GitLab-Gruppe `genai-plattform/applications`. Die Plattform liest daraus den Ordner `genai-cli/`, erzeugt pro Umgebung die Terragrunt-Dateien und rollt aus.
1 · DuModul-Repo mit genai-cli/, SemVer-Tag
→
2 · Plattform-Teamimportiert das Repo in den App-Katalog
→
3 · BestellungApp wird für eine Umgebung gewählt, Abhängigkeiten kommen mit
→
4 · WorkflowSecrets erzeugen, Dateien rendern, Terragrunt ausführen
Was die Plattform für dich übernimmt: - **Abhängigkeiten** – brauchst du Redis, wird Redis mit ausgerollt und vor deiner App angewendet. - **Zugangsdaten** – Redis-Passwort, LiteLLM-Schlüssel, Datenbank, S3 und eigene Secrets werden erzeugt, verschlüsselt (SOPS) abgelegt und deiner App als Eingabe übergeben. - **Domain, TLS, Ingress-Klasse, Pull-Secrets** – kommen aus der Umgebung. - **Admin-Zugang** – die Umgebung hat einen Admin (E-Mail + Passwort), den deine App übernehmen kann. - **Katalog-Kachel** – Name, Beschreibung und Logo erscheinen im Katalog und im AI Hub. - **Pflicht-Labels** – `genai.govdigital.de/*` setzt die Plattform selbst. ## Aufbau des Repositorys {#repo}
meine-app/ ├── main.tf # Terraform-Modul: meist ein helm_release ├── variables.tf # Eingaben, die die Plattform liefert ├── outputs.tf ├── terraform.tf # required_providers, keine provider-Blöcke ├── chart/ # eigenes Helm-Chart (optional, oder Chart aus Repo) └── genai-cli/ # ← das liest die Plattform ├── terragrunt.hcl.genai # Pflicht: wie das Modul in der Umgebung aufgerufen wird ├── metadata.json # Name, Beschreibung, Katalogtexte ├── logo.svg # oder logo.png ├── config.domains.hcl.genai # öffentliche Adresse ├── config.hosts.hcl.genai # Adresse im Cluster ├── config.secrets.hcl.genai # eigene Secrets, Admin-Login ├── config.litellm.hcl.genai # LiteLLM-Schlüssel ├── config.databases.hcl.genai # verwaltetes Postgres ├── config.s3_buckets.hcl.genai # verwalteter S3-Bucket └── config.versions.hcl.genai # App- und Chart-Version
| Datei | Pflicht | Wofür | |---|---|---| | `terragrunt.hcl.genai` | ja | Aufruf des Moduls: Quelle, Abhängigkeiten, Eingaben. Ohne sie ist das Repo nicht importierbar. | | `metadata.json` | praktisch ja | Name, Beschreibung, Maintainer, Katalogtexte, Versionsquellen. | | `logo.svg` / `logo.png` | für Partner ja | Kachel im Katalog und AI Hub. Ohne Logo sehen Partner die App nicht. | | `config.domains.hcl.genai` | für Web-Apps | Öffentliche Adresse. Ohne sie keine Kachel im AI Hub. | | `config.hosts.hcl.genai` | empfohlen | Interne Adresse, damit andere Apps dich erreichen. | | `config.secrets.hcl.genai` | bei Bedarf | Secrets, die die Plattform erzeugen soll (z. B. Admin-Passwort). | | `config.litellm.hcl.genai` | wenn die App ein LLM nutzt | Eigener LiteLLM-Schlüssel. | | `config.databases.hcl.genai` | wenn die App Postgres braucht | Verbindungsdaten der verwalteten Datenbank. | | `config.s3_buckets.hcl.genai` | wenn die App S3 braucht | Verbindungsdaten des Buckets. | | `config.versions.hcl.genai` | empfohlen | App- und Chart-Version an einer Stelle. | Jede `.genai`-Datei darf auch ohne Endung heißen (`terragrunt.hcl`); die Plattform sucht zuerst die Variante mit `.genai`. ## Schnellstart mit der Vorlage {#schnellstart} Schnellster Weg: im [Baukasten](#baukasten) Schlüssel und Bausteine wählen und das fertige Repo als ZIP laden. Von Hand geht es so: 1. Den Ordner [vorlage/](vorlage/) in ein neues Repo kopieren. 2. Überall `meine-app` und `meine_app` durch deinen Schlüssel ersetzen ([Regeln](#schluessel)). 3. Nicht benötigte `config.*`-Dateien löschen und die passenden Zeilen in `terragrunt.hcl.genai` entfernen. 4. `main.tf` auf deine App anpassen, Logo austauschen, `metadata.json` ausfüllen. 5. Lokal prüfen ([Lokal prüfen](#lokal)), SemVer-Tag setzen, an das Plattform-Team übergeben ([Übergabe](#uebergabe)). ```bash title="Vorlage übernehmen" cp -R docs/app-entwicklung/vorlage ../meine-wissensdatenbank cd ../meine-wissensdatenbank grep -rl 'meine[-_]app' . | xargs sed -i '' -e 's/meine-app/wissensdatenbank/g' -e 's/meine_app/wissensdatenbank/g' ./pruefen.sh ``` ## Der Schlüssel der App {#schluessel} Der Schlüssel ist der feste Name der App, zum Beispiel `open-webui`. Er lässt sich nach dem Import nicht mehr ändern. - Nur Kleinbuchstaben, Ziffern und Bindestrich, höchstens 40 Zeichen, nicht mit `-` beginnen oder enden. - Reserviert: `apps`, `import`, `new`. - Der Import nimmt den einzigen Schlüssel aus `config.hosts`/`config.domains`, sonst den Repo-Namen ohne `terraform-`/`-terraform`. Die Plattform schreibt den Schlüssel in zwei Formen. Das ist die häufigste Fehlerquelle: | Wo | Schreibweise | Beispiel | |---|---|---| | Katalog, Ordner in der Umgebung, `dependencies` | mit Bindestrich | `open-webui` | | Schlüssel in allen `config.*`-Dateien und in `inputs.*.` | mit Unterstrich | `open_webui` | | Abschnitt in `config.secrets.hcl.genai` und `secrets.` | mit Unterstrich | `secrets.open_webui.admin_password` | ## `terragrunt.hcl.genai` {#terragrunt} Die Datei wird pro Umgebung in zwei Schritten verarbeitet: 1. **Jinja** (`{{ … }}`) – beim Erzeugen der Umgebung. Verfügbar sind `source` (die Modulquelle mit Tag) und jeder Wert aus den Katalog-Standardwerten (`defaultValues`). Immer mit `default(…)` schreiben. 2. **Terragrunt/HCL** (`${ … }`, `try(…)`) – beim Ausrollen. Hier liest du alles, was die Umgebung bereitstellt, aus `local.common_vars.inputs`. ```hcl title="genai-cli/terragrunt.hcl.genai" include "root" { path = find_in_parent_folders("root.hcl") } # Provider kommen von der Plattform. kubernetes.hcl ignoriert die Pflicht-Labels. include "kubernetes" { path = "${get_terragrunt_dir()}/../providers/kubernetes.hcl" } include "helm" { path = "${get_terragrunt_dir()}/../providers/helm.hcl" } terraform { source = "{{ source | default('../../../../../terraform-meine-app') }}" } # Was vor dieser App angewendet sein muss. Daraus wird im Katalog "dependsOn". dependencies { paths = ["../redis", "../../applications/litellm"] } locals { common_vars = read_terragrunt_config(find_in_parent_folders("terragrunt.hcl")) } inputs = merge( local.common_vars.inputs, { release_name = "meine-app" domain = try(local.common_vars.inputs.domains.meine_app, "") replicas = {{ replicas | default(1) }} # Redis – vom Redis-Modul der Umgebung redis_host = try(local.common_vars.inputs.databases.redis.host, "") redis_port = try(local.common_vars.inputs.databases.redis.port, "6379") redis_password = try(local.common_vars.inputs.databases.redis.password, "") # LiteLLM – OpenAI-kompatibel, eigener Schlüssel aus config.litellm.hcl.genai openai_base_url = "http://${try(local.common_vars.inputs.hosts.litellm, "localhost")}:4000/v1" openai_api_key = try(local.common_vars.inputs.litellm.meine_app.key, "") # Admin – der Admin der Umgebung admin_email = try(local.common_vars.inputs.default_user_email, "") admin_password = try(local.common_vars.inputs.default_user_password, "") } ) ``` > [!IMPORTANT] Immer `try(…, default)` > Zugangsdaten für Datenbank und S3 stehen erst nach dem ersten Secret-Abgleich in der Umgebung. Ein nackter Zugriff wie `inputs.databases.meine_app.password` bricht dann schon den Plan ab. `try()` hält ihn lauffähig. ### Was die Umgebung bereitstellt {#inputs} Alles unter `local.common_vars.inputs`. Weil `inputs = merge(local.common_vars.inputs, {...})` alles weiterreicht, kann dein Modul diese Namen auch direkt als Terraform-Variable deklarieren. Was es nicht deklariert, ignoriert Terraform. | Eingabe | Inhalt | |---|---| | `namespace`, `release_name_prefix` | Kubernetes-Namespace der Umgebung und Präfix für Release-Namen | | `domain`, `environment`, `organisation` | Basis-Domain, Umgebungsname, Organisation | | `ingress_class_name`, `cluster_issuer_name`, `certificates` | Ingress-Klasse, cert-manager-Issuer, Zertifikate | | `storage_class` | Storage-Klasse für Volumes | | `image_pull_secrets`, `registry_credentials` | Namen der Pull-Secrets, Registry-Zugänge | | `domains.`, `hosts.` | öffentliche und interne Adressen aller Apps der Umgebung | | `databases.`, `s3_buckets.` | Verbindungsdaten (auch `databases.redis`) | | `litellm..key` | LiteLLM-Schlüssel je App | | `secrets..` | eigene Secrets aus `config.secrets.hcl.genai` | | `versions.`, `helm_chart_versions.` | Versionen aus `config.versions.hcl.genai` | | `default_user_email`, `default_user_first_name`, `default_user_password` | Admin der Umgebung | | `smtp_host`, `smtp_port`, `smtp_from`, `smtp_from_name`, `smtp_username`, `smtp_password` | Mailversand, leer wenn die Umgebung keinen hat | | `http_proxy`, `https_proxy`, `no_proxy` | Proxy, meist leer | | `models` | Modell-Liste der Umgebung | ### Regeln für das Terraform-Modul {#modul} - **Keine `provider`-Blöcke und kein `backend`.** Beides erzeugt die Plattform. Eine Datei `providers.tf` wird beim Ausrollen leer überschrieben. - **Helm-Provider `~> 2.0`.** Die erzeugte Provider-Konfiguration nutzt die Syntax von Version 2. - **`k8s_config_path` und `k8s_context` nicht deklarieren** – die Plattform deklariert sie selbst. - **Nichts außerhalb des Namespace** `var.namespace` anlegen; Cluster-Ressourcen gehören nicht in ein App-Modul. - **Keine eigenen `genai.govdigital.de/*`-Labels** und nie Selektoren oder Pod-Template-Labels dafür ändern. - **Release- und Workload-Namen enthalten den Schlüssel** (`${release_name_prefix}meine-app`). Daran ordnet das Monitoring Pods der App zu. ## Abhängigkeiten (`dependsOn`) {#abhaengigkeiten} Du erklärst eine Abhängigkeit an **einer** Stelle: im `dependencies`-Block von `terragrunt.hcl.genai`. Daraus leitet der Import das Katalogfeld `dependsOn` ab. ```hcl title="Abhängigkeit auf Redis und LiteLLM" dependencies { paths = ["../redis", "../../applications/litellm"] } ``` Was dann passiert: 1. **Beim Bestellen** wählt die Plattform jede Abhängigkeit automatisch mit, auch über mehrere Stufen (deine App → LiteLLM → Redis, Presidio). 2. **Beim Erzeugen** bekommt die Abhängigkeit ihre Secrets (z. B. das Redis-Passwort) und schreibt ihre Verbindungsdaten in die Umgebung. 3. **Beim Ausrollen** wendet Terragrunt die Abhängigkeit vor deiner App an. 4. **Deine App** liest die Verbindungsdaten über `local.common_vars.inputs` – nicht über Terragrunt-Outputs. Regeln: - Der letzte Pfadteil ist der **Katalogschlüssel** der Abhängigkeit (`redis`, `litellm`). Ist er nicht im Katalog, blockiert der Import. Eine eigene Abhängigkeit (z. B. eine zweite App von dir) muss deshalb zuerst importiert werden. - `../name` heißt „gleiche Stufe“, `../../applications/name` „andere Stufe“. Liegt die Abhängigkeit woanders, korrigiert die Plattform den Pfad selbst. - Verweist deine Datei auf `../../…`, landet die App in der Stufe `ai-applications` (nach `applications`). Das ist richtig für Apps, die LiteLLM nutzen. - Postgres ist **keine** Abhängigkeit, sondern eine verwaltete Datenbank ([Datenbank und S3](#datenbank)). | Abhängigkeit | Pfad | Was deine App bekommt | |---|---|---| | Redis | `../redis` | `inputs.databases.redis.{host,port,password}` | | LiteLLM | `../../applications/litellm` | `inputs.hosts.litellm`, `inputs.domains.litellm`, eigener Schlüssel über `config.litellm.hcl.genai` | | Presidio | `../presidio` | `inputs.hosts.presidio_analyzer`, `inputs.hosts.presidio_anonymizer` | | eine andere App | `../` | was diese App in `config.hosts`/`config.domains` veröffentlicht | > [!WARNING] Nachinstallieren in eine laufende Umgebung > Bestellung und Assistent ziehen Abhängigkeiten automatisch mit. Fügt ein Operator eine App einzeln einer bestehenden Umgebung hinzu, passiert das heute nicht – die Abhängigkeit muss dort schon laufen. ## Redis anbinden {#redis} 1. Abhängigkeit eintragen: `"../redis"` im `dependencies`-Block. 2. Verbindungsdaten lesen. Das Redis-Modul der Umgebung veröffentlicht sie unter `databases.redis`: ```hcl title="terragrunt.hcl.genai – Redis" redis_host = try(local.common_vars.inputs.databases.redis.host, "") redis_port = try(local.common_vars.inputs.databases.redis.port, "6379") redis_password = try(local.common_vars.inputs.databases.redis.password, "") ``` 3. Im Modul daraus die URL bauen, die deine App erwartet: ```hcl title="main.tf – Redis-URL für die App" locals { redis_url = "redis://:${var.redis_password}@${var.redis_host}:${var.redis_port}/0" } ``` - `host` ist der Service im Namespace (`redis-master.`); wer einen voll qualifizierten Namen braucht, hängt `.svc.cluster.local` an. - Das Passwort erzeugt die Plattform, sobald Redis in der Umgebung ist. Du musst nichts dafür deklarieren. - Teile dir Redis mit anderen Apps: nimm eine eigene Datenbanknummer oder ein Schlüsselpräfix. > [!NOTE] Ältere Schreibweise > Ältere Module lesen `inputs.hosts.redis` und `inputs.redis_password`. Beides funktioniert noch; für neue Apps gilt `inputs.databases.redis.*`. ## LiteLLM anbinden {#litellm} Deine App spricht mit LiteLLM wie mit der OpenAI-API. Sie bekommt einen eigenen Schlüssel mit eigenem Team, damit Nutzung und Kosten getrennt sichtbar sind. 1. Abhängigkeit eintragen: `"../../applications/litellm"`. 2. Schlüssel anmelden mit `config.litellm.hcl.genai`: ```hcl title="genai-cli/config.litellm.hcl.genai" include "global" { path = find_in_parent_folders("global.hcl") expose = true } locals { litellm = { meine_app = { team_alias = "meine-app-team" key_alias = "meine-app-api-key" create_api_key = true models = [] # leer = alle Modelle der Umgebung metadata = { service = "meine-app" } key = try(include.global.locals.secrets.litellm.keys.meine_app, "") } } } ``` 3. Schlüssel und Adresse übergeben: ```hcl title="terragrunt.hcl.genai – LiteLLM" openai_base_url = "http://${try(local.common_vars.inputs.hosts.litellm, "localhost")}:4000/v1" openai_api_key = try(local.common_vars.inputs.litellm.meine_app.key, "") ``` - Die Zeile `key = try(include.global.locals.secrets.litellm.keys., "")` ist Pflicht und muss genau so heißen: an ihr erkennt die Plattform, dass sie einen `sk-…`-Schlüssel erzeugen und in LiteLLM anlegen soll. - Nimm die interne Adresse (`hosts.litellm`, Port 4000). Die öffentliche (`https://${inputs.domains.litellm}`) geht auch, läuft aber über den Ingress. - Welche Modelle es gibt, steht in `inputs.models`. Modellnamen besser als Katalog-Standardwert (`{{ model_chat | default('…') }}`) statt fest im Code. ## Datenbank und S3 {#datenbank} Postgres und S3 sind **verwaltet**: die Plattform legt Datenbank bzw. Bucket in der Infrastruktur-Stufe an und schreibt die Zugangsdaten in die Secrets der Umgebung. Deine App bekommt sie über zwei Dateien. ```hcl title="genai-cli/config.databases.hcl.genai" locals { databases = { meine_app = { host = try(include.global.locals.secrets.database.meine_app.host, "localhost") port = try(include.global.locals.secrets.database.meine_app.port, "5432") name = try(include.global.locals.secrets.database.meine_app.name, "${include.global.locals.environment_name}-meine-app") username = try(include.global.locals.secrets.database.meine_app.username, "${include.global.locals.environment_name}_meine_app") password = try(include.global.locals.secrets.database.meine_app.password, "") } } } ``` ```hcl title="genai-cli/config.s3_buckets.hcl.genai" locals { s3_buckets = { meine_app = { bucket = try(include.global.locals.secrets.s3_buckets.meine_app.bucket, "meine-app") endpoint = try(include.global.locals.secrets.s3_buckets.meine_app.endpoint, "") region = try(include.global.locals.secrets.s3_buckets.meine_app.region, "eu01") force_path_style = false username = try(include.global.locals.secrets.s3_buckets.meine_app.access_key, "") secret_key = try(include.global.locals.secrets.s3_buckets.meine_app.secret_key, "") } } } ``` In `terragrunt.hcl.genai` liest du dann `inputs.databases.meine_app.{host,port,name,username,password}` und `inputs.s3_buckets.meine_app.{bucket,endpoint,region,username,secret_key}`. - Der Import erkennt die Datenbank nur, wenn `config.databases` auf `secrets.database.` verweist. Dann trägt er `requires.databases` im Katalog ein. - Im S3-Abschnitt heißt der Access-Key **`username`** – so erwartet es die Bucket-Bereitstellung. Lies ihn als `inputs.s3_buckets..username`. - Eine App, die Postgres selbst mitbringt (eigener StatefulSet), braucht beides nicht – dann ist das Passwort ein eigenes Secret ([Eigene Secrets](#secrets)). ## Admin-Zugang und eigene Secrets {#secrets} ### Der Admin der Umgebung Jede Umgebung hat einen Admin: E-Mail, Vorname, Passwort. Der Partner gibt die E-Mail bei der Bestellung an, das Passwort erzeugt die Plattform. Übernimm diesen Admin, statt einen eigenen zu erfinden – dann meldet sich der Partner überall gleich an. ```hcl title="terragrunt.hcl.genai – Admin übernehmen" admin_email = try(local.common_vars.inputs.default_user_email, "") admin_first_name = try(local.common_vars.inputs.default_user_first_name, "Administrator") admin_password = try(local.common_vars.inputs.default_user_password, "") ``` Dein Modul legt damit beim ersten Start den Admin an (Bootstrap-Variablen, Init-Job oder Helm-Hook – je nach App). ### Eigene Secrets erzeugen lassen Braucht deine App weitere Secrets (Signaturschlüssel, Admin-Benutzername, interne Passwörter), meldest du sie in `config.secrets.hcl.genai` an. Die Plattform erzeugt fehlende Werte beim Anlegen der Umgebung und überschreibt nie einen vorhandenen. ```hcl title="genai-cli/config.secrets.hcl.genai" locals { required_secrets = { meine_app = { admin_username = { type = "static" default = "admin" description = "Login-Name des Admins" } admin_password = { type = "password" length = 32 description = "Passwort des Admins" } session_secret = { type = "hex" length = 32 description = "Signiert Sitzungen" } } } } ``` Lesen in `terragrunt.hcl.genai`: ```hcl title="terragrunt.hcl.genai – eigene Secrets" admin_username = try(local.common_vars.inputs.secrets.meine_app.admin_username, "admin") admin_password = try(local.common_vars.inputs.secrets.meine_app.admin_password, "") session_secret = try(local.common_vars.inputs.secrets.meine_app.session_secret, "") ``` | `type` | erzeugt | Felder | |---|---|---| | `password` | Buchstaben und Ziffern | `length` (Standard 32) | | `hex` | Hex-Zeichenkette, 2 Zeichen je Byte | `length` in Bytes (Standard 32) | | `fernet` | Fernet-Schlüssel (44 Zeichen) | – | | `prefixed` | `-` | `prefix`, `length` (Standard 24) | | `static` | genau `default` | `default` – ohne `default` bleibt der Wert leer | > [!WARNING] `admin_password` erbt das Admin-Passwort > Heißt ein Secret `admin_password`, bekommt es das Passwort des Admins der Umgebung statt eines Zufallswerts. Das ist gewollt (ein Login für alle Apps) und **vorläufig**: es entfällt, sobald Konten zentral über den AI Hub vergeben werden. Willst du ein unabhängiges Passwort, nenne es anders. - Zugangsdaten nie in `metadata.json`, Katalog-Standardwerte oder Klartext-Dateien schreiben. Secrets gehören in `config.secrets.hcl.genai`. - Ein Secret, das der Partner selbst setzen muss (z. B. ein externer API-Schlüssel), meldest du als `static` ohne `default` an und beschreibst es in `description`. Das Plattform-Team trägt es dann ein. ## Adressen: Domain und Host {#adressen} ```hcl title="genai-cli/config.domains.hcl.genai" locals { domains = { meine_app = "meine-app-${include.global.locals.domain}" } } ``` ```hcl title="genai-cli/config.hosts.hcl.genai" locals { hosts = { meine_app = "${include.global.locals.release_name_prefix}meine-app.${include.global.locals.k8s_namespace}" } } ``` - `domains.` ist die öffentliche Adresse. Dein Ingress nutzt sie zusammen mit `ingress_class_name` und dem Issuer `cluster_issuer_name`. - `hosts.` muss zum Namen deines Kubernetes-Service passen. Andere Apps erreichen dich darüber. - `include.global.locals.*` darfst du in allen `config.*`-Dateien nutzen; fehlt der `include "global"`-Block, ergänzt ihn die Plattform. ## `metadata.json` und Katalogtexte {#metadata} ```json title="genai-cli/metadata.json" { "name": "Meine App", "description": "Ein Satz, was die App tut.", "maintainers": ["team-meine-app"], "catalog": { "label": "Meine App", "summary": "Ein Satz für die Katalog-Kachel, ohne Zeilenumbruch.", "details": "Ein bis drei Absätze für die Detailseite.\n\nNur Klartext, kein Markdown.", "vendor": "Hersteller (Open Source)", "category": "ui", "role": "application", "defaultValues": { "replicas": 1, "model_chat": "stackit-openai-gpt-oss-120b" } }, "versions": { "meine_app": { "description": "Image der App", "source": { "type": "registry", "host": "registry.onstackit.cloud", "repo": "govdigital/meine-app", "tagPrefix": "v", "limit": 50 } } } } ``` | Feld | Bedeutung | |---|---| | `name`, `description`, `maintainers` | Grunddaten; `description` ohne `catalog.summary` wird zur Kurzbeschreibung | | `catalog.label` | Anzeigename | | `catalog.summary` | ein Satz, eine Zeile – Pflicht für Partner | | `catalog.details` | Klartext-Absätze; keine Zeichen `<` `>` `*` `#` und kein Backtick – Pflicht für Partner | | `catalog.vendor` | Hersteller – Pflicht für Partner | | `catalog.category` | `core`, `observability`, `ui`, `tooling` oder `data` – Pflicht, ohne Kategorie lehnt der Katalog den Eintrag ab | | `catalog.role` | `application` (Nutzer sehen sie) oder `service` (Baustein für andere Apps, z. B. Redis) | | `catalog.tier` | `applications` oder `ai-applications`; sonst aus den Abhängigkeitspfaden abgeleitet | | `catalog.defaultValues` | Jinja-Werte für `terragrunt.hcl.genai`; Platzhalter `${env}`, `${org}`, `${envName}` werden ersetzt | | `versions..source` | woher die Konsole verfügbare Versionen liest: `registry`, `github-release` oder `gitlab-release` mit `host`, `repo`, `tagPrefix`, `stripPrefix`, `limit` | | `helmChartVersions.` | dasselbe für die Chart-Version | ## Logo {#logo} Lege das Logo in `genai-cli/` ab. Die Plattform sucht in dieser Reihenfolge: `logo.svg`, `logo.png`, `logo.jpg`, `logo.jpeg`. | Regel | Wert | |---|---| | Formate | SVG (bevorzugt), PNG, JPEG | | Größe | höchstens 1 MiB im Repo | | Rasterbilder | quadratisch, am besten 256 × 256 px; größere werden auf 256 px verkleinert | | SVG | höchstens 128 KiB, kein `