aboutsummaryrefslogtreecommitdiffstats
path: root/skills
diff options
context:
space:
mode:
Diffstat (limited to 'skills')
-rw-r--r--skills/ship-setup/SKILL.md93
-rw-r--r--skills/ship-setup/setup.sh147
2 files changed, 178 insertions, 62 deletions
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]"
8 8
9Configure ship and prepare a VPS for deployments. 9Configure ship and prepare a VPS for deployments.
10 10
11## Companion Script
12
13Server setup is handled by `setup.sh` in this skill's directory. It is idempotent,
14handles all branching logic, and mirrors the behavior of the original `ship host init`
15Go CLI command.
16
17```
18skills/ship-setup/setup.sh <ssh-host> <base-domain> <nickname> [--default]
19```
20
11## Config File 21## Config File
12 22
13All ship skills read from `~/.config/ship/config.json`. This skill creates or updates it. 23All ship skills read from `~/.config/ship/config.json`. The setup script creates or
24updates it — never overwrites the whole file.
14 25
15Structure:
16```json 26```json
17{ 27{
18 "default": "prod", 28 "default": "prod",
@@ -43,80 +53,39 @@ If it exists, show the user the current hosts so they know what's already config
43 53
44### 2. Get host details 54### 2. Get host details
45 55
46If no nickname was provided as an argument, ask the user: 56If not already known, ask the user:
47- **Nickname** — a short name for this host (e.g. `prod`, `staging`, `vps`) 57- **Nickname** — short name for this host (e.g. `prod`, `staging`)
48- **SSH connection string** — e.g. `ubuntu@1.2.3.4` or an SSH config alias like `alaskav6` 58- **SSH connection string** — e.g. `ubuntu@1.2.3.4` or an alias from `~/.ssh/config`
49- **Base domain** — the domain pointing to this server (e.g. `example.com`) 59- **Base domain** — the domain pointing to this server (e.g. `example.com`)
60- **Default?** — if this is the first host, make it default. Otherwise ask.
50 61
51If this is the first host, ask if it should be the default. If hosts already exist, ask if this should replace the current default. 62### 3. Run the setup script
52
53### 3. Test SSH connection
54
55Verify the connection works before saving anything:
56
57```bash
58ssh -o ConnectTimeout=5 <host> "echo ok"
59```
60
61If it fails, tell the user and stop. Don't save config for an unreachable host.
62
63### 4. Save config
64
65Write 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:
66
67```bash
68python3 -c "
69import json, os
70path = os.path.expanduser('~/.config/ship/config.json')
71os.makedirs(os.path.dirname(path), exist_ok=True)
72cfg = json.load(open(path)) if os.path.exists(path) else {'default': None, 'hosts': {}}
73cfg['hosts']['<nickname>'] = {'host': '<ssh-host>', 'domain': '<domain>'}
74if not cfg['default']:
75 cfg['default'] = '<nickname>'
76json.dump(cfg, open(path, 'w'), indent=2)
77print('saved')
78"
79```
80
81### 5. Install server dependencies
82
83SSH in and ensure the required directories and software exist. This is idempotent — safe to run multiple times.
84
85**Install Caddy** (if not present):
86```bash
87ssh <host> "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)"
88```
89 63
90**Create directory structure:**
91```bash 64```bash
92ssh <host> "sudo mkdir -p /etc/ship/env /etc/ship/ports /etc/caddy/sites-enabled /var/www && sudo chmod 755 /etc/ship" 65bash ~/.claude/skills/ship-setup/setup.sh <ssh-host> <domain> <nickname> [--default]
93``` 66```
94 67
95**Configure main Caddyfile** (only if not already set up): 68The script will:
96```bash 691. Test the SSH connection — stops early if it fails
97ssh <host> "sudo test -f /etc/caddy/Caddyfile && echo exists || echo '{ 702. Detect OS (Ubuntu/Debian only)
98} 713. Install Caddy if not present
99 724. Configure the main Caddyfile if not already set up
100import /etc/caddy/sites-enabled/*' | sudo tee /etc/caddy/Caddyfile" 735. Create required directories (`/etc/ship/env`, `/etc/ship/ports`, `/etc/caddy/sites-enabled`, `/var/www`)
101``` 746. Enable and start Caddy
102 757. Save the host to `~/.config/ship/config.json`
103**Enable and start Caddy:**
104```bash
105ssh <host> "sudo systemctl enable caddy && sudo systemctl start caddy"
106```
107 76
108### 6. Confirm 77### 4. Confirm
109 78
110Tell the user: 79Tell the user:
111- Host nickname and SSH target saved 80- Host nickname and SSH target saved
112- Whether it's the default host 81- Whether it's the default host
113- That the server is ready for deployments 82- That the server is ready for deployments
114- How to add another host: `/ship-setup <nickname>` 83- How to add another host: `/ship-setup`
115- How to deploy: `/ship-deploy` 84- How to deploy: `/ship-deploy`
116 85
117## Notes 86## Notes
118 87
119- Never overwrite the entire config file — always merge 88- Safe to run multiple times — all steps are idempotent
120- If a nickname already exists in config, confirm before overwriting it 89- The SSH host can be an alias from `~/.ssh/config`
121- The SSH host can be an alias from `~/.ssh/config`no need to require raw IP 90- If a nickname already exists in config, the script overwrites it confirm with the user first
122- Default host is used by all other ship skills when no host is specified 91- 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 @@
1#!/usr/bin/env bash
2# ship-setup: Configure a VPS for ship deployments.
3# Usage: ./setup.sh <ssh-host> <base-domain> <nickname> [--default]
4#
5# - Idempotent: safe to run multiple times
6# - Mirrors the behavior of `ship host init` from the Go CLI
7# - Updates ~/.config/ship/config.json with the new host
8
9set -euo pipefail
10
11# ── Args ────────────────────────────────────────────────────────────────────
12
13if [ $# -lt 3 ]; then
14 echo "Usage: $0 <ssh-host> <base-domain> <nickname> [--default]"
15 echo " ssh-host: SSH connection string or alias (e.g. ubuntu@1.2.3.4 or alaskav6)"
16 echo " base-domain: Base domain for this server (e.g. example.com)"
17 echo " nickname: Short name for this host (e.g. prod, staging)"
18 echo " --default: Make this the default host"
19 exit 1
20fi
21
22SSH_HOST="$1"
23DOMAIN="$2"
24NICKNAME="$3"
25MAKE_DEFAULT=false
26if [ "${4:-}" = "--default" ]; then
27 MAKE_DEFAULT=true
28fi
29
30CONFIG_FILE="$HOME/.config/ship/config.json"
31
32# ── Step 1: Test SSH connection ──────────────────────────────────────────────
33
34echo "→ Testing SSH connection to $SSH_HOST..."
35if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "$SSH_HOST" "echo ok" &>/dev/null; then
36 echo "✗ SSH connection failed. Make sure your key is authorized:"
37 echo " ssh-copy-id $SSH_HOST"
38 exit 1
39fi
40echo " ✓ Connected"
41
42# ── Step 2: Detect OS ────────────────────────────────────────────────────────
43
44echo "→ Detecting OS..."
45OS_RELEASE=$(ssh "$SSH_HOST" "cat /etc/os-release")
46if ! echo "$OS_RELEASE" | grep -qE "Ubuntu|Debian"; then
47 echo "✗ Unsupported OS. Only Ubuntu and Debian are supported."
48 exit 1
49fi
50echo " ✓ OS supported"
51
52# ── Step 3: Install Caddy (if not present) ───────────────────────────────────
53
54echo "→ Checking Caddy..."
55if ssh "$SSH_HOST" "which caddy" &>/dev/null; then
56 echo " ✓ Caddy already installed"
57else
58 echo " Installing Caddy..."
59 ssh "$SSH_HOST" "sudo apt-get update -qq && \
60 sudo apt-get install -y -qq debian-keyring debian-archive-keyring apt-transport-https curl gnupg && \
61 curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' -o /tmp/caddy.gpg && \
62 sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg < /tmp/caddy.gpg && \
63 curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list && \
64 sudo apt-get update -qq && \
65 sudo apt-get install -y -qq caddy"
66 echo " ✓ Caddy installed"
67fi
68
69# ── Step 4: Configure Caddyfile (only if not already set up) ─────────────────
70
71echo "→ Checking Caddyfile..."
72if ssh "$SSH_HOST" "sudo test -f /etc/caddy/Caddyfile && sudo grep -q 'sites-enabled' /etc/caddy/Caddyfile" 2>/dev/null; then
73 echo " ✓ Caddyfile already configured"
74else
75 echo " Writing Caddyfile..."
76 ssh "$SSH_HOST" "echo '{
77}
78
79import /etc/caddy/sites-enabled/*' | sudo tee /etc/caddy/Caddyfile > /dev/null"
80 echo " ✓ Caddyfile configured"
81fi
82
83# ── Step 5: Create directory structure ───────────────────────────────────────
84
85echo "→ Creating directories..."
86ssh "$SSH_HOST" "sudo mkdir -p \
87 /etc/ship/env \
88 /etc/ship/ports \
89 /etc/ship/ttl \
90 /etc/ship/overrides \
91 /etc/caddy/sites-enabled \
92 /var/www && \
93 sudo chmod 755 /etc/ship"
94echo " ✓ Directories ready"
95
96# ── Step 6: Enable and start Caddy ───────────────────────────────────────────
97
98echo "→ Enabling Caddy..."
99ssh "$SSH_HOST" "sudo systemctl enable caddy && sudo systemctl start caddy || sudo systemctl reload caddy"
100echo " ✓ Caddy running"
101
102# ── Step 7: Save config locally ──────────────────────────────────────────────
103
104echo "→ Saving host config to $CONFIG_FILE..."
105mkdir -p "$(dirname "$CONFIG_FILE")"
106
107python3 - <<EOF
108import json, os
109
110path = os.path.expanduser("$CONFIG_FILE")
111cfg = {"default": None, "hosts": {}}
112
113if os.path.exists(path):
114 with open(path) as f:
115 cfg = json.load(f)
116
117cfg["hosts"]["$NICKNAME"] = {
118 "host": "$SSH_HOST",
119 "domain": "$DOMAIN"
120}
121
122if cfg["default"] is None or "$MAKE_DEFAULT" == "true":
123 cfg["default"] = "$NICKNAME"
124
125with open(path, "w") as f:
126 json.dump(cfg, f, indent=2)
127 f.write("\n")
128
129print(f" Hosts: {list(cfg['hosts'].keys())}")
130print(f" Default: {cfg['default']}")
131EOF
132
133echo " ✓ Config saved"
134
135# ── Done ─────────────────────────────────────────────────────────────────────
136
137echo ""
138echo "✓ $NICKNAME ($SSH_HOST) is ready for deployments"
139echo " Domain: $DOMAIN"
140if [ "$MAKE_DEFAULT" = true ]; then
141 echo " Set as default host"
142fi
143echo ""
144echo "Next steps:"
145echo " Deploy a binary: /ship-deploy"
146echo " Check what's running: /ship-status"
147echo " Add another host: ./setup.sh <host> <domain> <nickname>"