# Startklar `.klar.yml` – Referenz v1

Startklar ist eine Extension für [mittwald mStudio](https://www.mittwald.de/mstudio). Eine Datei `.klar.yml` im Projekt beschreibt, was ein Projekt braucht: App, Datenbanken, Container, Cronjobs, Mail, Backups und Domains. Startklar richtet das im mStudio ein und hält es laufend so, wie es in der Datei steht.

Diese Seite beschreibt Version 1 des Dateiformats (`version: 1`). Sie liegt dauerhaft unter **https://startklar.cyber.house/v1/**, das JSON Schema unter **https://startklar.cyber.house/v1/schema.json**.

> **Status:** Startklar ist in Entwicklung. Das Format ist festgelegt und wird geprüft, das Einrichten im mStudio entsteht gerade. Fragen, Fehler oder Wünsche: [Kontakt](https://www.cyberhouse.at/contact/).

## Schnellstart

1. Startklar in der Organisation im mStudio installieren und im Projekt aktivieren.
2. Eine `.klar.yml` ins Repository legen und deployen, z.B. nach `~/html/prod/current/`.
3. Den neuen Fundort im Startklar-Menü des Projekts freigeben. Ab dann richtet Startklar alles ein und gleicht Änderungen automatisch ab.

Die kleinste gültige Datei:

```yaml
# Startklar config — docs: https://startklar.cyber.house/v1/
# $schema: https://startklar.cyber.house/v1/schema.json
version: 1
```

Die zwei Kommentarzeilen sind optional, aber empfohlen: Die erste zeigt Menschen und KI-Agents, wo die Doku liegt, die zweite schaltet im Editor Autovervollständigung, Erklärungen beim Hovern und Prüfung ein – in VS Code und Cursor mit der YAML-Extension von Red Hat, in PhpStorm direkt.

Was eine Datei anlegt, zeigt der [Editor](https://startklar.cyber.house/editor/): Datei einfügen, und daneben erscheinen Apps, Umgebungen, Datenbanken, Container und Domains – geprüft mit demselben Parser wie in der Extension.

## Wie Startklar arbeitet

- **Fundorte:** Startklar sucht an diesen Stellen:
  - `~/html/.klar.yml`
  - `~/html/*/.klar.yml`
  - `~/html/*/current/.klar.yml` (Deployments mit Releases, z.B. Magallanes oder Deployer)
  - zusätzlich in allen Verzeichnissen, die eine gefundene Datei unter [`targets`](#target) angibt, auch tiefer, jeweils mit und ohne `current`
- **Zeitplan:** Bekannte Dateien prüft Startklar alle 15 Sekunden auf Änderungen; nur wenn sich eine geändert hat, wird abgeglichen. Nach neuen Fundorten sucht es alle 5 Minuten – und sofort, wenn das Startklar-Menü im Projekt geöffnet wird.
- **Fehler beim Anwenden:** Schlägt bei mittwald etwas fehl, versucht Startklar es selbst erneut, mit wachsendem Abstand. Den Stand zeigt das Menü.
- **Freigabe:** Ein neuer Fundort wird erst eingerichtet, wenn er im Startklar-Menü des Projekts freigegeben wurde.
- **Fehlerhafte Datei:** Ist die Datei ungültig, ändert Startklar gar nichts und zeigt die Fehler mit Zeilennummer im Projekt-Menü.
- **Fehlende Abschnitte:** Was nicht in der Datei steht, fasst Startklar nicht an. Ohne `backups` bleiben z.B. die Backup-Einstellungen des Projekts, wie sie sind.

Alles außer `version` ist optional.

## apps

Die Apps an diesem Fundort, nach frei wählbarem Namen (`a-z`, `0-9`, `-`). Meist ist es eine, z.B. `web`. Ein Repository kann aber auch mehrere enthalten, etwa ein PHP-Backend und zwei Node-Frontends – mit `target` auch unterschiedlich pro Umgebung:

```yaml
apps:
    backend:
        type: php # php, node, python, static
        version: "8.3" # nicht bei static
        documentRoot: public
        envFile: ../../shared/.env.local
        envValues: # eigene Werte für die envFile
            TYPO3_CONTEXT:
                prod: Production
                stage: Production/Staging
        software:
            composer: "2"
            imagemagick: latest
        phpExtensions: [excimer, apcu]
        phpIni:
            memory_limit: 512M
            session.save_handler: redis
            session.save_path: "tcp://${REDIS_HOST}:${REDIS_PORT}"
    shop:
        type: node
        version: "22"
        path: frontend/shop
        command: node server.js
    landing:
        type: node
        version: "22"
        path: frontend/landing
        command: node server.js
        target: prod # nur auf prod
```

Auf `prod` entstehen so drei Apps, auf `stage` zwei.

| Feld            | Beschreibung                                                                                              |
| --------------- | --------------------------------------------------------------------------------------------------------- |
| `type`          | `php`, `node`, `python` oder `static`                                                                     |
| `version`       | Version **in Anführungszeichen**, sonst liest YAML `8.10` als Zahl `8.1`                                  |
| `path`          | Verzeichnis der App relativ zur Datei; Standard: das Verzeichnis der Datei                                |
| `target`        | nur an diesen Fundorten, siehe [target](#target)                                                          |
| `documentRoot`  | relativ zur Datei, z.B. `public`; Pflicht bei `php` und `static`, bei `node` und `python` nicht vorhanden |
| `command`       | nur `node` und `python`: Startbefehl                                                                      |
| `envFile`       | Pfad der `.env`-Datei der App, siehe [Variablen](#variablen)                                              |
| `envValues`     | eigene Werte zusätzlich zu den Zugangsdaten, siehe [Variablen](#envvalues-eigene-werte)                   |
| `software`      | zusätzliche Systemsoftware mit Version; entfernte Einträge werden deinstalliert                           |
| `phpExtensions` | nur `php`: einzuschaltende Erweiterungen                                                                  |
| `phpIni`        | nur `php`: php.ini-Werte, `${NAME}` wird ersetzt                                                          |

Zwei Apps dürfen am selben Fundort nicht im selben Verzeichnis liegen. Gibt es mehr als eine App, geben `routing` und `cronjobs` mit `app` an, welche gemeint ist.

**php.ini:** Die Werte landen in `/tmp/php/php.ini`. Diese Datei gilt für das **ganze Projekt**: Setzen zwei Fundorte unterschiedliche Werte für denselben Schlüssel, meldet Startklar einen Konflikt und schreibt nichts. Startklar schreibt die Datei nur, wenn sich der Inhalt ändert, weil jedes Schreiben PHP neu startet.

## databases

MySQL- und Redis-Datenbanken, nach frei wählbarem Namen (`a-z`, `0-9`, `-`).

```yaml
databases:
    db:
        type: mysql
        version: "8.4"
        charset: utf8mb4
        collation: utf8mb4_unicode_ci
        target: [prod, stage]
        envValues:
            DB_HOST: ${host}
            DB_PORT: ${port}
            DB_NAME: ${name}
            DB_USER: ${user}
            DB_PASSWORD: ${password}
    cache:
        type: redis
        version: "7.2"
        shared: true
        maxMemory: 512mb
        maxMemoryPolicy: allkeys-lru
        persistent: false
        envValues:
            REDIS_HOST: ${host}
            REDIS_PORT: ${port}
```

| Feld                   | MySQL                                              | Redis                  | Beschreibung                                                                                                                    |
| ---------------------- | -------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `type`                 | `mysql`                                            | `redis`                |                                                                                                                                 |
| `version`              | ✓                                                  | ✓                      | in Anführungszeichen                                                                                                            |
| `charset`, `collation` | ✓                                                  |                        |                                                                                                                                 |
| `maxMemory`            |                                                    | ✓                      | z.B. `512mb`                                                                                                                    |
| `maxMemoryPolicy`      |                                                    | ✓                      | `noeviction`, `allkeys-lru`, `allkeys-lfu`, `allkeys-random`, `volatile-lru`, `volatile-lfu`, `volatile-random`, `volatile-ttl` |
| `persistent`           |                                                    | ✓                      | Daten auf Platte sichern                                                                                                        |
| `shared`, `target`     | ✓                                                  | ✓                      | siehe [target](#target) und [shared](#shared)                                                                                   |
| `envValues`            | `${…}`: `host`, `port`, `name`, `user`, `password` | `${…}`: `host`, `port` | siehe [Variablen](#an-ressourcen)                                                                                               |

## containers

Container nach Name.

```yaml
containers:
    solr:
        image: ghcr.io/acme/solr:9.6
        port: 8983
        shared: true
        volumes:
            - solr-data:/var/solr
        limits:
            cpus: "1"
            memory: 2g
        secrets:
            - SOLR_PASSWORD
        envValues:
            SOLR_HOST: ${host}
            SOLR_PORT: ${port}
            SOLR_PASSWORD: ${SOLR_PASSWORD}
    worker:
        image: ghcr.io/acme/worker:1.4
        target: prod # nur auf prod
        command: ["php", "bin/worker", "--queue=default"]
        environment:
            DATABASE_URL: "mysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}/${DB_NAME}"
            LOG_LEVEL: info
```

| Feld               | Beschreibung                                                                                                          |
| ------------------ | --------------------------------------------------------------------------------------------------------------------- |
| `image`            | Docker-Image; private Registries siehe [registries](#registries)                                                      |
| `port`             | Port, auf dem der Container lauscht                                                                                   |
| `command`          | Befehl als Liste, statt dem Standardbefehl des Images                                                                 |
| `environment`      | Variablen **im** Container, für das Programm darin; `${NAME}` meint eine Variable aus den `envValues` einer Ressource |
| `volumes`          | `"<name>:<pfad im container>"`                                                                                        |
| `limits`           | `cpus`, `memory`                                                                                                      |
| `secrets`          | siehe unten                                                                                                           |
| `envValues`        | Variablen **für die Apps**, in deren `envFile`; `${…}`: `host`, `port` und Namen von Secrets                          |
| `shared`, `target` | siehe [target](#target) und [shared](#shared)                                                                         |

`environment` und `envValues` gehen in entgegengesetzte Richtungen: `environment` konfiguriert den Container selbst (z.B. bekommt der Worker die `DATABASE_URL`), `envValues` sagt den Apps, wie sie den Container erreichen (z.B. bekommt die TYPO3-App `SOLR_HOST` in ihre `.env`). Ein Container kann beides brauchen.

**An eine gemeinsame Instanz hängen:** Liegen mehrere Repositories im selben Projekt – jedes mit eigener `.klar.yml` –, kann eines einen Container mit `shared: true` definieren und die anderen hängen sich nur an. Ein Eintrag **ohne** `image` legt nichts an, sondern nutzt die gleichnamige Instanz und bekommt deren Zugangsdaten in die eigene `envFile`. Erlaubt sind dann nur `shared`, `target` und `envValues`:

```yaml
# ~/html/website/.klar.yml – definiert die Instanz
containers:
    solr:
        image: solr:9.6
        port: 8983
        shared: true
        secrets: [SOLR_PASSWORD]
        envValues:
            SOLR_HOST: ${host}
            SOLR_PASSWORD: ${SOLR_PASSWORD}
```

```yaml
# ~/html/shop/.klar.yml – hängt sich nur an
containers:
    solr:
        shared: true
        envValues:
            SOLR_HOST: ${host}
            SOLR_PASSWORD: ${SOLR_PASSWORD}
```

Definiert keine Datei im Projekt die Instanz, wird der Eintrag nicht angewendet und das Startklar-Menü zeigt es an.

### Secrets

Startklar erzeugt Secrets einmalig, speichert sie verschlüsselt, übergibt sie dem Container als Umgebungsvariable und schreibt sie über die `envValues` des Containers (z.B. `SOLR_PASSWORD: ${SOLR_PASSWORD}`) in die `envFile` jeder App, die den Container nutzt – auch bei gemeinsamen Instanzen.

```yaml
secrets:
    - SOLR_PASSWORD # 32 Zeichen, A-Z a-z 0-9
    - name: LEGACY_API_PASSWORD
      length: 12 # 8 bis 256
      chars: "A-Za-z0-9!#%" # wie eine Regex-Zeichenklasse
      pattern: "^[A-Za-z].*[0-9]" # wird neu erzeugt, bis es passt
```

## smtp

Ein Postfach zum Versenden von Mails aus der App – für alles, was die App selbst verschickt, z.B. Kontaktformulare oder Passwort-Links. Startklar legt das Postfach an und schreibt die Zugangsdaten in die `envFile`.

```yaml
smtp:
    envValues:
        SMTP_HOST: ${host}
        SMTP_PORT: ${port}
        SMTP_USER: ${user}
        SMTP_PASSWORD: ${password}
```

Mit [`shared`](#shared) und [`target`](#target) wie bei Datenbanken.

## cronjobs

Cronjobs nach Name, ausgeführt in einer App des Fundorts. Gibt es mehrere Apps, sagt `app`, in welcher.

```yaml
cronjobs:
    scheduler:
        schedule: "*/5 * * * *"
        command: vendor/bin/typo3 scheduler:run
        app: backend # nur nötig bei mehreren Apps
        target: prod
        timeout: 600
        timezone: Europe/Vienna
        email: ops@acme.at
```

| Feld       | Beschreibung                                      |
| ---------- | ------------------------------------------------- |
| `schedule` | Cron-Zeitplan mit 5 Feldern, in Anführungszeichen |
| `command`  | Befehl, im Verzeichnis der App                    |
| `app`      | App aus `apps`; nur nötig bei mehreren Apps       |
| `timeout`  | Sekunden                                          |
| `timezone` | z.B. `Europe/Vienna`                              |
| `email`    | Benachrichtigung bei Fehlern                      |

## backups

Backup-Zeitpläne für das **ganze Projekt**. Definieren mehrere Dateien Backups, müssen sie übereinstimmen, sonst meldet Startklar einen Konflikt. Ohne `backups` bleiben die Einstellungen des Projekts unverändert.

```yaml
backups:
    - schedule: "0 3 * * *"
      keep: 14d
```

## registries

Nur nötig, wenn Container-Images aus einer **privaten** Registry kommen, z.B. eigene Images auf `ghcr.io`; öffentliche Images wie auf Docker Hub brauchen keinen Eintrag. Angegeben wird nur der Hostname. Die Zugangsdaten stehen **nicht** in der Datei, sondern werden einmal pro Organisation im Startklar-Menü hinterlegt.

```yaml
registries:
    - ghcr.io
```

## routing

Domains als Liste von Einträgen. Jeder Eintrag zeigt auf **eine** App, **einen** Container oder leitet weiter. Auch Hunderte Domains bleiben so übersichtlich. TLS-Zertifikate kommen automatisch von Let's Encrypt.

```yaml
routing:
    - domains: [www.acme.at, www.acme.de]
      target: prod
    - domains: [stage.acme.at]
      target: stage
    - domains: [shop.acme.at]
      app: shop # nur nötig bei mehreren Apps
      target: prod
    - domains: [www.acme.at/search]
      container: solr
    - domains: [acme.at, acme.de]
      redirect: https://www.{domain}
    - domains: [acme-online.at]
      redirect: https://www.acme.at
```

| Art           | Felder                                 | Regeln                                                                                             |
| ------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------- |
| App           | `target`, bei mehreren Apps `app`      | genau ein Verzeichnis; die App muss dort existieren                                                |
| Container     | `container`, optional `target`, `port` | `target` ist Pflicht, wenn der Container nicht `shared` ist; `port`, wenn der Container keinen hat |
| Weiterleitung | `redirect`                             | nicht mit `target`, `app` oder `container` kombinierbar                                            |
- Domains dürfen einen Pfad haben: `www.acme.at/search`.
- Jede Domain darf nur einmal vorkommen, auch über mehrere Einträge.
- Im `redirect` wird `{domain}` durch die jeweilige Domain ersetzt, `{domain-without-www}` durch dieselbe ohne führendes `www.`.

## target

Meist liegt dieselbe Datei in mehreren Verzeichnissen, z.B. für prod und stage. Jeder Fundort bekommt eigene Apps und eigene Ressourcen. Mit `target` gilt ein Eintrag nur für bestimmte Fundorte:

```yaml
cronjobs:
    scheduler:
        schedule: "*/5 * * * *"
        command: vendor/bin/typo3 scheduler:run
        target: prod # oder eine Liste: [prod, stage]
```

Ohne `target` gilt ein Eintrag überall. Im `routing` ist genau ein Ziel erlaubt, weil eine Domain nur auf eine App an einem Fundort zeigen kann.

### targets

Welcher Fundort welches target ist, lässt sich auf zwei Arten festlegen:

**Ohne `targets`:** Ein target ist das gleichnamige Verzeichnis direkt unter `~/html`. `target: prod` passt auf `~/html/prod/.klar.yml` und `~/html/prod/current/.klar.yml` – nicht auf `preprod`.

**Mit `targets` (optional, empfohlen):** Die Umgebungen stehen oben in der Datei, jeweils mit ihrem Verzeichnis:

```yaml
targets:
    prod: ~/html/path/to/prod
    stage: ~/html/path/to/stage-new
```

- Jeder Pfad beginnt mit `~/html` und meint das Deploy-Verzeichnis, mit oder ohne `current`.
- Pfade dürfen auch tiefer liegen; Startklar sucht dort zusätzlich.
- Jedes `target` in der Datei muss einer dieser Namen sein, Tippfehler wie `prdo` fallen sofort auf.
- An Fundorten, die zu keinem Pfad passen, wendet Startklar nichts an und meldet das im Menü.

## shared

Standardmäßig bekommt jeder Fundort eigene Datenbanken, Container und SMTP-Postfächer. Mit `shared: true` gibt es **eine** Instanz für das ganze Projekt, verbunden über den Namen:

```yaml
databases:
    cache:
        type: redis
        version: "7.2"
        shared: true # ein Redis für prod und stage
```

`shared` ist nie der Standard. Eine Datei kann sich an eine gemeinsame Instanz hängen, die in einer anderen Datei definiert ist, siehe [Container](#containers).

## Pfade

Alle Pfade sind relativ zum **echten** Ort der `.klar.yml`, so wie sie im Dateisystem liegt. Bei Deployments mit Releases ist das `~/html/prod/releases/<id>/`, erreicht über den Symlink `current`.

| Angabe                             | Bedeutung                                                                         |
| ---------------------------------- | --------------------------------------------------------------------------------- |
| `documentRoot: public`             | `~/html/prod/current/public` – Pfade im Release werden auf `current/…` abgebildet |
| `envFile: ../../shared/.env.local` | `~/html/prod/shared/.env.local` – Pfade außerhalb bleiben, wie sie sind           |

Eine App ohne `path` wird im Verzeichnis oberhalb von `current` installiert, also `~/html/prod`. Mit `path: frontend/shop` liegt sie in `~/html/prod/current/frontend/shop`.

## Variablen

Jede App kann eine `.env`-Datei bekommen. Was hineinkommt, steht überall gleich als **`envValues`** – immer `NAME: wert`:

| Wo                                                     | Was                                                       | Landet in                                                              |
| ------------------------------------------------------ | --------------------------------------------------------- | ---------------------------------------------------------------------- |
| `envFile` bei der App                                  | **wohin:** Pfad der `.env`-Datei, relativ zur `.klar.yml` | –                                                                      |
| `envValues` bei Datenbanken, Redis, Containern, `smtp` | Zugangsdaten und Werte dieser Ressource                   | der `envFile` **jeder** App an den targets der Ressource – automatisch |
| `envValues` bei der App                                | eigene Werte                                              | nur der `envFile` **dieser** App                                       |

```text
.env der App  =  envValues aller Ressourcen, die an diesem Fundort gelten (automatisch)
              +  envValues der App
```

Startklar verwaltet in der Datei nur den Abschnitt zwischen `#startklar` und `#endklar`. Alles andere bleibt unverändert:

```dotenv
APP_ENV=prod
#startklar
DB_HOST=mysql-abc123.pg-s-xyz.db.project.host
DB_PASSWORD=…
TYPO3_CONTEXT=Production
#endklar
```

### An Ressourcen

In den `envValues` einer Ressource meint `${…}` ein **Feld dieser Ressource**. So kommen Zugangsdaten unter dem Namen in die `.env`, den die App erwartet – auch zusammengesetzt oder als fester Wert:

```yaml
databases:
    db:
        type: mysql
        version: "8.4"
        envValues:
            DB_HOST: ${host}
            DB_PASSWORD: ${password}
            DATABASE_URL: "mysql://${user}:${password}@${host}/${name}"
```

| Ressource | Felder für `${…}`                                              |
| --------- | -------------------------------------------------------------- |
| MySQL     | `host`, `port`, `name`, `user`, `password`                     |
| Redis     | `host`, `port`                                                 |
| Container | `host`, `port` (wenn angegeben) und die Namen seiner `secrets` |
| `smtp`    | `host`, `port`, `user`, `password`                             |

### An Apps

Eigene Werte für die `envFile` einer App – z.B. den Kontext pro Umgebung, sodass in der `.env.local` nichts mehr von Hand gepflegt werden muss:

```yaml
apps:
    backend:
        type: php
        version: "8.3"
        documentRoot: public
        envFile: ../../shared/.env.local
        envValues:
            TYPO3_CONTEXT: # ein Wert pro target
                prod: Production
                stage: Production/Staging
            LOG_LEVEL: info # überall gleich
```

### Regeln

- **`${…}` außerhalb von Ressourcen** – in den `envValues` einer App, in `phpIni` und im `environment` von Containern – meint eine Variable, die eine Ressource in ihren `envValues` definiert, z.B. `tcp://${REDIS_HOST}:${REDIS_PORT}`.
- **Werte pro target:** Ein einfacher Wert gilt überall, eine Angabe pro target nur dort – und nur an targets, an denen es die Ressource bzw. App gibt.
- **Namen:** Jeder Name der Ressourcen darf nur einmal vorkommen; die `envValues` einer App dürfen keinen davon wiederholen. Verschiedene Apps dürfen denselben Namen verwenden.
- **`envFile`:** Eine App mit eigenen `envValues` braucht eine `envFile`. Ohne `envFile` bekommt eine App auch keine Zugangsdaten.

### Reihenfolge

Platzhalter brauchen die echten Werte. Startklar geht deshalb bei jedem Abgleich in dieser Reihenfolge vor:

1. Secrets erzeugen
2. Datenbanken, Redis und SMTP-Postfach anlegen und warten, bis sie bereit sind
3. Container anlegen – mit aufgelösten Platzhaltern in `environment`
4. Apps anlegen, samt Software und PHP-Erweiterungen
5. `envFile` schreiben – `envValues` der Ressourcen und der App
6. php.ini schreiben – mit aufgelösten Platzhaltern
7. Cronjobs, Routing und Backups
8. `STARTKLAR_HASH` in die `envFile` schreiben (siehe [Im Deploy warten](#im-deploy-warten))

Fehlt für einen Platzhalter der Wert, weil die zugehörige Ressource noch nicht bereit ist, schreibt Startklar weder die php.ini noch den betroffenen Container noch den Hash und versucht es später erneut. Eine halb aufgelöste Einstellung wie `tcp://:` entsteht so nie.

## Was Startklar ändert und löscht

| Fall                                                                                                        | Verhalten                                                |
| ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| Neuer Eintrag                                                                                               | wird angelegt                                            |
| Geänderter Eintrag                                                                                          | wird angepasst                                           |
| Entfernte Einstellung ohne Daten (Software, PHP-Erweiterung, Cronjob, Variable, Container-Konfiguration)    | wird automatisch entfernt                                |
| Entfernte Ressource mit Daten oder sichtbarer Wirkung (Datenbank, Redis, Container mit Volume, Domain, App) | wird erst nach Bestätigung im Startklar-Menü entfernt    |
| Ungültige Datei                                                                                             | nichts wird geändert, Fehler mit Zeilennummer im Menü    |
| Konflikt zwischen Dateien (Domain doppelt, php.ini, Backups)                                                | betroffener Teil wird nicht angewendet, Konflikt im Menü |

Startklar ändert nur, was es selbst angelegt hat.

### Aufräumen (Projekt-Einstellung)

Wer es aufgeräumt haben will, kann im Startklar-Menü des Projekts einstellen, dass Ressourcen ohne Eintrag in einer `.klar.yml` automatisch entfernt werden – pro Art einzeln, standardmäßig ist alles aus. Die Einstellung steht bewusst nicht in der Datei: Ein Commit allein soll nie Daten löschen können.

| Einstellung                                   | Verhalten                                                                                |
| --------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Domains                                       | werden entfernt                                                                          |
| Container ohne Volumes                        | werden entfernt                                                                          |
| Container mit Volumes, Datenbanken, Redis     | werden zum Löschen vorgemerkt und nach 24 Stunden entfernt; bis dahin im Menü abbrechbar |
| Auch nicht von Startklar angelegte Ressourcen | gilt zusätzlich für Ressourcen, die von Hand angelegt wurden                             |

Unabhängig davon gilt immer:

- Aufgeräumt wird nur, wenn im selben Durchlauf **alle** freigegebenen Fundorte gelesen wurden und gültig sind.
- Sieht eine Änderung wie eine Umbenennung aus (eine Ressource verschwindet, eine gleichartige kommt dazu), fragt Startklar immer nach.
- Apps werden nie automatisch entfernt.
- Ressourcen, die in keiner Datei stehen, zeigt das Menü an – zum Entfernen oder zum Übernehmen in die Datei.

## Prüfung und Fehler

Startklar prüft die Datei vollständig, bevor es etwas tut. Jeder Fehler kommt mit Pfad und Zeile, z.B.:

```text
routing[1].domains[0] (Zeile 84): www.acme.at kommt schon in routing[0] vor
```

Unbekannte Schlüssel sind Fehler, damit Tippfehler wie `docRoot` statt `documentRoot` auffallen.

## Im Deploy warten

Startklar bemerkt eine geänderte `.klar.yml` innerhalb von 15 Sekunden nach dem Deploy und gleicht sie dann ab, einen neuen Fundort erst nach der Freigabe im Menü. Damit z.B. Datenbank-Migrationen beim ersten Aufsetzen erst laufen, wenn die Datenbank existiert, wartet Startklar nach dem Anlegen, bis mittwald jede Ressource als bereit meldet (Datenbanken und Redis `ready`, Apps `ready`, Container `running`), und schreibt erst dann – als **letzten** Schritt – den Hash der angewendeten Datei in den eigenen Block der `envFile`. Auf Domains und TLS-Zertifikate wird dabei nicht gewartet, ihren Stand zeigt das Startklar-Menü:

```dotenv
#startklar
DB_HOST=…
DB_PASSWORD=…
STARTKLAR_HASH=3f9a…
#endklar
```

Steht dort der Hash der aktuellen `.klar.yml`, ist alles angewendet – auch die Zugangsdaten in derselben Datei. Warten lässt sich darauf mit Bordmitteln, im Verzeichnis der `.klar.yml` (z.B. im Deploy nach dem Umschalten von `current`, vor den Migrationen):

```bash
timeout 99 sh -c 'until grep -q $(sha256sum .klar.yml | cut -c-64) ../../shared/.env.local; do sleep 5; done'
```

- `99` ist die maximale Wartezeit in Sekunden; danach endet der Befehl mit Exit-Code `124` und der Deploy bricht ab.
- `../../shared/.env.local` ist die `envFile` der App, relativ zur `.klar.yml`.
- Blockiert wird nur, was die App zum Laufen braucht: Datenbanken, Redis, Apps, Software, PHP und die `envFile`. Probleme mit Domains, Zertifikaten oder Backups und ausstehende Bestätigungen erscheinen als Warnung im Menü, der Hash wird trotzdem geschrieben.
- Ist die Datei ungültig oder schlägt etwas dauerhaft fehl, kommt der Hash nicht und der Befehl endet nach der Wartezeit; die Fehler stehen im Startklar-Menü.

Fehler in der Datei selbst lassen sich schon vor dem Deploy erkennen – im Editor dank der `$schema`-Zeile oder in der Pipeline mit einem beliebigen JSON-Schema-Validator, z.B.:

```bash
pipx run check-jsonschema --schemafile https://startklar.cyber.house/v1/schema.json .klar.yml
```

Ändert sich ein Dienst, kann es während des Abgleichs kurz eine Downtime geben – das Warten verhindert nur, dass der Deploy in dieser Zeit weiterläuft.

## Berechtigungen und Sicherheit

- **Installation:** Startklar wird einmal pro Organisation im mStudio installiert. Dabei bestätigt man, worauf es zugreifen darf: Apps, Datenbanken, Container, Domains, Cronjobs, Mail, Backups, Registries, Projekte und SSH-Benutzer.
- **Aktivierung:** Startklar arbeitet nur in Projekten, in denen es im Startklar-Menü des Projekts aktiviert ist, und richtet neue Fundorte erst nach Freigabe ein.
- **Lesen und Schreiben:** Die `.klar.yml` liest Startklar über die mStudio-API. Schreiben – den eigenen Block in der `envFile` und die php.ini – erledigt es über einen eigenen SSH-Benutzer, den es im Projekt anlegt. Andere Dateien fasst es nicht an.
- **Secrets:** Erzeugte Passwörter und Registry-Zugangsdaten werden verschlüsselt gespeichert und im Menü nie angezeigt.
- **Sichtbarkeit:** Im Menü sieht jede Person nur Projekte, auf die sie im mStudio selbst Zugriff hat. Organisationen sind strikt voneinander getrennt.

## Was Startklar nicht macht

- **Code deployen** – das bleibt bei Magallanes, Deployer, Git oder deiner Pipeline.
- **Postfächer für Menschen** – nur das Versand-Postfach der App (`smtp`).
- **DNS-Einträge** – Domains müssen bereits auf mittwald zeigen.
- **Eigene TLS-Zertifikate** – Zertifikate kommen von Let's Encrypt.
- **SSH-Benutzer und Projekt-Mitglieder für Menschen.**
- **KI-Hosting.**

## Für KI-Agents

Wenn du als KI-Agent eine `.klar.yml` bearbeitest:

- Das Format ist hier vollständig beschrieben; die maschinenlesbare Form ist https://startklar.cyber.house/v1/schema.json, die ganze Doku als Text https://startklar.cyber.house/llms-full.txt.
- Versionen, Zeitpläne und Werte mit `:` oder `{` immer in Anführungszeichen.
- Laufen im Deploy Migrationen, davor auf `STARTKLAR_HASH` in der `envFile` warten (siehe „Im Deploy warten“).
- Keine Zugangsdaten in die Datei schreiben: Passwörter erzeugt Startklar (`secrets`), Registry-Zugangsdaten kommen ins Startklar-Menü.
- Bei mehreren Umgebungen `targets` mit den Pfaden angeben und nur diese Namen in `target` verwenden.
- `shared: true` nur setzen, wenn eine Ressource wirklich von mehreren Fundorten genutzt werden soll.
- Pfade relativ zum echten Ort der Datei angeben (bei Releases: `releases/<id>/`).
- Fehlende Abschnitte bedeuten „nicht verwalten“, nicht „löschen“.

## Beispiel

Ein vollständiges Beispiel mit allen Funktionen: TYPO3-Backend und zwei Node-Frontends, deployt nach `~/html/prod` und `~/html/stage`. Fahre über einen Eintrag – oder tippe ihn an –, um zu sehen, was er bewirkt. Zum Herunterladen: [example.klar.yml](https://startklar.cyber.house/v1/example.klar.yml).

```yaml
# Startklar config — docs: https://startklar.cyber.house/v1/
# $schema: https://startklar.cyber.house/v1/schema.json
#
# Example with every feature: TYPO3 backend plus two
# Node frontends, deployed via Magallanes.
# Everything except "version" is optional, missing
# sections are not touched. Paths are relative to
# the real location of this file (releases/<id>).
version: 1

# Optional: environments and their deploy directories.
# Without it, "target: prod" means ~/html/prod.
targets:
    prod: ~/html/prod
    stage: ~/html/acme/staging

apps:
    backend:
        type: php
        version: "8.3"
        documentRoot: public
        # Startklar only writes its own block in this file
        envFile: ../../shared/.env.local
        # Own values for this envFile, in addition to the credentials
        envValues:
            TYPO3_CONTEXT:
                prod: Production
                stage: Production/Staging
            LOG_LEVEL: info
        software:
            composer: "2"
            imagemagick: latest
        phpExtensions:
            - excimer
            - apcu
        # Global for the project; ${NAME} from the envValues of a resource
        phpIni:
            memory_limit: 512M
            max_execution_time: 120
            upload_max_filesize: 64M
            post_max_size: 64M
            session.save_handler: redis
            session.save_path: "tcp://${REDIS_HOST}:${REDIS_PORT}"
    shop:
        type: node
        version: "22"
        path: frontend/shop
        command: node server.js
        envFile: ../../shared/frontend/shop/.env.local
    landing:
        type: node
        version: "22"
        path: frontend/landing
        command: node server.js
        target: prod # only on prod

databases:
    db:
        type: mysql
        version: "8.4"
        charset: utf8mb4
        collation: utf8mb4_unicode_ci
        envValues:
            DB_HOST: ${host}
            DB_PORT: ${port}
            DB_NAME: ${name}
            DB_USER: ${user}
            DB_PASSWORD: ${password}
    cache:
        type: redis
        version: "7.2"
        shared: true # one Redis for prod and stage
        maxMemory: 512mb
        maxMemoryPolicy: allkeys-lru
        persistent: false
        envValues:
            REDIS_HOST: ${host}
            REDIS_PORT: ${port}

# Credentials go into the Startklar UI, not here
registries:
    - ghcr.io

containers:
    solr:
        image: ghcr.io/acme/solr:9.6
        port: 8983
        shared: true # one instance for prod and stage
        volumes:
            - solr-data:/var/solr
        limits:
            cpus: "1"
            memory: 2g
        secrets:
            - SOLR_PASSWORD # generated once, 32 chars
        envValues:
            SOLR_HOST: ${host}
            SOLR_PORT: ${port}
            SOLR_PASSWORD: ${SOLR_PASSWORD}
    worker:
        image: ghcr.io/acme/worker:1.4
        target: prod
        command: ["php", "bin/worker", "--queue=default"]
        environment:
            DATABASE_URL: "mysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}/${DB_NAME}"
            REDIS_URL: "redis://${REDIS_HOST}:${REDIS_PORT}"
            LOG_LEVEL: info
    legacy-api:
        image: ghcr.io/acme/legacy-api:2.1
        port: 8080
        target: prod
        secrets:
            - name: LEGACY_API_PASSWORD
              length: 12
              chars: "A-Za-z0-9!#%"
              pattern: "^[A-Za-z].*[0-9]"
        envValues:
            LEGACY_API_HOST: ${host}
            LEGACY_API_PORT: ${port}
            LEGACY_API_PASSWORD: ${LEGACY_API_PASSWORD}
    mailpit:
        image: axllent/mailpit:v1.21
        port: 8025
        target: stage

smtp:
    target: prod
    envValues:
        SMTP_HOST: ${host}
        SMTP_PORT: ${port}
        SMTP_USER: ${user}
        SMTP_PASSWORD: ${password}

cronjobs:
    scheduler:
        schedule: "*/5 * * * *"
        command: vendor/bin/typo3 scheduler:run
        app: backend
        target: prod
        timeout: 600
        timezone: Europe/Vienna
        email: ops@acme.at
    cache-warmup:
        schedule: "0 6 * * *"
        command: vendor/bin/typo3 cache:warmup
        app: backend
        target: [prod, stage]

# Project-wide: all files must agree
backups:
    - schedule: "0 3 * * *"
      keep: 14d
    - schedule: "0 4 * * 0"
      keep: 90d

routing:
    - domains:
          - www.acme.at
          - www.acme.de
      app: backend
      target: prod
    - domains:
          - stage.acme.at
      app: backend
      target: stage
    - domains:
          - shop.acme.at
      app: shop
      target: prod
    - domains:
          - shop.stage.acme.at
      app: shop
      target: stage
    - domains:
          - start.acme.at
      app: landing
      target: prod
    - domains:
          - www.acme.at/search
      container: solr
    - domains:
          - api.acme.at
      container: legacy-api
      target: prod
      port: 8080
    - domains:
          - mail.stage.acme.at
      container: mailpit
      target: stage
    - domains:
          - acme.at
          - acme.de
      redirect: https://www.{domain}
    - domains:
          - www.acme-online.at
      redirect: https://{domain-without-www}
```

## Änderungen am Format

| Datum   | Änderung  |
| ------- | --------- |
| 2026-11 | Version 1 |

Neue, optionale Felder kommen in Version 1 dazu. Inkompatible Änderungen bekommen eine neue Version mit eigener Doku; diese Seite bleibt erhalten.
