fix: Wire Woodpecker CI to local Forgejo (#612)

Add ci_commit_status() and ci_pipeline_number() helpers to
lib/ci-helpers.sh that query Woodpecker directly with a forge API
fallback. Replace all 12 inline forge commit status calls across 6
files with the new helpers.

Add setup_woodpecker() to bin/disinto init that creates a Forgejo
OAuth2 app for Woodpecker and activates the repo.

Document manual Woodpecker+Forgejo setup in BOOTSTRAP.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-23 17:19:01 +00:00
parent 7de1dca12c
commit 50dff34b89
9 changed files with 250 additions and 25 deletions

View file

@ -551,6 +551,129 @@ install_cron() {
echo "Cron entries installed"
}
# Set up Woodpecker CI to use Forgejo as its forge backend.
# Creates an OAuth2 app on Forgejo for Woodpecker, activates the repo.
setup_woodpecker() {
local forge_url="$1" repo_slug="$2"
local wp_server="${WOODPECKER_SERVER:-}"
if [ -z "$wp_server" ]; then
echo "Woodpecker: not configured (WOODPECKER_SERVER not set), skipping"
return
fi
# Check if Woodpecker is reachable
if ! curl -sf --max-time 5 "${wp_server}/api/version" >/dev/null 2>&1; then
echo "Woodpecker: not reachable at ${wp_server}, skipping"
return
fi
echo ""
echo "── Woodpecker CI setup ────────────────────────────────"
echo "Server: ${wp_server}"
# Create OAuth2 application on Forgejo for Woodpecker
local oauth2_name="woodpecker-ci"
local redirect_uri="${wp_server}/authorize"
local existing_app client_id client_secret
# Check if OAuth2 app already exists
existing_app=$(curl -sf \
-H "Authorization: token ${FORGE_TOKEN}" \
"${forge_url}/api/v1/user/applications/oauth2" 2>/dev/null \
| jq -r --arg name "$oauth2_name" '.[] | select(.name == $name) | .client_id // empty' 2>/dev/null) || true
if [ -n "$existing_app" ]; then
echo "OAuth2: ${oauth2_name} (already exists, client_id=${existing_app})"
client_id="$existing_app"
else
local oauth2_resp
oauth2_resp=$(curl -sf -X POST \
-H "Authorization: token ${FORGE_TOKEN}" \
-H "Content-Type: application/json" \
"${forge_url}/api/v1/user/applications/oauth2" \
-d "{\"name\":\"${oauth2_name}\",\"redirect_uris\":[\"${redirect_uri}\"],\"confidential_client\":true}" \
2>/dev/null) || oauth2_resp=""
if [ -z "$oauth2_resp" ]; then
echo "Warning: failed to create OAuth2 app on Forgejo" >&2
return
fi
client_id=$(printf '%s' "$oauth2_resp" | jq -r '.client_id // empty')
client_secret=$(printf '%s' "$oauth2_resp" | jq -r '.client_secret // empty')
if [ -z "$client_id" ]; then
echo "Warning: OAuth2 app creation returned no client_id" >&2
return
fi
echo "OAuth2: ${oauth2_name} created (client_id=${client_id})"
fi
# Store Woodpecker forge config in .env
local env_file="${FACTORY_ROOT}/.env"
local wp_vars=(
"WOODPECKER_FORGEJO=true"
"WOODPECKER_FORGEJO_URL=${forge_url}"
)
if [ -n "${client_id:-}" ]; then
wp_vars+=("WOODPECKER_FORGEJO_CLIENT=${client_id}")
fi
if [ -n "${client_secret:-}" ]; then
wp_vars+=("WOODPECKER_FORGEJO_SECRET=${client_secret}")
fi
for var_line in "${wp_vars[@]}"; do
local var_name="${var_line%%=*}"
if grep -q "^${var_name}=" "$env_file" 2>/dev/null; then
sed -i "s|^${var_name}=.*|${var_line}|" "$env_file"
else
printf '%s\n' "$var_line" >> "$env_file"
fi
done
echo "Config: Woodpecker forge vars written to .env"
# Activate repo in Woodpecker (if not already)
local wp_token="${WOODPECKER_TOKEN:-}"
if [ -z "$wp_token" ]; then
echo "Warning: WOODPECKER_TOKEN not set — cannot activate repo" >&2
echo " Activate manually: woodpecker-cli repo add ${repo_slug}" >&2
return
fi
local wp_repo_id
wp_repo_id=$(curl -sf \
-H "Authorization: Bearer ${wp_token}" \
"${wp_server}/api/repos/lookup/${repo_slug}" 2>/dev/null \
| jq -r '.id // empty' 2>/dev/null) || true
if [ -n "$wp_repo_id" ] && [ "$wp_repo_id" != "0" ]; then
echo "Repo: ${repo_slug} already active in Woodpecker (id=${wp_repo_id})"
else
local activate_resp
activate_resp=$(curl -sf -X POST \
-H "Authorization: Bearer ${wp_token}" \
-H "Content-Type: application/json" \
"${wp_server}/api/repos" \
-d "{\"forge_remote_id\":\"${repo_slug}\"}" 2>/dev/null) || activate_resp=""
wp_repo_id=$(printf '%s' "$activate_resp" | jq -r '.id // empty' 2>/dev/null) || true
if [ -n "$wp_repo_id" ] && [ "$wp_repo_id" != "0" ]; then
echo "Repo: ${repo_slug} activated in Woodpecker (id=${wp_repo_id})"
else
echo "Warning: could not activate repo in Woodpecker" >&2
echo " Activate manually: woodpecker-cli repo add ${repo_slug}" >&2
fi
fi
# Store repo ID for later TOML generation
if [ -n "$wp_repo_id" ] && [ "$wp_repo_id" != "0" ]; then
_WP_REPO_ID="$wp_repo_id"
fi
}
# ── init command ─────────────────────────────────────────────────────────────
disinto_init() {
@ -684,6 +807,26 @@ p.write_text(text)
echo "Created: ${toml_path}"
fi
# Set up Woodpecker CI to use Forgejo as forge backend
_WP_REPO_ID=""
setup_woodpecker "$forge_url" "$forge_repo"
# Use detected Woodpecker repo ID if ci_id was not explicitly set
if [ "$ci_id" = "0" ] && [ -n "${_WP_REPO_ID:-}" ]; then
ci_id="$_WP_REPO_ID"
echo "CI ID: ${ci_id} (from Woodpecker)"
# Update TOML if it already exists
if [ "$toml_exists" = true ] && [ -f "$toml_path" ]; then
python3 -c "
import sys, re, pathlib
p = pathlib.Path(sys.argv[1])
text = p.read_text()
text = re.sub(r'^woodpecker_repo_id\s*=\s*.*$', 'woodpecker_repo_id = ' + sys.argv[2], text, flags=re.MULTILINE)
p.write_text(text)
" "$toml_path" "$ci_id"
fi
fi
# Create labels on remote
create_labels "$forge_repo" "$forge_url"