skills/deploy-server/references/digitalocean.md

Referencia DigitalOcean

Všetky úryvky používajú curl + python3 (bez externých závislostí). Pred spustením nastavte DO_TOKEN.

Kompatibilita shellu: Všetky argumenty python3 -c používajú jednoduché úvodzovky, takže ani bash, ani zsh nerozbalia premenné. Kód v Pythone používa iba dvojité úvodzovky.

Nastavenie DO_TOKEN: V zsh DO_TOKEN=... curl ... na jednom riadku NEFUNGUJE, pretože $DO_TOKEN sa rozbalí skôr, než sa priradenie uplatní. Použite jednu z týchto možností:

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

Spracovanie chýb: Všetky úryvky skontrolujú chyby DO API skôr, než začnú pracovať s dátami. Chyby DO majú formát {"id": "...", "message": "..."}.

Veľkosti inštancií

Zobrazenie a filtrovanie dostupných veľkostí:

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")
'

Podľa potreby upravte region, vcpus a mem_gb. Ak chcete hľadať minimálnu zhodu, použite >= namiesto ==.

Vyhľadanie obrazov

Zistenie slugu najnovšieho 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}")
'

Vyhľadanie regiónov

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

  • Región: nyc3
  • Obraz: najnovšie Ubuntu LTS dostupné cez API
  • Plán: Basic / Regular (Shared CPU)
  • Ukážková veľkosť: 2 vCPU / 4 GB RAM / 80 GB SSD / 4 TB prenosu (približne 24 USD mesačne)
  • Názov: wlsly-<adjective>-<surname> (vygenerovaný generátorom názvov nižšie)

Kľúče SSH

Zobrazenie kľúčov:

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}")
'

Nahratie nového kľúča:

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

Zobrazenie projektov:

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}")
'

Priradenie zdrojov 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 zdrojov projektu

  • Droplet: do:droplet:<id>
  • Spravovaná databáza: do:dbaas:<id>

Generátor názvov

Vygenerovanie náhodného názvu v štýle Dockeru s predponou wlsly- pre droplety a databázy:

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"

Rovnaký vygenerovaný názov použite pre droplet aj pre jeho spravovanú databázu (ak ju vytvárate).

Vytvorenie 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"])
'

Čakanie na IP adresu dropletu

Droplet nemá IP adresu hneď. Dopytujte sa, kým nebude 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é cez DigitalOcean)

Ak DNS spravuje DigitalOcean, agent môže po pridelení IP adresy dropletu vytvoriť alebo upraviť záznamy cez API.

Kontrola, či doména už v DO 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"])
'

Vytvorenie domény (iba raz, ak sa nenašla):

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

Zobrazenie existujúcich záznamov 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}")
'

Vytvorenie záznamu A pre koreňovú 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"

Vytvorenie záznamu A pre subdoménu storage (povinné, ak nepoužívate 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"

Úprava existujúceho záznamu (ak záznam už 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>"

Skôr než spustíte certbot v režime standalone, uistite sa, že záznamy A/AAAA pre <domain> (a pre storage.<domain>, ak ho používate) smerujú na IP adresu dropletu. Prepínače CLI: --public-ip (prepíše zistenú IP adresu) a --skip-dns-preflight (preskočí kontrolu). Ak pri dočasnom nastavení musíte použiť samopodpísané certifikáty, zadajte --self-signed-certs (vygeneruje samopodpísané certifikáty).

Poznámky k DNS

Ak nie je nastavené CDN, platforma používa storage.<domain>. Použite záznam A/AAAA na IP adresu dropletu alebo CNAME na koreňovú doménu, ak to váš poskytovateľ DNS umožňuje.

Príprava hostiteľa (SSH)

Dôležité: Viacriadkové skripty spúšťajte na vzdialenom serveri vždy cez ssh host 'bash -s' << 'EOF' (heredoc v úvodzovkách). Skripty nikdy nepredávajte ako argumenty v dvojitých úvodzovkách ssh host "...". Lokálny shell (hlavne zsh) by totiž rozbalil $(...), vzory [...] aj $VARIABLES ešte pred odoslaním na vzdialený server.

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 sa prepnite na používateľa bez oprávnení root:

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

Spravovaný Postgres (DigitalOcean)

Prihlasovacie údaje poznáte až po vytvorení databázy. Najprv databázu vytvorte a potom si prečítajte údaje o pripojení.

Vytvorenie:

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"])
'

Čakanie, kým databáza nebude online (trvá 3 až 5 minút):

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ískanie hesla používateľa databázy (heslo je dostupné iba z tohto 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átené údaje (host, port, databázu, používateľa a heslo) zadajte pomocou prepínačov: --postgres-host, --postgres-port, --postgres-database, --postgres-user, --postgres-password.

Ak heslo chýba alebo vypršalo, resetujte autentifikáciu používateľa databázy.

Nasadenie cez Welley CLI (príklady)

Základný príklad s tajnými údajmi:

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>

Pred spustením certbotu sa uistite, že záznamy DNS A/AAAA pre <domain> (a pre storage.<domain>, ak ho používate) smerujú na IP adresu dropletu.

Ďalšie dokumenty