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