skills/deploy-server/references/digitalocean.md

# Referenční příručka k DigitalOcean

Všechny ukázky používají `curl` + `python3` (žádné externí závislosti). Před spuštěním nastavte `DO_TOKEN`.

**Kompatibilita shellu:** Všechny argumenty `python3 -c` používají jednoduché uvozovky, takže bash ani zsh nerozbalují proměnné. Kód v Pythonu používá jen dvojité uvozovky.

**Nastavení DO\_TOKEN:** V zsh `DO_TOKEN=... curl ...` na jednom řádku NEFUNGUJE: `$DO_TOKEN` se rozbalí dřív, než se přiřazení projeví. Použijte jednu z těchto možností:

```
export DO_TOKEN=...          # then run snippets normally
DO_TOKEN=...; curl ...       # semicolon separates assignment from command
```

**Zpracování chyb:** Všechny ukázky před přístupem k datům kontrolují chyby DO API. Chyby DO mají formát `{"id": "...", "message": "..."}`.

## Velikosti instancí

Výpis a filtrování dostupných velikostí:

```
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
  "https://api.digitalocean.com/v2/sizes?per_page=200" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "sizes" not in data:
    print("Error:", data.get("message", data)); sys.exit(1)
region, vcpus, mem_gb = "nyc3", 2, 2
for s in sorted(data["sizes"], key=lambda x: (x["vcpus"], x["memory"], x["price_monthly"])):
    if not s["available"] or region not in s["regions"]:
        continue
    if s["vcpus"] == vcpus and s["memory"] == mem_gb * 1024:
        slug, vc, mem, disk, price = s["slug"], s["vcpus"], s["memory"]/1024, s["disk"], s["price_monthly"]
        print(f"{slug:30s} {vc}vCPU {mem:.0f}GB {disk}GB ${price}/mo")
'
```

Upravte `region`, `vcpus` a `mem_gb` podle potřeby. Pokud chcete najít velikosti s alespoň těmito hodnotami, použijte `>=` místo `==`.

## Zjištění image

Zjistěte slug nejnovější Ubuntu LTS:

```
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
  "https://api.digitalocean.com/v2/images?type=distribution&per_page=200" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "images" not in data:
    print("Error:", data.get("message", data)); sys.exit(1)
ubuntu = [i for i in data["images"] if i["distribution"] == "Ubuntu" and "LTS" in i.get("description", "")]
ubuntu.sort(key=lambda x: x["slug"], reverse=True)
for i in ubuntu[:5]:
    slug, desc = i["slug"], i["description"]
    print(f"{slug:30s} {desc}")
'
```

## Zjištění regionů

```
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
  "https://api.digitalocean.com/v2/regions" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "regions" not in data:
    print("Error:", data.get("message", data)); sys.exit(1)
for r in data["regions"]:
    if r["available"]:
        slug, name = r["slug"], r["name"]
        print(f"{slug:10s} {name}")
'
```

## Základní profil dropletu

-   Region: `nyc3`
-   Image: nejnovější Ubuntu LTS dostupné přes API
-   Plán: Basic / Regular (Shared CPU)
-   Příklad velikosti: 2 vCPU / 4 GB RAM / 80 GB SSD / 4 TB přenosu (přibližně $24 měsíčně)
-   Název: `wlsly-<adjective>-<surname>` (vygenerovaný pomocí generátoru názvů níže)

## Klíče SSH

Výpis klíčů:

```
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
  "https://api.digitalocean.com/v2/account/keys?per_page=200" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "ssh_keys" not in data:
    print("Error:", data.get("message", data)); sys.exit(1)
for k in data["ssh_keys"]:
    kid, name, fp = k["id"], k["name"], k["fingerprint"]
    print(f"{kid:>10}  {name:30s}  {fp}")
'
```

Nahrání nového klíče:

```
curl -sS -X POST \
  -H "Authorization: Bearer $DO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "wlycli", "public_key": "'"$(cat ~/.ssh/wlsly_do.pub)"'" }' \
  "https://api.digitalocean.com/v2/account/keys"
```

