skills/deploy-server/SKILL.md

```
name: deploy-server
description: Use this skill to provision or update a Welley server on a supported cloud (currently DigitalOcean) when the user asks to deploy a new instance or run a server update. Covers single-instance deployments and SSH-based updates, including interactive collection of missing credentials, SSH keys, domain, region, and sizing.
```

# Server bereitstellen

## Überblick

Dieser Skill richtet einen Welley-Stack mit einer einzelnen Instanz auf DigitalOcean ein und kann Updates über SSH ausführen. Er führt den Agenten dazu, fehlende Angaben einzuholen, die verfügbaren Instanzgrößen aus einer festgelegten Quelle abzurufen, die VM zu erstellen, den Host zu konfigurieren, mit `wlycli deployment create` bereitzustellen und das Ergebnis zu prüfen.

Die Befehlsnamen müssen mit der CLI übereinstimmen:

-   `wlycli deployment create`
-   `wlycli deployment update`
-   `wlycli deployment delete`

## Standardwerte (DigitalOcean)

-   Region: `nyc3`
-   Image: `latest LTS available via API` (über `images?type=distribution` ermitteln)
-   Droplet-Name: zufällig erzeugt als `wlsly-<adjective>-<surname>` (z. B. `wlsly-bright-hopper`). Verwende dafür das Snippet zur Namensgenerierung aus `references/digitalocean.md`. Verwende denselben Namen für die verwaltete Datenbank, falls du eine erstellst.
-   Zieltarif: Basic / Regular (Shared CPU)
-   Zielgröße: 2 vCPU / 4 GB RAM / 80 GB SSD / 4 TB Transfer (ca. 24 $/Monat)

## Regel für abweichende Angaben

Wenn der Nutzer einen der Standardwerte ändert (Region, Image, Droplet-Name, Projekt, Größe), muss der Agent:

1.  die abweichende Angabe wörtlich zurückgeben.
2.  um Bestätigung bitten, bevor er weitermacht.

## Vorlage für interaktive Eingaben

Frage nur nach dem, was fehlt. Verwende genau diese Formulierungen, damit nichts missverständlich bleibt. Frage in mehreren Runden. Runde 1: höchstens 2 Fragen pro Durchgang. In Runde 2 kann eine gebündelte Option „alle Standardwerte übernehmen“ angeboten werden. Wenn die Laufzeitumgebung Auswahlmöglichkeiten unterstützt, biete pro Frage 2 bis 3 Optionen an (A/B/C). Verzichte auf freie Eingaben, wenn es nicht nötig ist.

Fragen in Runde 1 (Identität und Zugriff):

1.  „Hast du ein DigitalOcean-API-Token, das ich verwenden kann, oder soll ich warten, bis du eins bereitstellst?“
2.  „Welche Domain soll dieser Server verwenden (z. B. `super-puper.example.com`)? Hast du Zugriff auf ihr DNS?“
3.  „Hast du schon einen öffentlichen SSH-Schlüssel, den ich verwenden kann, oder soll ich einen erstellen und hochladen?“
4.  „Möchtest du dieses Droplet einem bestehenden DigitalOcean-Projekt zuordnen? Wenn ja, nenne den Projektnamen oder die ID.“
5.  „DNS-Voraussetzung: Du musst A/AAAA-Einträge für `<domain>` und `storage.<domain>` anlegen (wenn kein CDN genutzt wird). Kannst du das erledigen, bevor certbot läuft?“
6.  „E-Mail-DNS-Voraussetzung: Die Domain aus `--smtp-from` braucht SPF-, DMARC- und DKIM-Einträge, sonst können Einladungs- und Bestätigungs-E-Mails an neue Empfänger unbemerkt verworfen werden. Sind sie vorhanden, oder soll ich die Einträge auflisten, die dein SMTP-Anbieter braucht?“
7.  Wenn der Nutzer fragt, wo er ein DO-Token bekommt, gib diesen Link an:

```
https://cloud.digitalocean.com/account/api/tokens
```

Fragen in Runde 2 (Größe, Standort und Optionen für die Bereitstellung):

