Live workflow_dispatch (run #249) exposed a bug in the prior PR-merge rework: `main` requires Tekton status checks, so an immediate merge returns HTTP 405 while checks are pending. `curl -fsS` then failed the job and left an orphaned bot PR open. Merge step now tries an immediate merge (fast path on instances with no required checks) and falls back to Forgejo's scheduled auto-merge (merge_when_checks_succeed) so the PR lands unattended once checks pass and the job exits 0.
137 lines
6.9 KiB
YAML
137 lines
6.9 KiB
YAML
# Daily snapshot of release-asset download counts into docs/downloads/downloads.jsonl.
|
|
#
|
|
# NOTE: this is the ONLY Forgejo Actions workflow in the repo — CI/CD otherwise runs
|
|
# on Tekton (op1st cluster). It exists here because a tiny scheduled curl+commit is
|
|
# not worth a cluster CronJob. Requires:
|
|
# - Forgejo Actions enabled for the repo (Settings → Actions).
|
|
# - A repo/org secret DOWNLOAD_TRACKER_TOKEN: a token with write access used to
|
|
# push the daily commit back to the default branch.
|
|
#
|
|
# Runs on the ubuntu-latest runner. To stay portable we don't assume jq is present
|
|
# and avoid JS actions — the job installs any missing tool, then clones the repo
|
|
# manually with plain git (the token in the clone URL also authenticates the push).
|
|
#
|
|
# Branch protection on `main` forbids the old direct `git push origin HEAD:main`.
|
|
# Instead the job pushes the snapshot to a throwaway branch, opens a PR via the
|
|
# Forgejo API, and merges it with the same token (which has PR-merge permission).
|
|
# The branch deletes itself on merge (delete_branch_after_merge), so nothing
|
|
# accumulates. DOWNLOAD_TRACKER_TOKEN must therefore carry repo write + PR merge.
|
|
name: track-downloads
|
|
|
|
on:
|
|
schedule:
|
|
- cron: "17 6 * * *" # 06:17 UTC daily (off the hour to dodge runner contention)
|
|
workflow_dispatch: {}
|
|
|
|
jobs:
|
|
snapshot:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Snapshot + commit
|
|
# POSIX sh, not bash: the runner's pod image is not guaranteed to ship bash,
|
|
# and Forgejo's default `run:` shell (bash -e) fails to start if it's absent.
|
|
shell: sh
|
|
env:
|
|
TRACKER_TOKEN: ${{ secrets.DOWNLOAD_TRACKER_TOKEN }}
|
|
API_BASE: https://codeberg.org/api/v1
|
|
REPO_SLUG: goern/forgejo-mcp
|
|
BASE_BRANCH: main
|
|
run: |
|
|
set -eu
|
|
|
|
# OpenShift runs the pod as an arbitrary UID with HOME=/ (read-only), so
|
|
# git's global config and tool caches have nowhere writable. Respect XDG:
|
|
# point HOME + XDG base dirs at a fresh writable directory.
|
|
XDG_BASE="$(mktemp -d)"
|
|
export HOME="$XDG_BASE"
|
|
export XDG_CONFIG_HOME="$XDG_BASE/.config"
|
|
export XDG_CACHE_HOME="$XDG_BASE/.cache"
|
|
mkdir -p "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME"
|
|
|
|
# --- ensure curl, jq, git (distro-agnostic; pod image may be UBI/Debian/Alpine) ---
|
|
need=""
|
|
for t in curl jq git; do command -v "$t" >/dev/null 2>&1 || need="$need $t"; done
|
|
if [ -n "$need" ]; then
|
|
echo "Installing:$need"
|
|
if command -v microdnf >/dev/null 2>&1; then microdnf install -y $need
|
|
elif command -v dnf >/dev/null 2>&1; then dnf install -y $need
|
|
elif command -v yum >/dev/null 2>&1; then yum install -y $need
|
|
elif command -v apt-get >/dev/null 2>&1; then apt-get update -qq && apt-get install -y -qq --no-install-recommends $need ca-certificates
|
|
elif command -v apk >/dev/null 2>&1; then apk add --no-cache $need
|
|
else echo "No known package manager to install:$need" >&2; exit 1
|
|
fi
|
|
fi
|
|
|
|
# --- clone (shallow) using the token so the later push is authenticated ---
|
|
REPO_URL="https://${TRACKER_TOKEN}@codeberg.org/goern/forgejo-mcp.git"
|
|
git clone --depth 1 "$REPO_URL" work
|
|
cd work
|
|
# Local (repo) identity — clone and git run as the same UID, so no
|
|
# global safe.directory needed and nothing writes outside the workspace.
|
|
git config user.name "op1st-gitops"
|
|
git config user.email "op1st-gitops@noreply.codeberg.org"
|
|
|
|
# --- snapshot today's download counts ---
|
|
./hack/snapshot-downloads.sh
|
|
|
|
# --- nothing to do if the counts didn't move ---
|
|
if git diff --quiet -- docs/downloads/downloads.jsonl; then
|
|
echo "No change in download counts today — nothing to commit."
|
|
exit 0
|
|
fi
|
|
|
|
# --- commit onto a throwaway branch (main is protected) ---
|
|
# Unique, dated branch name: avoids collisions if a prior day's PR
|
|
# branch was left behind by a failed run.
|
|
BRANCH="bot/downloads-$(date -u +%Y%m%d-%H%M%S)"
|
|
git checkout -b "$BRANCH"
|
|
git add docs/downloads/downloads.jsonl
|
|
git commit -m "chore: 📊 snapshot release download counts"
|
|
git push origin "HEAD:refs/heads/$BRANCH"
|
|
|
|
# --- open a PR via the Forgejo API ---
|
|
# token auth header; jq builds the JSON body so titles/bodies are
|
|
# always correctly escaped.
|
|
AUTH="Authorization: token $TRACKER_TOKEN"
|
|
pr_body="$(jq -n \
|
|
--arg head "$BRANCH" \
|
|
--arg base "$BASE_BRANCH" \
|
|
--arg title "chore: 📊 snapshot release download counts" \
|
|
--arg body "Automated daily download-count snapshot. Generated by the track-downloads workflow." \
|
|
'{head:$head, base:$base, title:$title, body:$body}')"
|
|
pr_json="$(curl -fsS -X POST \
|
|
-H "$AUTH" -H 'Content-Type: application/json' \
|
|
-d "$pr_body" \
|
|
"$API_BASE/repos/$REPO_SLUG/pulls")"
|
|
PR_NUMBER="$(printf '%s' "$pr_json" | jq -r '.number')"
|
|
if [ -z "$PR_NUMBER" ] || [ "$PR_NUMBER" = "null" ]; then
|
|
echo "Failed to create PR; API response was:" >&2
|
|
printf '%s\n' "$pr_json" >&2
|
|
exit 1
|
|
fi
|
|
echo "Opened PR #$PR_NUMBER from $BRANCH."
|
|
|
|
# --- merge it; delete the branch on success ---
|
|
# Two-step because `main` is protected by required status checks
|
|
# (Tekton Pipelines-as-Code). An immediate merge returns HTTP 405
|
|
# while those checks are still pending, so we:
|
|
# 1. try an immediate merge — succeeds on instances with no
|
|
# required checks (the fast path), then
|
|
# 2. fall back to Forgejo's scheduled auto-merge
|
|
# (merge_when_checks_succeed) so the PR lands unattended once
|
|
# the checks go green, and the job still exits 0.
|
|
# delete_branch_after_merge keeps the bot/ refs from piling up.
|
|
merge_now='{"Do":"merge","delete_branch_after_merge":true}'
|
|
merge_sched='{"Do":"merge","merge_when_checks_succeed":true,"delete_branch_after_merge":true}'
|
|
merge_url="$API_BASE/repos/$REPO_SLUG/pulls/$PR_NUMBER/merge"
|
|
if curl -fsS -X POST -H "$AUTH" -H 'Content-Type: application/json' \
|
|
-d "$merge_now" "$merge_url" >/dev/null 2>&1; then
|
|
echo "Merged PR #$PR_NUMBER immediately."
|
|
elif curl -fsS -X POST -H "$AUTH" -H 'Content-Type: application/json' \
|
|
-d "$merge_sched" "$merge_url" >/dev/null 2>&1; then
|
|
echo "Scheduled PR #$PR_NUMBER to auto-merge when checks succeed."
|
|
else
|
|
echo "Could not merge or schedule PR #$PR_NUMBER (perms? base out of date?)." >&2
|
|
echo "Leaving PR open for manual handling; branch $BRANCH retained." >&2
|
|
exit 1
|
|
fi
|