## Projekty

Výpis projektů:

```
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
  "https://api.digitalocean.com/v2/projects?per_page=200" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "projects" not in data:
    print("Error:", data.get("message", data)); sys.exit(1)
for p in data["projects"]:
    default = " (default)" if p.get("is_default") else ""
    pid, name = p["id"], p["name"]
    print(f"{pid}  {name}{default}")
'
```

Přiřazení prostředků k projektu:

```
curl -sS -X POST \
  -H "Authorization: Bearer $DO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "resources": ["do:droplet:<droplet-id>", "do:dbaas:<db-id>"] }' \
  "https://api.digitalocean.com/v2/projects/<project-id>/resources"
```

## URN prostředků projektu

-   Droplet: `do:droplet:<id>`
-   Spravovaná databáze: `do:dbaas:<id>`

## Generátor názvů

Vygenerujte náhodný název ve stylu Dockeru s předponou `wlsly-` pro droplety a databáze:

```
WLSLY_NAME=wlsly-$(shuf -n1 -e agile bold brave bright calm clever cool daring eager fair fast fierce fond frank gifted glad grand happy hardy keen kind lively lucid merry modest noble pensive proud quick quiet sharp shrewd sleek smart solid steady stoic sunny swift tender vivid warm wise witty zen)-$(shuf -n1 -e agnesi babbage bell bohr brahmagupta buck carson chatelet chebyshev curie darwin dijkstra easley einstein euler fermat feynman galois gauss goldberg goodall hamilton hawking hodgkin hopper hypatia jackson johnson kare keller knuth leavitt lovelace lumiere mahavira mandela mccarthy meitner mendel mirzakhani morse nash neumann nightingale noether northcutt panini pare pascal pasteur pike poincare ramanujan ride ritchie rosalind sagan shannon snyder swartz tesla thompson turing villani wescoff wing wozniak wright yalow)
echo "$WLSLY_NAME"
```

Pro droplet i jeho spravovanou databázi (pokud ji vytváříte) použijte stejný vygenerovaný název.

## Vytvoření dropletu

```
curl -sS -X POST \
  -H "Authorization: Bearer $DO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name":"'"$WLSLY_NAME"'",
    "region":"<region>",
    "size":"<size-slug>",
    "image":"<ubuntu-image-slug>",
    "ssh_keys":["<fingerprint>"],
    "monitoring":true,
    "tags":["wlsly"]
  }' \
  "https://api.digitalocean.com/v2/droplets" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "droplet" not in data:
    print("Error:", data.get("message", data)); sys.exit(1)
d = data["droplet"]
print("Droplet ID:", d["id"])
print("Name:", d["name"])
print("Status:", d["status"])
'
```

## Zjištění IP adresy dropletu

Droplet nemá IP adresu hned. Dotazujte se, dokud není `status=active`:

```
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
  "https://api.digitalocean.com/v2/droplets/<droplet-id>" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "droplet" not in data:
    print("Error:", data.get("message", data)); sys.exit(1)
d = data["droplet"]
print("Status:", d["status"])
for net in d.get("networks", {}).get("v4", []):
    print("IPv4 (%s): %s" % (net["type"], net["ip_address"]))
'
```

## DNS (spravované DigitalOcean)

Pokud DNS spravuje DigitalOcean, agent může po přidělení IP adresy dropletu vytvářet a aktualizovat záznamy přes API.

**Zkontrolujte, zda doména v DO už existuje:**

```
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
  "https://api.digitalocean.com/v2/domains/<domain>" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "domain" in data:
    print("Domain: %s (exists)" % data["domain"]["name"])
else:
    print("Domain not found - needs to be created")
    if "message" in data:
        print("API:", data["message"])
'
```

**Vytvoření domény (jen jednou, pokud nebyla nalezena):**