1.  „Möchtest du die Standardregion `nyc3` oder eine andere Region?“
2.  „Möchtest du die Standardgröße (2 vCPU / 2 GB) oder ein anderes Ziel für CPU und RAM?“
3.  „Soll ich die neueste Ubuntu-LTS-Version aus der API verwenden oder eine bestimmte Ubuntu-Version?“
4.  „Welchen SSH-Nutzer sollen wir nach dem Bootstrap verwenden (Standard: `wlsly`)? Du kannst den Standard behalten.“
5.  „Möchtest du eine verwaltete Postgres-Datenbank oder den mitgelieferten Postgres-Container?“
6.  Wenn verwaltetes Postgres gewählt wird: „Welche Region und Größe soll ich für die Datenbank verwenden? Ich kann `nyc3` und 1 vCPU / 2 GB als Standard nehmen.“
7.  Wenn verwaltetes Postgres gewählt wird: „Soll ich die Datenbank zum angezeigten Monatspreis des Anbieters erstellen?“
8.  „Gib bitte die erforderlichen SMTP-Einstellungen an (Host, Nutzer, Passwort, Absender). OTEL ist optional, gib es nur an, wenn du es brauchst.“
9.  „Zum Erstellen ist eine Admin-E-Mail-Adresse nötig. Gib sie jetzt an (Flag: `--email` / `-e`). Wenn du sie nicht angibst, frage ich zweimal nach, um sie zu bestätigen.“

Biete in Runde 2 die Option „alle Standardwerte übernehmen“ an, damit die Eingaben schneller zusammenkommen.

Wenn eine Antwort fehlt, stelle eine gezielte Nachfrage (eine oder zwei Fragen).

## Ablauf (DigitalOcean, einzelne Instanz)

1.  **Erforderliche Angaben sammeln (nachfragen, wenn sie fehlen).**

-   DigitalOcean-API-Token
-   Projektname oder ID (optional; wenn sie fehlen oder das Projekt nicht gefunden wird, das Standardprojekt verwenden)
-   Region
-   Zielgröße der Instanz (z. B. 4 vCPU / 16 GB RAM)
-   Domainname
-   Öffentlicher SSH-Schlüssel (oder die Bestätigung, dass einer erstellt und registriert werden soll)
-   Admin-E-Mail-Adresse für die erste Anmeldung und die Passwortwiederherstellung (zum Erstellen erforderlich; `--email` / `-e`). Wenn sie fehlt, zweimal abfragen und prüfen, ob beide Eingaben übereinstimmen.
-   Optional: eigene TLS-Zertifikate oder certbot
-   Bestätigung des DNS-Zugriffs (A/AAAA-Einträge für `<domain>` und gegebenenfalls `storage.<domain>` müssen angelegt werden können)

1.  **Instanztypen abrufen und eine Größe wählen.**

-   Verwende das Snippet für Größen aus `references/digitalocean.md`, um nach CPU und RAM aufzulisten und zu filtern.
-   Bei mehreren Treffern beim Nutzer nachfragen.

2b. **Den Ubuntu-Image-Slug ermitteln.**

-   Rufe `GET /v2/images?type=distribution&per_page=200` auf und filtere nach Ubuntu.
-   Wenn der Nutzer eine bestimmte Version wollte, prüfe, ob es den Slug gibt, und lass dir das bestätigen.
-   Wähle andernfalls den neuesten LTS-Slug aus der Liste und lass ihn vor dem Erstellen vom Nutzer bestätigen.

1.  **Das Droplet erstellen.**

-   Verwende direkt die DigitalOcean-API (doctl ist optional).
-   Image: bevorzugt die Angabe des Nutzers, sonst die neueste LTS-Version aus der API-Liste.
-   Hänge den Fingerabdruck des SSH-Schlüssels an oder erstelle einen Schlüssel und lade ihn hoch.
-   Aktiviere das Monitoring.
-   Wenn ein Projektname oder eine ID angegeben, aber nicht gefunden wurde, erstelle das Droplet ohne Projekt (Standardprojekt).
-   Hinweis: Die verwaltete Datenbank kann parallel zum Droplet erstellt werden.

## Hinweise zur Parallelisierung

Der Agent sollte unabhängige Aufgaben überlappen lassen, um die Gesamtlaufzeit zu verkürzen:

-   Während du Angaben vom Nutzer einholst (Runde 2), starte die Erstellung des Droplets, wenn Token, Region, Größe, Image und SSH-Schlüssel schon bestätigt sind.
-   Starte die Erstellung der verwalteten Postgres-Datenbank (falls gewünscht), während das Droplet eingerichtet wird.
-   Sammle die restlichen Angaben (SMTP, OTEL, Admin-E-Mail) weiter, während das Droplet startet.
-   Wenn für einen Schritt eine wichtige Angabe fehlt, pausiere diesen Schritt und mach mit anderen Aufgaben weiter, die sich parallel erledigen lassen.
-   Wenn das DNS bei DigitalOcean verwaltet wird, erstelle oder aktualisiere die A/AAAA-Einträge, sobald die IP des Droplets vorliegt (warte nicht auf den vollständigen Bereitstellungsschritt).

1.  **DNS und Domain.**

-   Frage, ob das DNS bei DigitalOcean erstellt oder aktualisiert werden soll oder ob das externe DNS so bleiben soll.
-   Wenn du das DigitalOcean-DNS verwendest, lege A/AAAA-Einträge auf die IP des Droplets an.

1.  **Den Host per SSH einrichten.**

-   Lege einen Nutzer ohne Root-Rechte an (z. B. `wlsly`), gib ihm sudo-Rechte und kopiere die autorisierten SSH-Schlüssel.
-   **Melde dich als root ab und danach als der Nutzer ohne Root-Rechte wieder an**, bevor du weitermachst.
-   Installiere Docker und docker compose.
-   Lade die Welley-CLI-Binärdatei aus dem AppStore herunter und mach sie ausführbar:
    -   Basis-URL: `https://appstore.allroads.social/v1/social.wellesley.wlycli/latest?develop=stable&platform=<os>/<arch>`
    -   Wähle den passenden Wert für `platform` je nach Betriebssystem und Architektur:
        -   Linux x86\_64: `linux/amd64`
        -   Linux arm64: `linux/arm64`
        -   macOS x86\_64: `darwin/amd64`
        -   macOS arm64: `darwin/arm64`
        -   Windows x86\_64: `windows/amd64`
    -   Beispiel (Linux x86\_64):
        
        ```
        curl -fL 'https://appstore.allroads.social/v1/social.wellesley.wlycli/latest?develop=stable&platform=linux/amd64' -o wlycli
        chmod +x wlycli
        ```
        
    -   Zu den erwarteten Headern in der Antwort gehören:
        -   `x-artifact-display-name: Welley CLI`
        -   `x-artifact-full-name: social.wellesley.wlycli`
        -   `x-artifact-kind: cli`
        -   `x-artifact-platform: <os>/<arch>` zum Beispiel `linux/amd64`
        -   `x-artifact-sha256: <sha256>`
        -   `x-artifact-version: <version>`
        -   `x-channel: develop`
    -   Prüfe vor der Installation am besten die Header mit einer HEAD-Anfrage und kontrolliere, dass Artefaktname, Art, Plattform und Kanal zur angeforderten Binärdatei passen.
-   Prüfe die CLI-Binärdatei: Führe `wlycli -v` und `wlycli deployment --help` aus, um die erwarteten Build-Informationen zu bestätigen.

1.  **Welley bereitstellen.**

-   Führe `wlycli deployment create` mit den erforderlichen Optionen aus.
-   Verwende die Standardwerte für eine einzelne Instanz (kein externes Postgres, außer der Nutzer wünscht es).
-   Wenn verwaltetes Postgres gewünscht ist, erstelle es zuerst (DigitalOcean-Datenbank) und verwende dann Host, Port, Datenbank, Nutzer und Passwort aus der Antwort in den `--postgres-*`\-Flags.
-   Wenn das Passwort des Datenbanknutzers von der API oder der Oberfläche nicht zurückgegeben wird, setze die Zugangsdaten des Datenbanknutzers zurück, um neue zu erhalten, bevor du `wlycli` ausführst.
-   Nicht interaktive Läufe müssen `--email` / `-e` übergeben, sonst schlägt der Befehl fehl.
-   `--base-dir` zeigt auf das übergeordnete Verzeichnis, die CLI legt das Projektverzeichnis darin automatisch an.
-   Die CLI bricht ab, wenn das Projektverzeichnis oder die Container schon existieren. Vor einem neuen Versuch muss aufgeräumt werden.

1.  **Zertifikate (zwingend).**

