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.

Deploy Server

Overview

This skill provisions a single-instance Welley stack on DigitalOcean and can run updates over SSH. It guides the agent to gather missing inputs, fetch available instance specs from a defined source, create the VM, configure the host, deploy with wlycli deployment create, and validate.

Command names must match the CLI:

  • wlycli deployment create
  • wlycli deployment update
  • wlycli deployment delete

Defaults (DigitalOcean)

  • Region: nyc3
  • Image: latest LTS available via API (resolve via images?type=distribution)
  • Droplet name: randomly generated as wlsly-<adjective>-<surname> (e.g., wlsly-bright-hopper). Use the name generator snippet from references/digitalocean.md. Use the same name for the managed DB if created.
  • Plan target: Basic / Regular (Shared CPU)
  • Size target: 2 vCPU / 4 GB RAM / 80 GB SSD / 4 TB transfer (~$24/mo)

Default Override Rule

If the user specifies any of the defaults (region, image, droplet name, project, size), the agent must:

  1. Echo the override back verbatim.
  2. Ask for confirmation before proceeding.

Interactive Input Template

Ask only what’s missing. Use these exact prompts to reduce ambiguity. Ask in iterative rounds. Round 1: max 2 questions per turn. Round 2 may present a bundled “accept all defaults” option. If the runtime supports choices, present 2–3 options per question (A/B/C). Avoid free-form unless necessary.

Round 1 questions (identity + access):

  1. “Do you have a DigitalOcean API token to use, or should I wait for you to provide one?”
  2. “Which domain should this server use (e.g., super-puper.example.com)? Do you control its DNS?”
  3. “Do you already have an SSH public key to use, or should I create and upload one?”
  4. “Do you want to assign this droplet to an existing DigitalOcean project? If yes, provide the project name or ID.”
  5. “DNS requirement: you must create A/AAAA records for <domain> and storage.<domain> (if no CDN). Can you do that before certbot runs?”
  6. “Email DNS requirement: the --smtp-from domain needs SPF, DMARC and DKIM records, or invitation and confirmation emails to new recipients may be silently dropped. Are they in place, or should I list the records your SMTP provider needs?”
  7. If the user asks where to get a DO token, provide this link:
https://cloud.digitalocean.com/account/api/tokens

Round 2 questions (sizing + location + deploy options):

  1. “Do you want the default region nyc3, or a different region?”
  2. “Do you want the default size (2 vCPU / 2 GB), or a different CPU/RAM target?”
  3. “Should I use the latest Ubuntu LTS available in the API, or a specific Ubuntu version?”
  4. “Which SSH user should we use after bootstrap (default: wlsly)? You can keep the default.”
  5. “Do you want a managed Postgres DB, or use the bundled Postgres container?”
  6. If managed Postgres is chosen: “Which DB region and size should I use? I can default to nyc3 and 1 vCPU / 2 GB.”
  7. If managed Postgres is chosen: “Do you want me to proceed with creation at the provider’s listed monthly price?”
  8. “Please provide required SMTP settings (host, user, password, from). OTEL is optional — provide it only if needed.”
  9. “Admin email is required for create. Provide it now (flag: --email / -e). If not provided, I will prompt twice to confirm it.”

Offer an “accept all defaults” option in Round 2 to speed up input collection.

If any answer is missing, ask a focused follow-up (one or two questions).

Workflow (DigitalOcean, single instance)

  1. Collect required inputs (ask if missing).
  • DigitalOcean API token
  • Project name or ID (optional; if missing or not found, use default project)
  • Region
  • Instance size target (e.g., 4 vCPU / 16 GB RAM)
  • Domain name
  • SSH public key (or confirmation to create and register one)
  • Admin email for first login and password recovery (required for create; --email / -e). If missing, prompt twice and confirm match.
  • Optional: custom TLS certs vs certbot
  • DNS access confirmation (must be able to create A/AAAA records for <domain> and storage.<domain> if used)
  1. Fetch instance types and pick a size.
  • Use the sizes snippet from references/digitalocean.md to list and filter by CPU/RAM.
  • Confirm with the user if multiple matches.