```
curl -sS -X POST \
  -H "Authorization: Bearer $DO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"<domain>"}' \
  "https://api.digitalocean.com/v2/domains"
```

**Výpis existujících záznamů domény:**

```
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
  "https://api.digitalocean.com/v2/domains/<domain>/records?per_page=200" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "domain_records" not in data:
    print("Error:", data.get("message", data)); sys.exit(1)
for r in data["domain_records"]:
    rid, rtype, name, rdata = r["id"], r["type"], r["name"], r["data"]
    print(f"{rid:>10}  {rtype:6s}  {name:30s}  {rdata}")
'
```

**Vytvoření záznamu A pro kořenovou doménu (`@`):**

```
curl -sS -X POST \
  -H "Authorization: Bearer $DO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"type":"A","name":"@","data":"<droplet-ip>","ttl":300}' \
  "https://api.digitalocean.com/v2/domains/<domain>/records"
```

**Vytvoření záznamu A pro subdoménu `storage` (nutné, pokud nepoužíváte CDN):**

```
curl -sS -X POST \
  -H "Authorization: Bearer $DO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"type":"A","name":"storage","data":"<droplet-ip>","ttl":300}' \
  "https://api.digitalocean.com/v2/domains/<domain>/records"
```

**Aktualizace existujícího záznamu (pokud už záznam existuje):**

```
curl -sS -X PUT \
  -H "Authorization: Bearer $DO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"data":"<droplet-ip>"}' \
  "https://api.digitalocean.com/v2/domains/<domain>/records/<record-id>"
```

Než se spustí certbot v režimu standalone, ujistěte se, že záznamy A/AAAA pro `<domain>` (a `storage.<domain>`, pokud ho používáte) ukazují na IP adresu dropletu. Příznaky CLI: `--public-ip` (přepíše zjištěnou IP adresu) a `--skip-dns-preflight` (přeskočí kontrolu). Pokud pro dočasné nastavení musíte použít self-signed certifikáty, přidejte `--self-signed-certs` (vygeneruje self-signed certifikáty).

## Poznámky k DNS

Pokud není nakonfigurováno žádné CDN, platforma používá `storage.<domain>`. Použijte A/AAAA na IP adresu dropletu nebo CNAME na kořenovou doménu, pokud to váš poskytovatel DNS umožňuje.

## Příprava hostitele (SSH)

**Důležité:** Víceřádkové skripty na vzdáleném hostiteli vždy spouštějte pomocí `ssh host 'bash -s' << 'EOF'` (heredoc v uvozovkách). Nikdy neposílejte skripty jako argumenty `ssh host "..."` ve dvojitých uvozovkách: místní shell (zejména zsh) rozbalí `$(...)`, vzory `[...]` a `$VARIABLES` dřív, než se odešlou na vzdáleného hostitele.

```
ssh -o StrictHostKeyChecking=accept-new root@<droplet-ip> 'bash -s' << 'PROVISION'
set -euo pipefail

adduser --disabled-password --gecos "Welley Service" wlsly
usermod -aG sudo wlsly
echo "wlsly ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/wlsly
chmod 440 /etc/sudoers.d/wlsly

mkdir -p /home/wlsly/.ssh
cp /root/.ssh/authorized_keys /home/wlsly/.ssh/authorized_keys
chown -R wlsly:wlsly /home/wlsly/.ssh
chmod 700 /home/wlsly/.ssh
chmod 600 /home/wlsly/.ssh/authorized_keys

apt-get update -qq
apt-get install -y -qq ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list
apt-get update -qq
apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

usermod -aG docker wlsly

echo "=== Verify ==="
docker --version
docker compose version
id wlsly
PROVISION
```

Potom přepněte na uživatele bez práv roota:

```
ssh -o StrictHostKeyChecking=accept-new wlsly@<droplet-ip> 'echo "Connected as $(whoami)"'
```

## Spravovaný Postgres (DigitalOcean)

Přihlašovací údaje jsou známé až _po_ vytvoření. Nejdřív vytvořte databázi, potom si přečtěte údaje o připojení.

