From a531f9b9ce70073d337e64b039443cc99f08d890 Mon Sep 17 00:00:00 2001 From: bunker-admin Date: Wed, 20 May 2026 11:59:35 -0600 Subject: [PATCH] fix(ccp): make agent functional + fix Gitea release timestamp bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three related fixes uncovered during a marcelle CCP registration test: 1. ccp-agent image was missing bash + curl + jq + python3, so every spawn('bash', ...) in upgrade.routes.ts and backup.routes.ts failed silently with ENOENT. CCP kept reading stale status.json files from disk, masking that no agent had successfully checked for updates in weeks. apk-add the missing tools. 2. ccp-agent's /app/instance mount was :ro, blocking the agent from writing data/upgrade/status.json (and result/progress/backups). Agent already has docker.sock — removing :ro is not a security escalation. Patched both docker-compose.yml and docker-compose.prod.yml. 3. Gitea 1.23.x only initializes Release.CreatedUnix inside its createTag() helper, which is skipped if the tag already exists on origin. The old DEV_WORKFLOW pattern (push tag, then run build-release.sh --upload) was triggering this — releases got created_unix=0 and lost /releases/latest sort order to v2.9.14. build-release.sh now removes the remote tag first and POSTs with target_commitish so Gitea creates the tag and release atomically. After these fixes, CCP's "Check for Updates" path returns truthful data end-to-end (verified on marcelle: v2.9.15 -> v2.10.1, 1 behind). Bunker Admin --- changemaker-control-panel/agent/Dockerfile | 5 ++++- docker-compose.prod.yml | 7 ++++--- docker-compose.yml | 5 ++++- scripts/build-release.sh | 13 ++++++++++++- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/changemaker-control-panel/agent/Dockerfile b/changemaker-control-panel/agent/Dockerfile index 06cf98a..cf3aca7 100644 --- a/changemaker-control-panel/agent/Dockerfile +++ b/changemaker-control-panel/agent/Dockerfile @@ -8,7 +8,10 @@ COPY src/ ./src/ RUN npx tsc FROM node:20-alpine -RUN apk add --no-cache docker-cli docker-cli-compose git rsync +# bash + curl + jq + python3 are required by the changemaker scripts the agent +# shells out to (upgrade-check.sh, upgrade.sh, backup.sh). Without them, every +# /upgrade/* and /backup/* call returns "command not found" failures. +RUN apk add --no-cache docker-cli docker-cli-compose git rsync bash curl jq python3 WORKDIR /app COPY package*.json ./ RUN npm ci --production diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 6f860c0..bfaa678 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1427,9 +1427,10 @@ services: - /var/run/docker.sock:/var/run/docker.sock - ccp-agent-data:/var/lib/ccp-agent - ccp-agent-certs:/etc/ccp-agent - # Mount the instance directory so the agent can read compose files and run - # `docker compose -p ` commands against the real project on disk. - - .:/app/instance:ro + # Mount the instance directory so the agent can read compose files and + # write status.json + backups (writable; agent already has docker.sock, + # so file write access is not an additional security escalation). + - .:/app/instance environment: - AGENT_PORT=7443 - AGENT_DATA_DIR=/var/lib/ccp-agent diff --git a/docker-compose.yml b/docker-compose.yml index a60f3a6..fa307a1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1450,7 +1450,10 @@ services: - /var/run/docker.sock:/var/run/docker.sock - ccp-agent-data:/var/lib/ccp-agent - ccp-agent-certs:/etc/ccp-agent - - .:/app/instance:ro + # Writable: agent must write data/upgrade/{status,progress,result}.json + # and data/backups/*.tar.gz. Agent already has docker.sock — file write + # access is not an additional security escalation. + - .:/app/instance environment: - AGENT_PORT=7443 - AGENT_DATA_DIR=/var/lib/ccp-agent diff --git a/scripts/build-release.sh b/scripts/build-release.sh index dd02921..b49579a 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -295,12 +295,23 @@ if [[ "$UPLOAD" == "true" ]]; then fi fi + # Gitea 1.23.x only initializes Release.CreatedUnix inside its createTag() + # path. If the git tag already exists on origin when we POST /releases, + # createTag() is skipped and CreatedUnix stays 0, which makes /releases/latest + # silently return an older release. Remove the remote tag first so Gitea + # creates it via target_commitish below. The tag is preserved locally and + # gets recreated at the same SHA — no history is lost. + if git ls-remote --exit-code origin "refs/tags/${TAG}" >/dev/null 2>&1; then + warn "Removing remote tag ${TAG} so Gitea can recreate it (CreatedUnix init)" + git push origin ":refs/tags/${TAG}" >/dev/null 2>&1 || true + fi + info "Creating Gitea release ${TAG}..." RELEASE_RESPONSE=$(curl -sf -X POST \ "${GITEA_HOST}/api/v1/repos/admin/changemaker.lite/releases" \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ - -d "{\"tag_name\":\"${TAG}\",\"name\":\"Changemaker Lite ${TAG}\",\"body\":\"Release ${TAG} (${COMMIT_SHA})\"}" \ + -d "{\"tag_name\":\"${TAG}\",\"target_commitish\":\"${COMMIT_SHA}\",\"name\":\"Changemaker Lite ${TAG}\",\"body\":\"Release ${TAG} (${COMMIT_SHA})\"}" \ 2>/dev/null || true) RELEASE_ID=$(echo "$RELEASE_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)