2b. Resolve Ubuntu image slug.

  • Call GET /v2/images?type=distribution&per_page=200 and filter for Ubuntu.
  • If the user asked for a specific version, verify its slug exists and confirm.
  • Otherwise pick the latest LTS slug from the list and confirm with the user before creation.
  1. Create the droplet.
  • Use the DigitalOcean API directly (doctl is optional).
  • Image: prefer user-specified; otherwise pick the latest LTS from the API list.
  • Attach the SSH key fingerprint or create and upload a key.
  • Enable monitoring.
  • If project name/ID was provided but not found, create the droplet without a project (default project).
  • Note: Managed DB creation can run in parallel with droplet creation.

Parallelization Guidance

The agent should overlap independent tasks to reduce wall‑clock time:

  • While gathering user inputs (Round 2), start droplet creation if token, region, size, image, and SSH key are already confirmed.
  • While droplet is provisioning, start managed Postgres creation (if requested).
  • While droplet is starting, continue collecting remaining inputs (SMTP, OTEL, admin email).
  • If any critical input is missing for a step, pause that step and continue with other parallelizable tasks.
  • If DNS is managed in DigitalOcean, create/update A/AAAA records as soon as the droplet IP is available (do not wait for the full deployment step).
  1. DNS / domain.
  • Ask whether to create or update DNS in DigitalOcean or leave external DNS.
  • If using DigitalOcean DNS, create A/AAAA records to the droplet IP.
  1. Provision the host via SSH.
  • Create a non-root user (e.g., wlsly), grant sudo, and copy SSH authorized keys.
  • Exit root and re-login as the non-root user before continuing.
  • Install Docker + docker compose.
  • Fetch the Welley CLI binary from AppStore and make it executable:
    • Base URL: https://appstore.allroads.social/v1/social.wellesley.wlycli/latest?develop=stable&platform=<os>/<arch>
    • Choose the correct platform value by OS/arch:
      • Linux x86_64: linux/amd64
      • Linux arm64: linux/arm64
      • macOS x86_64: darwin/amd64
      • macOS arm64: darwin/arm64
      • Windows x86_64: windows/amd64
    • Example (Linux x86_64):
      curl -fL 'https://appstore.allroads.social/v1/social.wellesley.wlycli/latest?develop=stable&platform=linux/amd64' -o wlycli
      chmod +x wlycli
      
    • Expected response headers include:
      • x-artifact-display-name: Welley CLI
      • x-artifact-full-name: social.wellesley.wlycli
      • x-artifact-kind: cli
      • x-artifact-platform: <os>/<arch> such as linux/amd64
      • x-artifact-sha256: <sha256>
      • x-artifact-version: <version>
      • x-channel: develop
    • Before installing, prefer checking headers with a HEAD request and verify that artifact name, kind, platform, and channel match the requested binary.
  • Verify CLI binary: run wlycli -v and wlycli deployment --help to confirm expected build info.
  1. Deploy Welley.
  • Run wlycli deployment create with the required options.
  • Use single-instance defaults (no external Postgres unless user asks).
  • If managed Postgres is requested, create it first (DigitalOcean DB) and then use the returned host/port/db/user/password in --postgres-* flags.
  • If the DB user password is not returned by the API/UI, reset the DB user auth to obtain fresh credentials before running wlycli.
  • Non-interactive runs must pass --email / -e or the command will fail.
  • --base-dir points to the parent directory; the CLI creates the project subdirectory automatically.
  • The CLI refuses to run if the project directory or containers already exist; cleanup is required before retrying.
  1. Certificates (mandatory).
  • Default: use Certbot standalone (non-interactive).
  • User must point DNS A/AAAA records for the domain (and storage.<domain> if used) to the droplet IP before certbot runs.
  • CLI supports --public-ip (override detected IP) and --skip-dns-preflight if you must bypass the check.
  • Never assume DNS is configured. Ask and wait for explicit confirmation.
  • Never use --skip-dns-preflight unless the user explicitly requests it.
  • If CDN is not configured, certbot must include storage.<domain> in the certificate request.
  • Use self-signed certificates only if the user explicitly asks and pass --self-signed-certs (generates self-signed certs in Go).
  • Provide the user with DNS setup instructions and wait for confirmation:
  • “Create A/AAAA records so <domain> resolves to the droplet IP.”
  • “If storage.<domain> is used, create an A/AAAA record for it too.”
  • “Once DNS propagates, tell me and I’ll continue. I cannot observe propagation unless you ask me to verify resolution.”
  • The agent may verify DNS propagation itself using ping -c 1 <domain> and compare the IP to the droplet. If it matches, proceed.