**Vytvoření:**

```
curl -sS -X POST \
  -H "Authorization: Bearer $DO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name":"'"$WLSLY_NAME"'",
    "engine":"pg",
    "version":"16",
    "region":"nyc3",
    "size":"db-s-1vcpu-2gb",
    "num_nodes":1
  }' \
  "https://api.digitalocean.com/v2/databases" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "database" not in data:
    print("Error:", data.get("message", data)); sys.exit(1)
db = data["database"]
print("DB ID:", db["id"])
print("Status:", db["status"])
'
```

**Dotazujte se, dokud není databáze online** (trvá to 3 až 5 minut):

```
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
  "https://api.digitalocean.com/v2/databases/<db-id>" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "database" not in data:
    print("Error:", data.get("message", data)); sys.exit(1)
db = data["database"]
print("Status:", db["status"])
if db["status"] == "online":
    conn = db.get("connection", {})
    priv = db.get("private_connection", {})
    print("Host:", conn.get("host"))
    print("Private host:", priv.get("host"))
    print("Port:", conn.get("port"))
    print("Database:", conn.get("database"))
    print("User:", conn.get("user"))
'
```

**Získání hesla uživatele databáze** (heslo je dostupné jen z tohoto endpointu):

```
curl -sS -H "Authorization: Bearer $DO_TOKEN" \
  "https://api.digitalocean.com/v2/databases/<db-id>/users" | python3 -c '
import json, sys
data = json.load(sys.stdin)
if "users" not in data:
    print("Error:", data.get("message", data)); sys.exit(1)
for u in data["users"]:
    print("User:", u["name"], "Password:", u.get("password", "N/A"), "Role:", u.get("role", "N/A"))
'
```

Vrácený host, port, databázi, uživatele a heslo použijte s: `--postgres-host`, `--postgres-port`, `--postgres-database`, `--postgres-user`, `--postgres-password`.

Pokud heslo chybí nebo vypršelo, obnovte ověření uživatele databáze.

## Nasazení pomocí Welley CLI (příklady)

Základní příklad s tajnými údaji:

```
wlycli deployment create \
  --domain=example.com \
  --email=admin@example.com \
  --update-period=15min \
  --env=stage \
  --otel-endpoint=https://otlp-gateway-prod-us-east-2.grafana.net/otlp \
  --otel-use-grpc=false \
  --otel-headers='<OTEL_AUTH_HEADERS>' \
  --smtp-host=email-smtp.us-east-2.amazonaws.com \
  --smtp-user=<MAIL_USER> \
  --smtp-password=<MAIL_PASSWORD> \
  --smtp-from=noreply@mail.example.com \
  --language-service=internal \
  --translation-service=deepl \
  --translation-service-api-key=<DEEPL_KEY> \
  --with-sentry
```

Externí Postgres:

```
wlycli deployment create \
  --domain=example.com \
  --email=admin@example.com \
  --postgres-host=<DB_HOST> \
  --postgres-port=<DB_PORT> \
  --postgres-user=<DB_USER> \
  --postgres-database=<DB_NAME> \
  --postgres-password=<DB_PASSWORD>
```

Před spuštěním certbotu se ujistěte, že záznamy DNS A/AAAA pro `<domain>` (a `storage.<domain>`, pokud ho používáte) ukazují na IP adresu dropletu.

## Další dokumenty

-   [**Welley CLI** docs/index.md](https://welley.com/cs/administrators/docs/cli/)
-   [**Rychlý start: minimální nasazení Welley** docs/quick-start.md](https://welley.com/cs/administrators/docs/quick-start/)
-   [**Nasazení serveru** skills/deploy-server/SKILL.md](https://welley.com/cs/administrators/docs/deploy-server-skill/)
-   [**openai.yaml** skills/deploy-server/agents/openai.yaml](https://welley.com/cs/administrators/docs/deploy-server-openai-yaml/)