-   Standard: Certbot im Standalone-Modus verwenden (nicht interaktiv).
-   **Der Nutzer muss die A/AAAA-Einträge der Domain (und von `storage.<domain>`, falls verwendet) auf die IP des Droplets zeigen lassen, bevor certbot läuft.**
-   Die CLI unterstützt `--public-ip` (überschreibt die erkannte IP) und `--skip-dns-preflight`, falls du die Prüfung umgehen musst.
-   **Gehe nie davon aus, dass das DNS eingerichtet ist. Frage nach und warte auf eine ausdrückliche Bestätigung.**
-   **Verwende `--skip-dns-preflight` nur, wenn der Nutzer es ausdrücklich verlangt.**
-   Wenn kein CDN eingerichtet ist, muss certbot `storage.<domain>` in die Zertifikatsanfrage aufnehmen.
-   Verwende selbstsignierte Zertifikate nur, wenn der Nutzer es ausdrücklich verlangt, und übergib `--self-signed-certs` (erzeugt selbstsignierte Zertifikate in Go).
-   Gib dem Nutzer Anweisungen zur DNS-Einrichtung und warte auf seine Bestätigung:
-   „Lege A/AAAA-Einträge an, damit `<domain>` auf die IP des Droplets zeigt.“
-   „Wenn du `storage.<domain>` verwendest, lege auch dafür einen A/AAAA-Eintrag an.“
-   „Sobald sich das DNS verbreitet hat, sag mir Bescheid, dann mache ich weiter. Ich kann die Verbreitung nicht selbst sehen, es sei denn, du bittest mich, die Auflösung zu prüfen.“
-   Der Agent kann die DNS-Verbreitung selbst mit `ping -c 1 <domain>` prüfen und die IP mit der des Droplets vergleichen. Wenn sie übereinstimmt, kann er fortfahren.

7b. **E-Mail-DNS (SPF / DMARC / DKIM).**

-   E-Mails von einer Domain ohne SPF und DMARC werden von Gmail und Outlook bei Erstempfängern oft verworfen, auch wenn der SMTP-Anbieter sie als zugestellt meldet.
-   `deployment create` gibt eine `Warning:` aus, wenn die Domain aus `--smtp-from` keinen SPF-Eintrag hat oder keinen DMARC-Eintrag bei `_dmarc.<domain>` oder einer übergeordneten Domain. Die Bereitstellung wird dadurch nicht blockiert. Ignoriere die Warnung nie: Zeige sie dem Nutzer und nenne ihm die Einträge, die er hinzufügen muss:
    -   `<mail-domain>` TXT `v=spf1 include:<provider> ~all` (das genaue include kommt vom SMTP-Anbieter; in einen vorhandenen SPF-Eintrag einfügen, nie einen zweiten anlegen)
    -   `_dmarc.<mail-domain>` TXT `v=DMARC1; p=none; rua=mailto:dmarc@<mail-domain>`
    -   DKIM: die Einträge `<selector>._domainkey.<mail-domain>`, die der SMTP-Anbieter erzeugt, wenn die Domain verifiziert wird
-   Prüfe mit `dig +short TXT <mail-domain>` und `dig +short TXT _dmarc.<mail-domain>`.
-   Die CLI kann DKIM nicht prüfen (der Selector ist anbieterspezifisch). Bitte den Nutzer immer, zu bestätigen, dass DKIM beim SMTP-Anbieter verifiziert ist, auch wenn `create` keine Warnung ausgegeben hat. Bei Amazon SES und ähnlichen Anbietern wird SPF für die eigene Envelope-Domain des Anbieters geprüft, deshalb kann nur DKIM DMARC bestehen lassen. In SES muss unter _Verified identities_, dann `<mail-domain>`, dann DKIM der Status _Successful_ stehen.

1.  **Prüfen und berichten.**

-   Prüfe `docker ps` und die Health-Endpunkte der Dienste.
-   Gib die abschließenden Verbindungsdaten und die nächsten Schritte an.

## Ablauf des SSH-Updates

Wenn der Nutzer „Server aktualisieren“ sagt:

1.  Frage nach Ziel-Host oder IP und nach SSH-Nutzer und Schlüssel.
2.  Führe `wlycli deployment update --domain <domain> [--force-restart]` aus.
3.  Prüfe den Status der Container und von Nginx.

## Ressourcen

-   Snippets und Beispiele zur DigitalOcean-API: `references/digitalocean.md`.

