Roll the run-related changes from the local repositoryonto an already-running production server in a controlled way:.claude/skills/deploy-server/deploy.sh A single command runs through diff → backup → sync → rebuild → conditional reload → verify. By default, first --dry-run read-only preview, then after manual confirmation --apply. Real assertions throughout (container status / peers ls / domain rendering), not relying on "it should deploy".
This is arelease/opsskill; it touches theonline running stack, and never clears data.
- Is the cluster design correct (a one-off local test stack, discarded after use) → kubo-deploy-e2e。
- Is the release chain correct (token write entry + publish.sh e2e) → kubo-publish-e2e。
- Publish HTML/attachments as share links (publish content, do not touch infrastructure) → publish-artifact。
The real form of the production server: the remote directoryis not a git repository, but a subset of run files manually copied over. The container name is fixed as
cl-caddy、cl-ipfs0/1/2、cl-cluster0/1/2、cl-cloudflared. This skill only doesincremental updates to an already-running server, not initial bring-up (for bring-up seedocs/SINGLE_HOST_DEPLOYMENT.md)。
0. Prerequisite: fill in the skill's own .env
Skillself-contained, configuration lives in the skill directory's own .env,does not touchthe project root .env。
cd .claude/skills/deploy-server
cp .env.example .env # 首次
$EDITOR .env # 填服务器地址与凭据
Configuration resolution order:process environment variables take precedence → skill .env as fallback(in CI only environment variables can be used; locally no need to export every time). But .env must exist, otherwise the script stops and prompts cp .env.example .env。
| Key | Meaning | Default |
|---|---|---|
DEPLOY_SSH_HOST |
server IP/domain | required |
DEPLOY_SSH_USER |
login user | root |
DEPLOY_SSH_PORT |
SSH port | 22 |
DEPLOY_REMOTE_DIR |
remote deployment directory | /data/ipfs |
DEPLOY_SSH_KEY |
private key path (optional, preferred) | empty |
DEPLOY_SSH_PASSWORD |
password (optional, fallback; leave empty if using a key) | empty |
IPFS_BASE_URL |
external read domain, for read-only rendering verification | if empty, skip domain verification |
Credentialsnever go into git:
.claude/skills/**/.envis already blocked by the gitignore fallback. Only commitSKILL.md/deploy.sh/.env.example。
Connection method (auto-detected)
At the start of each run, the script prints the connection method used this time; detection order:
- Explicit
DEPLOY_SSH_KEY→ use that private key (-i <key> -o IdentitiesOnly=yes)。 - otherwise
BatchMode=yesprobe whether the default key / ssh-agent can connect without a password → if so, use it. - Otherwise if there is
DEPLOY_SSH_PASSWORD→sshpass -e(the password is passed via the$SSHPASSenvironment variable, not through the command line/logs). Missingsshpasswill promptbrew install sshpassor switch to using a key. - If none of these exist → error and exit.
All ssh/scp include -o StrictHostKeyChecking=accept-new(automatically trust the host key on first connection).
1. dry-run (default, read-only, never modifies anything)
.claude/skills/deploy-server/deploy.sh # 等价 --dry-run
.claude/skills/deploy-server/deploy.sh --dry-run
Per-file local↔remote sha256 comparison, print:
- Which files will be overwritten/added (
~changed /+added, with abbreviated local/remote sha). - Whether the compose file changed →
make up-cloudflarewhether services will be rebuilt as needed. caddy/Caddyfilechanged → whether apply needscaddy validate + reload。- Connection method for this run, and which verification assertions will be made after apply.
2. apply (executed after manual confirmation)
Once dry-run looks correct, execute:
.claude/skills/deploy-server/deploy.sh --apply
Do it in order,stop at the first failed step:
-
Get remote timestamp
date +%Y%m%d-%H%M%S, back up the files thatwill be overwrittento<REMOTE_DIR>/.backup/<ts>/(preserve directory structure,cp -p). Pure new files need no backup. -
scpSync differing files (create remote directories first). -
After syncre-check with sha256local↔remote match one by one; stop on any mismatch.
-
cd <REMOTE_DIR> && make up-cloudflare(=docker compose -f docker-compose.cluster.yml -f docker-compose.cloudflare.yml up -d, idempotent, only rebuild services whose command/config changed). -
Only when
caddy/Caddyfilechangedhandle Caddy. Key point:validate the new config before overwriting the mounted file—put the newCaddyfiledocker cpintocl-caddy's temporary pathcaddy validate,if it fails, abort and do not even overwrite the mounted file(otherwise, once a bad config lands on disk, any subsequent caddy restart/rebuild will load it → read path outage). After validation passes and the file is synced into place, becauseCaddyfileis a mounted file (in-container/etc/caddy/Caddyfile)、up -dwill not rebuild because its content changedcl-caddy, then reload separately:# 覆盖前:在容器内校验尚未落盘的新配置 docker cp <新Caddyfile> cl-caddy:/tmp/Caddyfile.new docker exec cl-caddy caddy validate --config /tmp/Caddyfile.new --adapter caddyfile # 通过并同步落盘后:热重载(reload 自身也会校验,坏配置会保留旧配置不生效) docker exec cl-caddy caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile -
Verify (see §3).
Sync whitelist (only touch runtime-related files)
docker-compose.cluster.yml docker-compose.cloudflare.yml docker-compose.node.yml
Makefile caddy/** scripts/** skills/publish-artifact/** e2e/**
Usewhitelistrather than blacklist: safest, and prefer using git ls-files enumeration (naturally excludes .env/runtime//fixtures). When new ops files need to be synced, manually add them to deploy.sh 's WHITELIST。
Never touch remote:.env、runtime/、.backup/、docs/、plans/、.git。
3. Verification (real assertions; read-only)
Automatically run at the end of apply; also the manual three steps for judging whether production is healthy:
# ① 容器状态:cl-* 应全部 Up/healthy
docker ps -a --filter name=cl- --format '{{.Names}} {{.Status}}'
# ② 集群成形:peers 数 = 集群节点数(脚本从本地 compose 推导期望值)
docker exec cl-cluster0 ipfs-cluster-ctl peers ls
# ③ 域名只读渲染(配了 IPFS_BASE_URL 才做):取一个真实已 pin CID,走对外域名应得 200 text/html
CID=$(docker exec cl-cluster0 ipfs-cluster-ctl pin ls | awk '{print $1}' | head -1)
curl -sS -o /dev/null -w '%{http_code} %{content_type}\n' "https://<IPFS_BASE_URL 的 host>/artifact/$CID/"
The cluster currently has no pinned CID, or this machine has no curl, or IPFS_BASE_URL is not configured → domain verification skipped (not counted as failure).
4. Failure handling and manual rollback
- Stop at the first failed step, do not continue further. The scriptalways prints the backup directory path
<REMOTE_DIR>/.backup/<ts>/。 - Manual rollback(no
--rollbacksubcommand in phase one): using the printed.backup/<ts>/path, restore the whitelist files to their corresponding locations in<REMOTE_DIR>, then on the remotecd <REMOTE_DIR> && make up-cloudflare; if what was restored isCaddyfile, manuallyvalidate + reload。 - When Caddyfile validation fails the script will not reload; the remote in-memory config is still the old one, so it is safe.
Not done (YAGNI)
- Do not do the
--rollbacksubcommand (phase one only guarantees the backup exists + prints the path; rollback is manual). - Do not touch remote
.env/runtime/data, do not do data backup (for data backup seedocs/CAPABILITIES_AND_OPERATIONS.md)。 - Do not do first bring-up (this skill is foralready runningserver performs incremental updates; for bring-up see
docs/SINGLE_HOST_DEPLOYMENT.md)。 - does not do multi-machine batch deployment (multi-machine goes via
docs/MULTI_HOST_DEPLOYMENT.md)。
Minimal usage example
# 0) 首次填配置
cd .claude/skills/deploy-server && cp .env.example .env && $EDITOR .env
# 1) dry-run 看清将改什么、会不会 reload Caddy
./deploy.sh --dry-run
# 输出示例:
# 将覆盖/新增的文件(local / remote sha 缩略):
# ~ caddy/Caddyfile local:9f2a... remote:1c04...
# Caddyfile: 有变更 → apply 时将 caddy validate + caddy reload(校验不过则中止)
# 确认无误后执行: ./deploy.sh --apply
# 2) 人工确认无误后 apply(备份 → 同步 → 复核 → make up-cloudflare → Caddy reload → 验证)
./deploy.sh --apply
# 末尾打印:部署完成。备份路径: /data/ipfs/.backup/20260710-142530
Related:kubo-deploy-e2e(one-off local test stack) · kubo-publish-e2e(release pipeline e2e) · publish-artifact(publish content and get a share link) · docs/SINGLE_HOST_DEPLOYMENT.md · docs/MULTI_HOST_DEPLOYMENT.md · docs/CAPABILITIES_AND_OPERATIONS.md