From d712f8696ac264544b903ec002f4ebd435042377 Mon Sep 17 00:00:00 2001 From: Clawd Date: Sat, 11 Apr 2026 22:22:41 -0700 Subject: Add setup.sh companion script for ship-setup skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracts server provisioning logic into a proper bash script rather than ad-hoc SSH commands in skill prose. Mirrors the behavior of the original `ship host init` Go CLI code exactly — Caddy install, directory structure, Caddyfile config, all idempotent. Updates SKILL.md to reference the script instead of inlining commands. Co-Authored-By: Claude Sonnet 4.6 --- skills/ship-setup/SKILL.md | 93 ++++++++++------------------ skills/ship-setup/setup.sh | 147 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+), 62 deletions(-) create mode 100644 skills/ship-setup/setup.sh diff --git a/skills/ship-setup/SKILL.md b/skills/ship-setup/SKILL.md index e3d08ee..73297b6 100644 --- a/skills/ship-setup/SKILL.md +++ b/skills/ship-setup/SKILL.md @@ -8,11 +8,21 @@ argument-hint: "[host-nickname]" Configure ship and prepare a VPS for deployments. +## Companion Script + +Server setup is handled by `setup.sh` in this skill's directory. It is idempotent, +handles all branching logic, and mirrors the behavior of the original `ship host init` +Go CLI command. + +``` +skills/ship-setup/setup.sh [--default] +``` + ## Config File -All ship skills read from `~/.config/ship/config.json`. This skill creates or updates it. +All ship skills read from `~/.config/ship/config.json`. The setup script creates or +updates it — never overwrites the whole file. -Structure: ```json { "default": "prod", @@ -43,80 +53,39 @@ If it exists, show the user the current hosts so they know what's already config ### 2. Get host details -If no nickname was provided as an argument, ask the user: -- **Nickname** — a short name for this host (e.g. `prod`, `staging`, `vps`) -- **SSH connection string** — e.g. `ubuntu@1.2.3.4` or an SSH config alias like `alaskav6` +If not already known, ask the user: +- **Nickname** — short name for this host (e.g. `prod`, `staging`) +- **SSH connection string** — e.g. `ubuntu@1.2.3.4` or an alias from `~/.ssh/config` - **Base domain** — the domain pointing to this server (e.g. `example.com`) +- **Default?** — if this is the first host, make it default. Otherwise ask. -If this is the first host, ask if it should be the default. If hosts already exist, ask if this should replace the current default. - -### 3. Test SSH connection - -Verify the connection works before saving anything: - -```bash -ssh -o ConnectTimeout=5 "echo ok" -``` - -If it fails, tell the user and stop. Don't save config for an unreachable host. - -### 4. Save config - -Write or update `~/.config/ship/config.json` with the new host. Merge with existing hosts — never overwrite the whole file. Use Python to safely read/write JSON: - -```bash -python3 -c " -import json, os -path = os.path.expanduser('~/.config/ship/config.json') -os.makedirs(os.path.dirname(path), exist_ok=True) -cfg = json.load(open(path)) if os.path.exists(path) else {'default': None, 'hosts': {}} -cfg['hosts'][''] = {'host': '', 'domain': ''} -if not cfg['default']: - cfg['default'] = '' -json.dump(cfg, open(path, 'w'), indent=2) -print('saved') -" -``` - -### 5. Install server dependencies - -SSH in and ensure the required directories and software exist. This is idempotent — safe to run multiple times. - -**Install Caddy** (if not present): -```bash -ssh "which caddy || (sudo apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl && curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg && curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list && sudo apt-get update && sudo apt-get install -y caddy)" -``` +### 3. Run the setup script -**Create directory structure:** ```bash -ssh "sudo mkdir -p /etc/ship/env /etc/ship/ports /etc/caddy/sites-enabled /var/www && sudo chmod 755 /etc/ship" +bash ~/.claude/skills/ship-setup/setup.sh [--default] ``` -**Configure main Caddyfile** (only if not already set up): -```bash -ssh "sudo test -f /etc/caddy/Caddyfile && echo exists || echo '{ -} - -import /etc/caddy/sites-enabled/*' | sudo tee /etc/caddy/Caddyfile" -``` - -**Enable and start Caddy:** -```bash -ssh "sudo systemctl enable caddy && sudo systemctl start caddy" -``` +The script will: +1. Test the SSH connection — stops early if it fails +2. Detect OS (Ubuntu/Debian only) +3. Install Caddy if not present +4. Configure the main Caddyfile if not already set up +5. Create required directories (`/etc/ship/env`, `/etc/ship/ports`, `/etc/caddy/sites-enabled`, `/var/www`) +6. Enable and start Caddy +7. Save the host to `~/.config/ship/config.json` -### 6. Confirm +### 4. Confirm Tell the user: - Host nickname and SSH target saved - Whether it's the default host - That the server is ready for deployments -- How to add another host: `/ship-setup ` +- How to add another host: `/ship-setup` - How to deploy: `/ship-deploy` ## Notes -- Never overwrite the entire config file — always merge -- If a nickname already exists in config, confirm before overwriting it -- The SSH host can be an alias from `~/.ssh/config` — no need to require raw IP +- Safe to run multiple times — all steps are idempotent +- The SSH host can be an alias from `~/.ssh/config` +- If a nickname already exists in config, the script overwrites it — confirm with the user first - Default host is used by all other ship skills when no host is specified diff --git a/skills/ship-setup/setup.sh b/skills/ship-setup/setup.sh new file mode 100644 index 0000000..4bb5a82 --- /dev/null +++ b/skills/ship-setup/setup.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +# ship-setup: Configure a VPS for ship deployments. +# Usage: ./setup.sh [--default] +# +# - Idempotent: safe to run multiple times +# - Mirrors the behavior of `ship host init` from the Go CLI +# - Updates ~/.config/ship/config.json with the new host + +set -euo pipefail + +# ── Args ──────────────────────────────────────────────────────────────────── + +if [ $# -lt 3 ]; then + echo "Usage: $0 [--default]" + echo " ssh-host: SSH connection string or alias (e.g. ubuntu@1.2.3.4 or alaskav6)" + echo " base-domain: Base domain for this server (e.g. example.com)" + echo " nickname: Short name for this host (e.g. prod, staging)" + echo " --default: Make this the default host" + exit 1 +fi + +SSH_HOST="$1" +DOMAIN="$2" +NICKNAME="$3" +MAKE_DEFAULT=false +if [ "${4:-}" = "--default" ]; then + MAKE_DEFAULT=true +fi + +CONFIG_FILE="$HOME/.config/ship/config.json" + +# ── Step 1: Test SSH connection ────────────────────────────────────────────── + +echo "→ Testing SSH connection to $SSH_HOST..." +if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "$SSH_HOST" "echo ok" &>/dev/null; then + echo "✗ SSH connection failed. Make sure your key is authorized:" + echo " ssh-copy-id $SSH_HOST" + exit 1 +fi +echo " ✓ Connected" + +# ── Step 2: Detect OS ──────────────────────────────────────────────────────── + +echo "→ Detecting OS..." +OS_RELEASE=$(ssh "$SSH_HOST" "cat /etc/os-release") +if ! echo "$OS_RELEASE" | grep -qE "Ubuntu|Debian"; then + echo "✗ Unsupported OS. Only Ubuntu and Debian are supported." + exit 1 +fi +echo " ✓ OS supported" + +# ── Step 3: Install Caddy (if not present) ─────────────────────────────────── + +echo "→ Checking Caddy..." +if ssh "$SSH_HOST" "which caddy" &>/dev/null; then + echo " ✓ Caddy already installed" +else + echo " Installing Caddy..." + ssh "$SSH_HOST" "sudo apt-get update -qq && \ + sudo apt-get install -y -qq debian-keyring debian-archive-keyring apt-transport-https curl gnupg && \ + curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' -o /tmp/caddy.gpg && \ + sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg < /tmp/caddy.gpg && \ + curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list && \ + sudo apt-get update -qq && \ + sudo apt-get install -y -qq caddy" + echo " ✓ Caddy installed" +fi + +# ── Step 4: Configure Caddyfile (only if not already set up) ───────────────── + +echo "→ Checking Caddyfile..." +if ssh "$SSH_HOST" "sudo test -f /etc/caddy/Caddyfile && sudo grep -q 'sites-enabled' /etc/caddy/Caddyfile" 2>/dev/null; then + echo " ✓ Caddyfile already configured" +else + echo " Writing Caddyfile..." + ssh "$SSH_HOST" "echo '{ +} + +import /etc/caddy/sites-enabled/*' | sudo tee /etc/caddy/Caddyfile > /dev/null" + echo " ✓ Caddyfile configured" +fi + +# ── Step 5: Create directory structure ─────────────────────────────────────── + +echo "→ Creating directories..." +ssh "$SSH_HOST" "sudo mkdir -p \ + /etc/ship/env \ + /etc/ship/ports \ + /etc/ship/ttl \ + /etc/ship/overrides \ + /etc/caddy/sites-enabled \ + /var/www && \ + sudo chmod 755 /etc/ship" +echo " ✓ Directories ready" + +# ── Step 6: Enable and start Caddy ─────────────────────────────────────────── + +echo "→ Enabling Caddy..." +ssh "$SSH_HOST" "sudo systemctl enable caddy && sudo systemctl start caddy || sudo systemctl reload caddy" +echo " ✓ Caddy running" + +# ── Step 7: Save config locally ────────────────────────────────────────────── + +echo "→ Saving host config to $CONFIG_FILE..." +mkdir -p "$(dirname "$CONFIG_FILE")" + +python3 - < " -- cgit v1.2.3