disinto/formulas/release.sh
Claude fbf1a6dcc2
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
fix: review feedback — cd path in release.sh, compose file access in edge container
- formulas/release.sh: cd to $FACTORY_ROOT (not parent) for docker compose build
- docker-compose.yml: mount docker-compose.yml into edge container, pass HOST_PROJECT_DIR
- dispatcher.sh: use -f and --project-directory so compose resolves volume paths
  against the host filesystem when invoked from inside the edge container

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 19:23:08 +00:00

187 lines
6.6 KiB
Bash

#!/usr/bin/env bash
# formulas/release.sh — Mechanical release script
#
# Implements the release workflow without Claude:
# 1. Validate prerequisites
# 2. Tag Forgejo main via API
# 3. Push tag to mirrors (Codeberg, GitHub) via token auth
# 4. Build and tag the agents Docker image
# 5. Restart agent containers
#
# Usage: release.sh <action-id>
#
# Expects env vars:
# FORGE_URL, FORGE_TOKEN, FORGE_REPO, PRIMARY_BRANCH
# GITHUB_TOKEN — for pushing tags to GitHub mirror
# CODEBERG_TOKEN — for pushing tags to Codeberg mirror
#
# The action TOML context field must contain the version, e.g.:
# context = "Release v1.2.0"
#
# Part of #516.
set -euo pipefail
FACTORY_ROOT="${FACTORY_ROOT:-/home/agent/disinto}"
OPS_REPO_ROOT="${OPS_REPO_ROOT:-/home/agent/ops}"
log() {
printf '[%s] release: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*"
}
# ── Argument parsing ─────────────────────────────────────────────────────
# VAULT_ACTION_TOML is exported by the runner entrypoint (entrypoint-runner.sh)
action_id="${1:-}"
if [ -z "$action_id" ]; then
log "ERROR: action-id argument required"
exit 1
fi
action_toml="${VAULT_ACTION_TOML:-${OPS_REPO_ROOT}/vault/actions/${action_id}.toml}"
if [ ! -f "$action_toml" ]; then
log "ERROR: vault action TOML not found: ${action_toml}"
exit 1
fi
# Extract version from context field (e.g. "Release v1.2.0" → "v1.2.0")
context=$(grep -E '^context\s*=' "$action_toml" \
| sed -E 's/^context\s*=\s*"(.*)"/\1/' | tr -d '\r')
RELEASE_VERSION=$(echo "$context" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+') || true
if [ -z "${RELEASE_VERSION:-}" ]; then
log "ERROR: could not extract version from context: '${context}'"
log "Context must contain a version like v1.2.0"
exit 1
fi
log "Starting release ${RELEASE_VERSION} (action: ${action_id})"
# ── Step 1: Preflight ────────────────────────────────────────────────────
log "Step 1/6: Preflight checks"
# Validate version format
if ! echo "$RELEASE_VERSION" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
log "ERROR: invalid version format: ${RELEASE_VERSION}"
exit 1
fi
# Required env vars
for var in FORGE_URL FORGE_TOKEN FORGE_REPO PRIMARY_BRANCH; do
if [ -z "${!var:-}" ]; then
log "ERROR: required env var not set: ${var}"
exit 1
fi
done
# Check Docker access
if ! docker info >/dev/null 2>&1; then
log "ERROR: Docker not accessible"
exit 1
fi
# Check tag doesn't already exist on Forgejo
if curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
"${FORGE_URL}/api/v1/repos/${FORGE_REPO}/tags/${RELEASE_VERSION}" >/dev/null 2>&1; then
log "ERROR: tag ${RELEASE_VERSION} already exists on Forgejo"
exit 1
fi
log "Preflight passed"
# ── Step 2: Tag main via Forgejo API ─────────────────────────────────────
log "Step 2/6: Creating tag ${RELEASE_VERSION} on Forgejo"
# Get HEAD SHA of primary branch
head_sha=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
"${FORGE_URL}/api/v1/repos/${FORGE_REPO}/branches/${PRIMARY_BRANCH}" \
| jq -r '.commit.id // empty')
if [ -z "$head_sha" ]; then
log "ERROR: could not get HEAD SHA for ${PRIMARY_BRANCH}"
exit 1
fi
# Create tag via API
curl -sf -X POST \
-H "Authorization: token ${FORGE_TOKEN}" \
-H "Content-Type: application/json" \
"${FORGE_URL}/api/v1/repos/${FORGE_REPO}/tags" \
-d "{\"tag_name\":\"${RELEASE_VERSION}\",\"target\":\"${head_sha}\",\"message\":\"Release ${RELEASE_VERSION}\"}" \
>/dev/null
log "Tag ${RELEASE_VERSION} created (SHA: ${head_sha})"
# ── Step 3: Push tag to mirrors ──────────────────────────────────────────
log "Step 3/6: Pushing tag to mirrors"
# Extract org/repo from FORGE_REPO (e.g. "disinto-admin/disinto" → "disinto")
project_name="${FORGE_REPO##*/}"
# Push to GitHub mirror (if GITHUB_TOKEN is available)
if [ -n "${GITHUB_TOKEN:-}" ]; then
log "Pushing tag to GitHub mirror"
# Create tag on GitHub via API
if curl -sf -X POST \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/Disinto/${project_name}/git/refs" \
-d "{\"ref\":\"refs/tags/${RELEASE_VERSION}\",\"sha\":\"${head_sha}\"}" \
>/dev/null 2>&1; then
log "GitHub: tag pushed"
else
log "WARNING: GitHub tag push failed (may already exist)"
fi
else
log "WARNING: GITHUB_TOKEN not set — skipping GitHub mirror"
fi
# Push to Codeberg mirror (if CODEBERG_TOKEN is available)
if [ -n "${CODEBERG_TOKEN:-}" ]; then
log "Pushing tag to Codeberg mirror"
# Codeberg uses Gitea-compatible API
# Extract owner from FORGE_REPO for Codeberg (use same owner)
codeberg_owner="${FORGE_REPO%%/*}"
if curl -sf -X POST \
-H "Authorization: token ${CODEBERG_TOKEN}" \
-H "Content-Type: application/json" \
"https://codeberg.org/api/v1/repos/${codeberg_owner}/${project_name}/tags" \
-d "{\"tag_name\":\"${RELEASE_VERSION}\",\"target\":\"${head_sha}\",\"message\":\"Release ${RELEASE_VERSION}\"}" \
>/dev/null 2>&1; then
log "Codeberg: tag pushed"
else
log "WARNING: Codeberg tag push failed (may already exist)"
fi
else
log "WARNING: CODEBERG_TOKEN not set — skipping Codeberg mirror"
fi
# ── Step 4: Build agents Docker image ────────────────────────────────────
log "Step 4/6: Building agents Docker image"
cd "$FACTORY_ROOT" || exit 1
docker compose build --no-cache agents 2>&1 | tail -5
log "Image built"
# ── Step 5: Tag image with version ───────────────────────────────────────
log "Step 5/6: Tagging image"
docker tag disinto/agents:latest "disinto/agents:${RELEASE_VERSION}"
log "Tagged disinto/agents:${RELEASE_VERSION}"
# ── Step 6: Restart agent containers ─────────────────────────────────────
log "Step 6/6: Restarting agent containers"
docker compose stop agents agents-llama 2>/dev/null || true
docker compose up -d agents agents-llama
log "Agent containers restarted"
# ── Done ─────────────────────────────────────────────────────────────────
log "Release ${RELEASE_VERSION} completed successfully"