## Zugangsdaten als Flags

Verwende für Zugangsdaten diese Flag-Namen:

-   SMTP-Host: `--smtp-host`
-   SMTP-Nutzer: `--smtp-user`
-   SMTP-Passwort: `--smtp-password`
-   SMTP-Absender: `--smtp-from`
-   OTEL-Endpunkt: `--otel-endpoint`
-   OTEL-Header: `--otel-headers`

## Minimaler Create-Befehl

Erforderliche Flags:

-   `--domain`
-   `--email`
-   `--smtp-host`
-   `--smtp-user`
-   `--smtp-password`
-   `--smtp-from`

Minimales Beispiel:

```
wlycli deployment create \
  --domain example.com \
  --email admin@example.com \
  --smtp-host smtp.example.com \
  --smtp-user user \
  --smtp-password pass \
  --smtp-from noreply@example.com
```

## Standardwerte für verwaltetes Postgres (DigitalOcean und CLI)

Wenn du das verwaltete Postgres von DigitalOcean verwendest, verlass dich nicht auf die Standardwerte der CLI. Setze alle `--postgres-*`\-Flags ausdrücklich:

-   Typische DO-Standardwerte:
    -   Port: `25060`
    -   Datenbank: `defaultdb`
    -   Nutzer: `doadmin`
-   Standardwerte der CLI (wenn du die Flags weglässt):
    -   Port: `5432`
    -   Datenbank: `wlsly`
    -   Nutzer: `wlsly`

## Hinweise zur CLI-Nutzung

-   Versionsprüfung: `wlycli -v` oder `wlycli --version` (Flag). Einen Unterbefehl `version` gibt es nicht.
-   Verwende `--base-dir` nur, wenn du einen anderen Speicherort als den Standard willst (Standard ist `~/.welley`). Die CLI legt das Verzeichnis an, wenn sie die Berechtigung dazu hat.
-   Nicht interaktive SSH-Läufe sollten `--email` und alle erforderlichen Flags enthalten, damit keine Rückfragen kommen.
-   Wenn der Nutzer eine CLI-Binärdatei auf dem Server bereitstellt (z. B. `~/wcli`), verwende sie direkt. Verschiebe sie nicht, wenn niemand darum bittet.
-   **Kopiere Snippets wörtlich** aus `references/digitalocean.md` für alle DO-API-Aufrufe. Schreibe das Python-Parsing nicht um, passe es nicht an und improvisiere nicht: Schon kleine Änderungen (z. B. beim Escaping oder bei Anführungszeichen) können je nach Shell fehlschlagen.
-   **Remote-Skripte per SSH:** Verwende immer `ssh host 'bash -s' << 'EOF'` (Heredoc in Anführungszeichen). Verwende nie `ssh host "..."` mit doppelten Anführungszeichen, denn zsh wertet `$(...)` und `[...]` lokal aus, bevor der Befehl an den entfernten Host gesendet wird.

## Wiederherstellung nach Fehlern

Wenn `deployment create` fehlschlägt und du es erneut versuchen musst:

-   Räume mit `wlycli deployment delete --domain <domain> --yes-please-delete-all-the-data` auf.
-   Delete versucht, Dateien, die root gehören, über Container zu entfernen. Wenn das trotzdem fehlschlägt, entferne das Projektverzeichnis unter `--base-dir` mit erhöhten Rechten und führe den Befehl erneut aus.

## Vorab-Test für Port 80 (optional)

Wenn certbot fehlschlägt, teste vor einem neuen Versuch, ob Port 80 erreichbar ist:

```
docker run --rm -d -p 80:80 --name test-http nginx:alpine
curl -s http://<domain>:80
docker rm -f test-http
```

## Weitere Dokumente

-   [**Welley CLI** docs/index.md](https://welley.com/de/administrators/docs/cli/)
-   [**Schnellstart: Minimales Welley-Deployment** docs/quick-start.md](https://welley.com/de/administrators/docs/quick-start/)
-   [**openai.yaml** skills/deploy-server/agents/openai.yaml](https://welley.com/de/administrators/docs/deploy-server-openai-yaml/)
-   [**DigitalOcean-Referenz** skills/deploy-server/references/digitalocean.md](https://welley.com/de/administrators/docs/deploy-server-digitalocean/)
