Files
ulicraft-server-v1/.claude/skills/deploy/SKILL.md
Oier Bravo Urtasun cd79b0e540 docs(files): document filestash across plan/README/deploy skill
Fold files./filestash into the docs that carry the service list, the vhost
map and the deploy procedure — README stack table, 00-overview, 02-caddy,
12-build-order, 14-deploy, and the deploy skill.

Two real gaps the filestash deploy exposed, now guardrails in the skill:
- update-all.sh never touches host nginx, so a NEW SUBDOMAIN silently 404s
  after deploy. It needs issue-letsencrypt.sh + render-nginx.sh --install.
- a new service with ${FOO:?} guards fails the WHOLE compose file, so a
  missing .env var means `up` starts nothing — and --hard has already run
  `down`. Check the host's .env BEFORE tearing the stack down.

Also: filestash/config.json is a single-file bind mount re-rendered every
run, so a rolling update-all leaves it on a stale inode (same trap as the
caddy conf) — force-recreate after any FILES_* change.

Correct the cochi divergence section: `git diff HEAD` on the host is now
empty (verified this deploy — the ff-pull succeeded). The documented
docker-compose.yml/package.json divergence was converged; only untracked
dnsmasq/, caddy-root-ca.crt and a stale pack/ remain. The stash-pull-pop
procedure it prescribed is no longer needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 18:23:18 +02:00

6.7 KiB

name, description
name description
deploy Redeploy the Ulicraft Minecraft stack to the production host `cochi` — git pull on main then a hard restart (compose down + up). Use when the user says "deploy", "redeploy", "deploy to cochi", "push to prod", "ship it", or "restart the server" for this project. Causes brief Minecraft downtime.

Deploy Ulicraft to cochi

Redeploy the running stack on the production host. Full reference: plan/14-deploy.md.

Fixed parameters

  • Host: cochi (SSH alias)
  • Path: /home/ubuntu/mc/ulicraft-server-v1
  • Branch: main
  • Stack: single unified docker-compose.yml (drasl, minecraft, mc-backup, caddy, nmsr, mc-status, uptime-kuma, filestash)
  • Auth: Drasl
  • Restart: hard (down → up) — players are disconnected for a few minutes

Procedure

This is an outward-facing, downtime-causing action on production. The user invoking this skill is the authorization to proceed, but:

  1. Pre-check (read-only). See what would change before tearing anything down:

    ssh cochi 'cd /home/ubuntu/mc/ulicraft-server-v1 && git fetch && git log --oneline HEAD..@{u}'
    

    Report the incoming commits. If the list is empty, tell the user the host is already up to date and ask whether to restart anyway (they may want a plain restart). If git fetch fails (host unreachable, auth), STOP and report.

    If the incoming commits ADD A SERVICE, check .env on the host first. A new service with ${FOO:?...} guards (filestash uses them) fails the whole compose file, not just that service — so a missing var means up refuses to start anything, and a --hard deploy has already run down. The stack stays down. Check before tearing it down:

    ssh cochi 'cd /home/ubuntu/mc/ulicraft-server-v1 && grep -c "^FILES_" .env'
    

    Same for a new subdomain: it needs a cert (LE_SUBDOMAINS + issue-letsencrypt.sh) and an nginx vhost — see step 2b, which the deploy does NOT do for you.

  2. Pull, ensure authlib, then update-all.sh --hard (single SSH session, halts on any error):

    ssh cochi 'set -e
      cd /home/ubuntu/mc/ulicraft-server-v1
      git pull --ff-only
      tooling/fetch-authlib.sh
      ./update-all.sh --hard'
    
    • --ff-only refuses to merge — if the pull is not a fast-forward, STOP and report (local changes on the host need manual resolution).
    • fetch-authlib.sh ensures runtime/authlib-injector.jar exists (gitignored; skips if already valid; NOT part of update-all since it rarely changes). Missing jar = minecraft crashloops with "Error opening zip file or JAR manifest missing". Run it before update-all so the jar is present when compose brings minecraft up.
    • update-all.sh is the single source of truth for the post-pull build + restart steps (render drasl/nmsr configs from .env, sync server mods, regen the landing mod list, rebuild www/, then apply via docker compose). It is also runnable on its own for a lighter rolling update (no --hard = change-detected restart, no world downtime). --hard here does down --remove-orphans && up -d --build for the deliberate full restart. render-config.sh halts on an invalid REGISTRATION_MODE (must be invite or open); a missing distribution jar halts sync-server-mods.sh. The landing rebuild is unconditional (the gitignored www/ is never updated by git pull), so no separate landing step is needed.

2b. Only if the deploy changed nginx/ulicraft-caddy.conf.tmpl (a new subdomain, a new body-size/rate-limit rule, a changed CADDY_HTTP_PORT). update-all.sh does not touch the host nginx, so a new vhost simply won't exist and the subdomain 404s / hits the wrong server block:

ssh cochi 'cd /home/ubuntu/mc/ulicraft-server-v1 && tooling/render-nginx.sh --install'

It runs nginx -t before reloading, so a bad render fails safe. The cert must already exist (certs/<name>/cert.pem) or nginx won't load the block — issue it first with tooling/issue-letsencrypt.sh (reads LE_SUBDOMAINS).

  1. Verify.
    ssh cochi 'cd /home/ubuntu/mc/ulicraft-server-v1 && docker compose ps'
    
    Confirm caddy, drasl, minecraft, mc-backup, nmsr, mc-status, uptime-kuma, filestash are Up. Tail the minecraft log briefly and confirm it reaches the ready marker (match Done (Ns)! For help — a bare Done also matches unrelated mod-loading lines):
    ssh cochi 'cd /home/ubuntu/mc/ulicraft-server-v1 && timeout 180 docker compose logs -f minecraft' 2>/dev/null | grep -m1 'Done ([0-9.]*s)! For help'
    
    If a public vhost changed, verify it end-to-end from your workstation rather than trusting compose ps (a container can be Up while nginx never routes to it): curl -s -o /dev/null -w '%{http_code}' https://<vhost>/.

Guardrails

  • Never git reset --hard or discard changes on cochi without telling the user first — there may be host-local edits.
  • Caddy conf changes need a recreate, not reload/restart. If the deploy touched any caddy/conf.d/*.caddy or the Caddyfile and you did a rolling update-all.sh (not --hard), the change WON'T apply: those are single-file bind mounts pinned to the host inode, and git pull gives the file a new inode. restart/caddy reload reuse the stale inode. Run docker compose up -d --force-recreate caddy, then verify with docker compose exec caddy cat /etc/caddy/conf.d/<file>. The --hard path (full down/up) already recreates, so it's unaffected.
  • Same trap for filestash/config.json — also a single-file bind mount, and it is re-rendered on every run by render-config.sh, so a rolling update-all.sh leaves filestash reading the stale inode after any FILES_* change (rotating the shared password, flipping FILES_AUTH). Apply with docker compose up -d --force-recreate filestash. --hard is unaffected.
  • Never let filestash/config.json be missing when compose runs. It is gitignored and rendered; if it doesn't exist, Docker creates a directory at that path and filestash breaks in a confusing way. update-all.sh renders before up, so the normal path is safe — just don't hand-run docker compose up on the host without rendering first. If it happened: rm -rf filestash/config.json && tooling/render-config.sh && docker compose up -d --force-recreate filestash.
  • Volumes (mc_data, drasl_state, kuma_data) persist across down; the world and monitors are safe. Do NOT pass -v/--volumes to down.
  • If a step fails, STOP and surface the exact error + the failing command. Do not improvise recovery on production.