aboutsummaryrefslogtreecommitdiffstats
path: root/skills/ship-deploy/SKILL.md
blob: 63e76bb3b9793479fc0cb026d1a83b3f55dab540 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
---
name: ship-deploy
description: Deploy an app to a ship VPS. Orchestrates ship-binary or ship-static depending on what you're deploying. Use when the user says "ship it", "deploy", "push to prod", or wants to deploy or redeploy an app.
argument-hint: "<app-name> [host-nickname]"
---

# ship-deploy

Orchestration runbook for deploying apps to a ship VPS. Guides the full deployment
process by calling the appropriate ship skills in the right order.

## Prerequisites

- `/ship-setup` must have been run at least once
- `~/.config/ship/config.json` must exist and contain at least one host
- The app must already be built locally (binary compiled or dist folder ready)

## Read Config

```bash
python3 -c "
import json, os
cfg = json.load(open(os.path.expanduser('~/.config/ship/config.json')))
nick = '<nickname-or-default>'
h = cfg['hosts'].get(nick, cfg['hosts'][cfg['default']])
print(h['host'])
print(h['domain'])
"
```

## Step 1 — Understand what we're deploying

Ask the user (or infer from context):

- **What is the app name?** (e.g. `foodtracker`)
- **What type?** Binary or static site?
- **Where is the artifact?** Path to binary or dist folder
- **What host?** Default unless specified
- **Any env vars needed?** Especially for first-time deploys
- **Custom domain?** Or use `<app-name>.<base-domain>`

If any of these are unclear, ask before proceeding.

## Step 2 — Check if app already exists

```bash
ssh <host> "test -f /etc/ship/ports/<app-name> && echo exists || echo new"
```

Tell the user whether this is a fresh deploy or an update to an existing app.

## Step 3 — Deploy

### For a binary app → follow ship-binary

Key steps in order:
1. Backup any SQLite databases in `/var/lib/<app-name>/`
2. Allocate or retrieve port
3. Upload binary via scp
4. Write/merge env file
5. Write systemd unit
6. `systemctl restart` (existing) or `systemctl enable --now` (new)
7. Write Caddy config and reload

### For a static site → follow ship-static

Key steps in order:
1. Validate `index.html` exists in dist folder
2. Rsync dist folder to `/var/www/<app-name>/`
3. Fix ownership to `www-data`
4. Write Caddy config and reload

## Step 4 — Verify

After deploying, confirm the service came up:

**Binary:**
```bash
ssh <host> "sudo systemctl is-active <app-name>"
```

If not active, immediately check logs:
```bash
ssh <host> "sudo journalctl -u <app-name> -n 30 --no-pager"
```

**Static:**
```bash
ssh <host> "curl -sI https://<domain> | head -5"
```

## Step 5 — Confirm to user

Report:
- App name and live URL
- Type (binary / static)
- New deploy or update
- Port (binary only)
- Any SQLite backups made
- Any env vars that were set
- Whether the service is running

## Checklist (reference)

Use this to make sure nothing is missed:

- [ ] Config file read, host resolved
- [ ] App type confirmed (binary / static)
- [ ] Artifact path confirmed and exists locally
- [ ] App name and domain confirmed
- [ ] Existing app check done
- [ ] SQLite backed up (binary, if db files exist)
- [ ] Port allocated or retrieved
- [ ] Artifact uploaded
- [ ] Env file written with merge semantics
- [ ] Systemd unit written and service started/restarted (binary)
- [ ] Caddy config written and reloaded
- [ ] Service confirmed running

## Notes

- Never skip the SQLite backup step for binary apps — always check even if you don't expect a db
- Always merge env vars — never overwrite the whole env file
- If anything fails mid-deploy, tell the user exactly where it failed and what state the server is in
- Use `systemctl restart` for existing apps, `enable --now` for new ones — not the other way around
- If the user says "deploy X" without more context, ask the minimum necessary questions before starting