Verify "Agent publishing pipelineis correct":token write endpoint gate → publish.sh single-file/directory publish → share link rendering → default expiry/permanent. All real curl / publish.sh / ipfs-cluster-ctl assertions.
This ispublishing e2e. Clusterinfrastructure(3 peers formed, multi-replica, gateway, node-stop fault tolerance) e2e see kubo-deploy-e2e skill /
make e2e。
Conventions and prerequisites
REPO=$(git rev-parse --show-toplevel)
ENDPOINT=http://localhost:9097 # token 写入口(仅 POST /add)—— Agent 发布打这里
BASE=http://localhost:8088 # 读网关 base —— /artifact/<CID>
PUBLISH="$REPO/skills/publish-artifact/publish.sh"
TOKEN=$(grep '^IPFS_PUBLISH_TOKEN=' "$REPO/.env" | cut -d= -f2)
- Image pinned versions:
kubo v0.42.0/ipfs-cluster v1.1.6/caddy:2-alpine。 - Secrets:
.env(CLUSTER_SECRET + IPFS_PUBLISH_TOKEN) andruntime/private/swarm.key, both gitignored.make secretsidempotently generated. - Publishing skill contract: 3 env
IPFS_PUBLISH_ENDPOINT/IPFS_PUBLISH_TOKEN/IPFS_BASE_URL。 - Docs: write endpoint and publishing see
docs/SINGLE_HOST_DEPLOYMENT.md§5、docs/AGENT_INTEGRATION_GUIDE.md。
1. One-click e2e (recommended)
Coverage: ① write endpoint gate (no token→401, token+GET /add→403, token+DELETE /pins→403) ② single-file publish rendering ③ directory site rendering (index+css) ④ default 1-week expiry set ⑤ --permanent no expiry.
make publish-e2e # = ./e2e/run-publish.sh(自起栈,跑完自动 down)
make publish-e2e-keep # = ./e2e/run-publish.sh --keep(保留集群继续手测)
Non-zero exit code means failure; at the end prints PASS=N FAIL=M, and generates a self-contained HTML report runtime/e2e/<时间戳>-publish/report.html. On failure do not tear down the stack; troubleshoot per §3.
2. Manual step-by-step (for demo / troubleshooting)
cd "$REPO"
export IPFS_PUBLISH_ENDPOINT="$ENDPOINT" IPFS_PUBLISH_TOKEN="$TOKEN" IPFS_BASE_URL="$BASE"
# ① 写入口闸门
curl -s -o /dev/null -w 'no-token: %{http_code}\n' -X POST "$ENDPOINT/add" # 401
curl -s -o /dev/null -w 'GET /add: %{http_code}\n' -H "Authorization: Bearer $TOKEN" "$ENDPOINT/add" # 403
curl -s -o /dev/null -w 'DELETE pins: %{http_code}\n' -H "Authorization: Bearer $TOKEN" -X DELETE "$ENDPOINT/pins/x" # 403
# ② 单文件发布 → 链接渲染
echo '<!doctype html><meta charset=utf-8><h1>HELLO_PUBLISH</h1>' > /tmp/p.html
link=$("$PUBLISH" /tmp/p.html); echo "link=$link"
curl -fsS "$link" # 应见正文
# ③ 目录站点(相对资源)
mkdir -p /tmp/site/css
echo '<!doctype html><meta charset=utf-8><link rel=stylesheet href="./css/app.css"><h1>DIR</h1>' > /tmp/site/index.html
echo 'h1{color:green}' > /tmp/site/css/app.css
dlink=$("$PUBLISH" /tmp/site); echo "dlink=$dlink"
curl -fsSI "${dlink}index.html" | grep -i 'HTTP/' # 200
curl -fsSI "${dlink}css/app.css" | grep -i 'HTTP/' # 200
# ④ 默认 1 周过期:pin 应有 expire_at
cid=$(printf '%s' "$link" | sed "s#$BASE/artifact/##; s#/\$##")
docker exec cl-cluster0 ipfs-cluster-ctl --enc=json pin ls "$cid" | grep expire_at
# ⑤ 永久发布:无 expire_at
plink=$("$PUBLISH" --permanent /tmp/p.html) # 注意 --permanent 应少量使用
Browser demo: directly open
$link(single file) or$dlink(directory site).
3. Troubleshooting
docker compose -f docker-compose.cluster.yml ps # 容器状态
docker exec cl-caddy caddy validate --config /etc/caddy/Caddyfile
docker logs cl-caddy 2>&1 | tail -30 # 写入口/反代日志
# 改了 caddy/Caddyfile 后 bind-mount 不会自动热更:
docker exec cl-caddy caddy reload --config /etc/caddy/Caddyfile
docker exec cl-cluster0 ipfs-cluster-ctl pin ls <CID> # 看 pin 期望态/过期
Pitfalls (must know)
- Write endpoint only allows POST /add:
caddy/Caddyfileof:9097first validates the Bearer token (otherwise 401), then requiresmethod POST+path /add(otherwise 403). GET /add, DELETE /pins, etc. all return 403 — the Agent cannot get management capabilities such as unpin/peers. - Must go through :9097 + token, not direct :9095/:9094: native ports are not exposed to the host; publishing only goes through the authenticated write endpoint.
- Default 1-week expiry:
publish.shdefaultexpire-in=168h; on expiry the cluster automatically unpins.--permanentonly then is it permanent (use sparingly to prevent buildup). There is no owner concept, the skill does not provide unpublish, cleanup relies on expiry + adminipfs-cluster-ctl。 - Single CID
pin ls <cid>is pretty-printed JSON("expire_at": "..."with a space after the colon), while the fullpin lsis compact — write assertion grep to tolerate spaces; permanent pin has no expire_at field. - Directory links have a trailing slash: single file
…/artifact/<cid>, directory…/artifact/<root>/; directory upload filenames use the path relative to the site root +wrap-with-directory=true, the root CID is the response line"name":"". - After changing Caddyfile, reload is required:
docker compose up -dwill not rebuild the container due to a bind-mounted Caddyfile change; needdocker exec cl-caddy caddy reload。
Related:kubo-deploy-e2e(deployment e2e) · skills/publish-artifact/(publishing skill itself) · docs/SINGLE_HOST_DEPLOYMENT.md §5 · docs/CLUSTER_CTL_REFERENCE.md