diff options
| author | Clawd <ai@clawd.bot> | 2026-04-11 20:43:41 -0700 |
|---|---|---|
| committer | Clawd <ai@clawd.bot> | 2026-04-11 20:43:41 -0700 |
| commit | d0ae31c24c3c98ae89eebd67227c0c0d01606ed5 (patch) | |
| tree | c684469e0f7d3b65477cfc631ecdaafa3c6a218a /skills/ship-static/SKILL.md | |
| parent | 5548b36e0953c17dbe30f6b63c892b7c83196b20 (diff) | |
Add ship-* Claude skills and plan
Introduces a skills/ directory with 8 Claude skills that reimagine ship
as a set of composable, human-driven deployment tools backed by Claude's
reasoning rather than a rigid CLI.
Skills:
- ship-setup: one-time VPS config, saves host to ~/.config/ship/config.json
- ship-status: derives live state from server, no local state file
- ship-env: read/write env vars with merge semantics, never overwrites
- ship-binary: deploy Go binaries with SQLite backup, correct restart behavior
- ship-caddy: manage per-app Caddyfile with validate-before-reload
- ship-service: systemd management and log inspection
- ship-static: rsync static sites with SPA routing support
- ship-deploy: orchestration runbook tying the others together
Also adds SKILLS_PLAN.md documenting the architecture and rationale.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'skills/ship-static/SKILL.md')
| -rw-r--r-- | skills/ship-static/SKILL.md | 121 |
1 files changed, 121 insertions, 0 deletions
diff --git a/skills/ship-static/SKILL.md b/skills/ship-static/SKILL.md new file mode 100644 index 0000000..1ef74d3 --- /dev/null +++ b/skills/ship-static/SKILL.md | |||
| @@ -0,0 +1,121 @@ | |||
| 1 | --- | ||
| 2 | name: ship-static | ||
| 3 | description: Deploy a static site to a ship VPS. Rsyncs a local dist folder to the server and configures Caddy to serve it. Use when deploying a built frontend, docs site, or any folder of static files. | ||
| 4 | argument-hint: "<path-to-dist> <app-name> [host-nickname]" | ||
| 5 | --- | ||
| 6 | |||
| 7 | # ship-static | ||
| 8 | |||
| 9 | Deploy a static site by rsyncing a local directory to the server and configuring Caddy. | ||
| 10 | |||
| 11 | ## Read Config | ||
| 12 | |||
| 13 | ```bash | ||
| 14 | python3 -c " | ||
| 15 | import json, os | ||
| 16 | cfg = json.load(open(os.path.expanduser('~/.config/ship/config.json'))) | ||
| 17 | nick = '<nickname-or-default>' | ||
| 18 | h = cfg['hosts'].get(nick, cfg['hosts'][cfg['default']]) | ||
| 19 | print(h['host']) | ||
| 20 | print(h['domain']) | ||
| 21 | " | ||
| 22 | ``` | ||
| 23 | |||
| 24 | ## Inputs | ||
| 25 | |||
| 26 | - **Dist path** — local directory containing the built static files (e.g. `./dist`, `./out`) | ||
| 27 | - **App name** — short lowercase name, becomes the subdomain (e.g. `mysite`) | ||
| 28 | - **Domain** — defaults to `<app-name>.<base-domain>`, ask if different | ||
| 29 | - **Host** — use default unless specified | ||
| 30 | |||
| 31 | ## Steps | ||
| 32 | |||
| 33 | ### 1. Validate local path | ||
| 34 | |||
| 35 | Check that the dist directory exists and contains an `index.html`: | ||
| 36 | |||
| 37 | ```bash | ||
| 38 | ls <dist-path>/index.html | ||
| 39 | ``` | ||
| 40 | |||
| 41 | If not found, tell the user — they may need to build first. | ||
| 42 | |||
| 43 | ### 2. Create remote directory | ||
| 44 | |||
| 45 | ```bash | ||
| 46 | ssh <host> "sudo mkdir -p /var/www/<app-name> && sudo chown $USER:$USER /var/www/<app-name>" | ||
| 47 | ``` | ||
| 48 | |||
| 49 | ### 3. Sync files | ||
| 50 | |||
| 51 | ```bash | ||
| 52 | rsync -avz --delete <dist-path>/ <host>:/var/www/<app-name>/ | ||
| 53 | ``` | ||
| 54 | |||
| 55 | The `--delete` flag removes files on the server that no longer exist locally, keeping | ||
| 56 | the deployment clean. Tell the user how many files were transferred. | ||
| 57 | |||
| 58 | ### 4. Fix ownership | ||
| 59 | |||
| 60 | After rsync, ensure Caddy can read the files: | ||
| 61 | |||
| 62 | ```bash | ||
| 63 | ssh <host> "sudo chown -R www-data:www-data /var/www/<app-name>" | ||
| 64 | ``` | ||
| 65 | |||
| 66 | ### 5. Write Caddy config | ||
| 67 | |||
| 68 | Check if a config already exists: | ||
| 69 | |||
| 70 | ```bash | ||
| 71 | ssh <host> "cat /etc/caddy/sites-enabled/<app-name>.caddy 2>/dev/null" | ||
| 72 | ``` | ||
| 73 | |||
| 74 | Write (or overwrite) the config: | ||
| 75 | |||
| 76 | ```bash | ||
| 77 | ssh <host> "sudo tee /etc/caddy/sites-enabled/<app-name>.caddy > /dev/null << 'EOF' | ||
| 78 | <domain> { | ||
| 79 | root * /var/www/<app-name> | ||
| 80 | file_server | ||
| 81 | encode gzip | ||
| 82 | } | ||
| 83 | EOF" | ||
| 84 | ``` | ||
| 85 | |||
| 86 | ### 6. Validate and reload Caddy | ||
| 87 | |||
| 88 | ```bash | ||
| 89 | ssh <host> "sudo caddy validate --config /etc/caddy/Caddyfile 2>&1" | ||
| 90 | ``` | ||
| 91 | |||
| 92 | If valid: | ||
| 93 | ```bash | ||
| 94 | ssh <host> "sudo systemctl reload caddy" | ||
| 95 | ``` | ||
| 96 | |||
| 97 | If invalid, show the error and do not reload. | ||
| 98 | |||
| 99 | ### 7. Confirm | ||
| 100 | |||
| 101 | Tell the user: | ||
| 102 | - URL the site is live at | ||
| 103 | - Number of files synced | ||
| 104 | - Whether this was a new deployment or an update | ||
| 105 | |||
| 106 | ## Notes | ||
| 107 | |||
| 108 | - Build before deploying — this skill does not run build commands | ||
| 109 | - `--delete` in rsync means files removed locally will be removed from the server too | ||
| 110 | - If the user wants a custom domain, use ship-caddy to update the config after deploying | ||
| 111 | - For SPAs with client-side routing, the Caddy config may need a `try_files` directive: | ||
| 112 | ``` | ||
| 113 | <domain> { | ||
| 114 | root * /var/www/<app-name> | ||
| 115 | try_files {path} /index.html | ||
| 116 | file_server | ||
| 117 | encode gzip | ||
| 118 | } | ||
| 119 | ``` | ||
| 120 | Ask the user if their site uses client-side routing. | ||
| 121 | - Use default host unless another is specified | ||