7b. Email DNS (SPF / DMARC / DKIM).

  • Mail from a domain without SPF and DMARC is often dropped by Gmail and Outlook for first-time recipients, even though the SMTP provider reports it as delivered.
  • deployment create prints a Warning: when the --smtp-from domain has no SPF record, or no DMARC record at _dmarc.<domain> or a parent domain. It does not block the deploy. Never ignore the warning: show it to the user and give them the records to add:
    • <mail-domain> TXT v=spf1 include:<provider> ~all (the exact include comes from the SMTP provider; merge into an existing SPF record, never add a second one)
    • _dmarc.<mail-domain> TXT v=DMARC1; p=none; rua=mailto:dmarc@<mail-domain>
    • DKIM: the <selector>._domainkey.<mail-domain> records the SMTP provider generates when the domain is verified
  • Check with dig +short TXT <mail-domain> and dig +short TXT _dmarc.<mail-domain>.
  • The CLI cannot check DKIM (the selector is provider-specific). Always ask the user to confirm DKIM is verified at the SMTP provider, even when create printed no warning. With Amazon SES and similar providers, SPF is checked for the provider's own envelope domain, so only DKIM can make DMARC pass. In SES: Verified identities → <mail-domain> → DKIM must read Successful.
  1. Validate and report.
  • Check docker ps and service health endpoints.
  • Provide final connection details and next steps.

SSH Update Flow

When user asks for “update server”:

  1. Ask for target host/IP and SSH user/key.
  2. Run wlycli deployment update --domain <domain> [--force-restart].
  3. Verify containers and Nginx status.

Resources

  • DigitalOcean API snippets and examples: references/digitalocean.md.

Secrets to Flags

Use these flag names for secrets:

  • SMTP host: --smtp-host
  • SMTP user: --smtp-user
  • SMTP password: --smtp-password
  • SMTP from: --smtp-from
  • OTEL endpoint: --otel-endpoint
  • OTEL headers: --otel-headers

Minimal Create Command

Required flags:

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

Minimal example:

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

Managed Postgres Defaults (DigitalOcean vs CLI)

When using DigitalOcean managed Postgres, do not rely on CLI defaults. Set all --postgres-* flags explicitly:

  • DO typical defaults:
    • Port: 25060
    • Database: defaultdb
    • User: doadmin
  • CLI defaults (if you omit flags):
    • Port: 5432
    • Database: wlsly
    • User: wlsly

CLI Usage Notes

  • Version check: wlycli -v or wlycli --version (flag). There is no version subcommand.
  • Use --base-dir only if you want a non-default location (default is ~/.welley). The CLI will create the directory if it has permission.
  • Non-interactive SSH runs should include --email and any required flags to avoid prompts.
  • If the user provides a CLI binary on the server (e.g. ~/wcli), use it directly. Do not relocate it unless asked.
  • Copy snippets verbatim from references/digitalocean.md for all DO API calls. Do not rewrite, adapt, or improvise the Python parsing — even small changes (e.g. escaping, quoting) can break across shells.
  • SSH remote scripts: Always use ssh host 'bash -s' << 'EOF' (quoted heredoc). Never use ssh host "..." with double quotes — zsh expands $(...) and [...] locally before sending to the remote host.

Failure Recovery

If deployment create fails and you need to retry:

  • Use wlycli deployment delete --domain <domain> --yes-please-delete-all-the-data to clean up.
  • Delete will attempt container-based cleanup of root-owned files. If it still fails, remove the project directory under --base-dir with elevated permissions and re-run.

Preflight Port-80 Test (Optional)

If certbot fails, test port 80 reachability before re-running:

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

Other documents