From 1f136ef1c632a05f5b479b15d64179c979bb3754 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 00:40:01 +0000 Subject: [PATCH 1/4] Add reusable check-fog-version workflow Ports the version-drift detection logic from fogproject PR #921 (https://github.com/FOGProject/fogproject/pull/921) as a workflow_call reusable workflow, mirroring how stable-releases.yml already owns fogproject's release automation from outside that repo. A thin push-triggered stub in fogproject delegates to this via `uses:` since GitHub Actions has no native cross-repo push trigger. Differences from PR #921: `stable` is dropped entirely (its version is already owned by stable-releases.yml's release flow), and a detected drift is committed straight to the branch instead of opening a review PR, since these are the same mechanical fixes the local pre-commit hook already makes without review. --- .github/workflows/check-fog-version.yml | 120 ++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 .github/workflows/check-fog-version.yml diff --git a/.github/workflows/check-fog-version.yml b/.github/workflows/check-fog-version.yml new file mode 100644 index 0000000..5c7bfc0 --- /dev/null +++ b/.github/workflows/check-fog-version.yml @@ -0,0 +1,120 @@ +name: Check FOG version + +# Reusable workflow backing the push-triggered stub in FOGProject/fogproject's +# .github/workflows/check-fog-version.yml (see fog-docs' Development section +# for the full picture of why this is split across two repos). +# +# Recomputes FOG_VERSION/FOG_CHANNEL using the same formula as fogproject's +# .githooks/pre-commit. That hook is client-side and never runs on a PR merged +# via GitHub's web UI, so this independently recomputes what the version +# should be and, if it disagrees with what's actually committed, pushes the +# fix directly to the branch that drifted - no fix branch, no PR, since this +# is a mechanical correction of the same kind the hook already makes locally. +# +# Never invoked for `stable` - that branch's version is owned entirely by +# stable-releases.yml's release flow. + +on: + workflow_call: + inputs: + branch: + required: true + type: string + +jobs: + check-version: + runs-on: ubuntu-24.04 + steps: + - uses: actions/create-github-app-token@v3 + id: app-token + with: + client-id: ${{ vars.FOG_WORKFLOWS_APPID }} + private-key: ${{ secrets.FOG_WORKFLOWS_PRIVATE_KEY }} + owner: FOGProject + repositories: "fogproject" + + - uses: actions/checkout@v7 + with: + token: ${{ steps.app-token.outputs.token }} + repository: FOGProject/fogproject + ref: ${{ inputs.branch }} + fetch-depth: 0 + fetch-tags: true + + - name: Fetch master (needed by the version formula) + run: git fetch origin master:master + + - name: Compute expected FOG_VERSION / FOG_CHANNEL + env: + GITBRANCH: ${{ inputs.branch }} + run: | + set -e + system_file="packages/web/lib/fog/system.class.php" + + gitcom=$(git rev-list --tags --no-walk --max-count=1) + gitcount=$(git rev-list master..HEAD --count) + + branchon=$(echo "$GITBRANCH" | awk -F'-' '{print $1}') + branchend=$(echo "$GITBRANCH" | awk -F'-' '{print $2}') + + current_version=$(grep "define('FOG_VERSION'" "$system_file" | sed "s/.*FOG_VERSION', '\([^']*\)');/\1/") + current_channel=$(grep "define('FOG_CHANNEL'" "$system_file" | sed "s/.*FOG_CHANNEL', '\([^']*\)');/\1/") + + verbegin="" + channel="$current_channel" + trunkversion="$current_version" + + case "$branchon" in + dev) + tagversion=$(git describe --tags "$gitcom") + baseversion=${tagversion%.*} + trunkversion="${baseversion}.${gitcount}" + channel="Patches" + ;; + working) + verbegin="${branchend}.0-beta" + trunkversion="${verbegin}.${gitcount}" + channel="Beta" + ;; + rc) + channel="Release Candidate" + version_prefix="${branchend}.0-RC" + n=$(printf '%s\n' "$current_version" | sed -n "s/^${version_prefix}-\([0-9][0-9]*\)\$/\1/p") + if [ -n "$n" ]; then + trunkversion="${version_prefix}-$((n + 1))" + else + trunkversion="${version_prefix}-1" + fi + ;; + feature) + verbegin="${branchend}.0-feature" + trunkversion="${verbegin}.${gitcount}" + channel="Feature" + ;; + esac + + sed -i "s/define('FOG_VERSION',.*);/define('FOG_VERSION', '$trunkversion');/g" "$system_file" + sed -i "s/define('FOG_CHANNEL',.*);/define('FOG_CHANNEL', '$channel');/g" "$system_file" + + - name: Commit and push if the committed version is stale + env: + GITBRANCH: ${{ inputs.branch }} + BOT_SLUG: ${{ steps.app-token.outputs.app-slug }} + run: | + set -e + system_file="packages/web/lib/fog/system.class.php" + + if git diff --quiet -- "$system_file"; then + echo "Version already correct on $GITBRANCH - nothing to do." + exit 0 + fi + + echo "Version drift detected on $GITBRANCH:" + git diff -- "$system_file" + + git config user.name "${BOT_SLUG}[bot]" + git config user.email "${BOT_SLUG}[bot]@users.noreply.github.com" + + git add "$system_file" + git commit -m "chore: fix stale FOG_VERSION/FOG_CHANNEL on ${GITBRANCH}" + git push origin "HEAD:${GITBRANCH}" From a582c5a0dd8f78642cfa961689d2b477855ba90e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 00:49:31 +0000 Subject: [PATCH 2/4] Add reusable workflow to bootstrap the version-check stub on new branches check-fog-version.yml's push trigger only fires on a branch that already has the stub committed to it. A branch cut from dev-branch after dev-branch has the stub inherits it for free, but a branch created any other way (or before dev-branch had it) would silently go unwatched. This reusable workflow, called from a new create-triggered stub in fogproject, copies the canonical stub - whatever's currently committed on dev-branch - onto any newly created working-1.6/dev-branch/rc-*/ feature-* branch that doesn't already have it. stable and anything outside those patterns are left untouched. --- .../bootstrap-fog-version-check-stub.yml | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 .github/workflows/bootstrap-fog-version-check-stub.yml diff --git a/.github/workflows/bootstrap-fog-version-check-stub.yml b/.github/workflows/bootstrap-fog-version-check-stub.yml new file mode 100644 index 0000000..bd9e830 --- /dev/null +++ b/.github/workflows/bootstrap-fog-version-check-stub.yml @@ -0,0 +1,78 @@ +name: Bootstrap FOG version check stub + +# Reusable workflow backing fogproject's create-triggered bootstrap stub +# (.github/workflows/bootstrap-fog-version-check-stub.yml on fogproject's +# default branch). Fires whenever a new branch is created in fogproject. +# +# check-fog-version.yml's push trigger only fires on a branch that already +# has a copy of that stub file committed to it. New feature-*/rc-* branches +# cut from dev-branch inherit it for free once dev-branch has it, but a +# branch created from anywhere else (or before dev-branch had it) would +# silently never be watched. This copies the canonical stub - whatever is +# currently committed on dev-branch - onto the new branch directly, if it +# matches the watched patterns and doesn't already have it. +# +# Never touches `stable` (excluded from the watched patterns, same as +# check-fog-version.yml itself) or any branch outside those patterns. + +on: + workflow_call: + inputs: + branch: + required: true + type: string + +jobs: + bootstrap-stub: + runs-on: ubuntu-24.04 + steps: + - name: Check branch matches watched patterns + id: match + env: + BRANCH: ${{ inputs.branch }} + run: | + matches=false + case "$BRANCH" in + working-1.6|dev-branch|rc-*|feature-*) matches=true ;; + esac + echo "matches=$matches" >> "$GITHUB_OUTPUT" + + - uses: actions/create-github-app-token@v3 + if: steps.match.outputs.matches == 'true' + id: app-token + with: + client-id: ${{ vars.FOG_WORKFLOWS_APPID }} + private-key: ${{ secrets.FOG_WORKFLOWS_PRIVATE_KEY }} + owner: FOGProject + repositories: "fogproject" + + - uses: actions/checkout@v7 + if: steps.match.outputs.matches == 'true' + with: + token: ${{ steps.app-token.outputs.token }} + repository: FOGProject/fogproject + ref: ${{ inputs.branch }} + + - name: Copy the stub from dev-branch if this branch doesn't have it yet + if: steps.match.outputs.matches == 'true' + env: + BRANCH: ${{ inputs.branch }} + BOT_SLUG: ${{ steps.app-token.outputs.app-slug }} + run: | + set -e + stub_path=".github/workflows/check-fog-version.yml" + + if [ -f "$stub_path" ]; then + echo "Stub already present on $BRANCH - nothing to do." + exit 0 + fi + + git fetch --depth 1 origin dev-branch + mkdir -p "$(dirname "$stub_path")" + git show origin/dev-branch:"$stub_path" > "$stub_path" + + git config user.name "${BOT_SLUG}[bot]" + git config user.email "${BOT_SLUG}[bot]@users.noreply.github.com" + git add "$stub_path" + git commit -m "ci: bootstrap check-fog-version stub on ${BRANCH}" + git push origin "HEAD:${BRANCH}" From 9eb8808829bca7b5d9510ec38059996ac59d6bf4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 01:16:56 +0000 Subject: [PATCH 3/4] Add daily sweep to bootstrap the stub, without touching fogproject's stable GitHub only resolves a create-event workflow from a repo's default branch, which for fogproject is stable - so a create-triggered stub there was rejected in favor of a scheduled sweep that stays entirely in this repo. bootstrap-fog-version-check-stub-sweep.yml runs daily, lists fogproject branches, filters to the ones check-fog-version.yml watches, and hands off any missing the stub to the existing single-branch worker (now also runnable by hand via workflow_dispatch). --- ...bootstrap-fog-version-check-stub-sweep.yml | 66 +++++++++++++++++++ .../bootstrap-fog-version-check-stub.yml | 19 ++++-- 2 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/bootstrap-fog-version-check-stub-sweep.yml diff --git a/.github/workflows/bootstrap-fog-version-check-stub-sweep.yml b/.github/workflows/bootstrap-fog-version-check-stub-sweep.yml new file mode 100644 index 0000000..f40df20 --- /dev/null +++ b/.github/workflows/bootstrap-fog-version-check-stub-sweep.yml @@ -0,0 +1,66 @@ +name: Sweep for branches missing the FOG version check stub + +# Runs daily. Lists fogproject branches, filters to the ones +# check-fog-version.yml watches (working-1.6/dev-branch/rc-*/feature-*, +# `stable` excluded), and hands off any that are missing the stub to +# bootstrap-fog-version-check-stub.yml. +# +# This exists instead of a create-triggered stub in fogproject because +# GitHub only resolves a `create`-event workflow from the repo's default +# branch, which for fogproject is `stable` - and this project's automation +# is kept off of `stable` entirely. A daily delay before a brand-new branch +# starts being watched is an acceptable trade for that: it only delays when +# watching starts, it doesn't cause a missed version fix on a branch +# already being watched. + +on: + schedule: + - cron: "30 4 * * *" + workflow_dispatch: {} + +jobs: + discover: + runs-on: ubuntu-24.04 + outputs: + branches: ${{ steps.discover.outputs.branches }} + steps: + - uses: actions/create-github-app-token@v3 + id: app-token + with: + client-id: ${{ vars.FOG_WORKFLOWS_APPID }} + private-key: ${{ secrets.FOG_WORKFLOWS_PRIVATE_KEY }} + owner: FOGProject + repositories: "fogproject" + + - name: Find watched branches missing the stub + id: discover + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + set -e + all_branches=$(gh api repos/FOGProject/fogproject/branches --paginate --jq '.[].name') + + missing=() + for b in $all_branches; do + case "$b" in + working-1.6|dev-branch|rc-*|feature-*) ;; + *) continue ;; + esac + if ! gh api "repos/FOGProject/fogproject/contents/.github/workflows/check-fog-version.yml?ref=$b" >/dev/null 2>&1; then + missing+=("$b") + fi + done + + json=$(printf '%s\n' "${missing[@]:-}" | jq -R . | jq -sc 'map(select(length > 0))') + echo "branches=$json" >> "$GITHUB_OUTPUT" + echo "Missing the stub on: $json" + + bootstrap: + needs: discover + if: needs.discover.outputs.branches != '[]' + strategy: + matrix: + branch: ${{ fromJson(needs.discover.outputs.branches) }} + uses: ./.github/workflows/bootstrap-fog-version-check-stub.yml + with: + branch: ${{ matrix.branch }} diff --git a/.github/workflows/bootstrap-fog-version-check-stub.yml b/.github/workflows/bootstrap-fog-version-check-stub.yml index bd9e830..4109991 100644 --- a/.github/workflows/bootstrap-fog-version-check-stub.yml +++ b/.github/workflows/bootstrap-fog-version-check-stub.yml @@ -1,19 +1,24 @@ name: Bootstrap FOG version check stub -# Reusable workflow backing fogproject's create-triggered bootstrap stub -# (.github/workflows/bootstrap-fog-version-check-stub.yml on fogproject's -# default branch). Fires whenever a new branch is created in fogproject. +# Single-branch worker, called by bootstrap-fog-version-check-stub-sweep.yml +# for each fogproject branch missing the stub (also runnable by hand via +# workflow_dispatch against one branch). # # check-fog-version.yml's push trigger only fires on a branch that already # has a copy of that stub file committed to it. New feature-*/rc-* branches # cut from dev-branch inherit it for free once dev-branch has it, but a # branch created from anywhere else (or before dev-branch had it) would # silently never be watched. This copies the canonical stub - whatever is -# currently committed on dev-branch - onto the new branch directly, if it +# currently committed on dev-branch - onto the given branch directly, if it # matches the watched patterns and doesn't already have it. # # Never touches `stable` (excluded from the watched patterns, same as # check-fog-version.yml itself) or any branch outside those patterns. +# Deliberately not triggered by anything in fogproject itself (unlike +# check-fog-version.yml's push stub) - GitHub only resolves a `create`-event +# workflow from fogproject's default branch, which is `stable`, and this +# repo's automation is kept off of `stable` entirely. The sweep workflow +# polls instead. on: workflow_call: @@ -21,6 +26,12 @@ on: branch: required: true type: string + workflow_dispatch: + inputs: + branch: + description: "fogproject branch to bootstrap the stub onto" + required: true + type: string jobs: bootstrap-stub: From 9f1f2fd6425ee84aedc62bac00ff21a1eaace4f5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 01:20:28 +0000 Subject: [PATCH 4/4] Add daily failsafe sweep for the version check + manual dispatch check-fog-version.yml now also accepts workflow_dispatch directly (single branch, for manual runs/testing) alongside workflow_call. New check-fog-version-sweep.yml runs daily plus workflow_dispatch, listing every branch check-fog-version.yml watches and running the real check-and-fix logic against each one - a catch-all for anything the push-triggered stub in fogproject might miss (missed webhook delivery, an Actions outage during the original push, a branch whose stub wasn't bootstrapped yet, etc), independent of whether the stub is even present on that branch. --- .github/workflows/check-fog-version-sweep.yml | 61 +++++++++++++++++++ .github/workflows/check-fog-version.yml | 18 +++++- 2 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/check-fog-version-sweep.yml diff --git a/.github/workflows/check-fog-version-sweep.yml b/.github/workflows/check-fog-version-sweep.yml new file mode 100644 index 0000000..a9e6ed9 --- /dev/null +++ b/.github/workflows/check-fog-version-sweep.yml @@ -0,0 +1,61 @@ +name: Sweep FOG version check across all watched branches + +# Failsafe catch-all, running daily in addition to the push-triggered stub in +# fogproject. The push trigger should catch essentially everything, but this +# covers anything it misses - a missed webhook delivery, an Actions outage +# during the original push, a branch whose stub hadn't been bootstrapped yet +# at push time, etc. +# +# Lists fogproject branches, filters to the ones check-fog-version.yml +# watches (working-1.6/dev-branch/rc-*/feature-*, `stable` excluded - owned +# by stable-releases.yml's release flow), and runs the real check-and-fix +# workflow against every one of them, whether or not the push stub is even +# present there yet. + +on: + schedule: + - cron: "45 4 * * *" + workflow_dispatch: {} + +jobs: + discover: + runs-on: ubuntu-24.04 + outputs: + branches: ${{ steps.discover.outputs.branches }} + steps: + - uses: actions/create-github-app-token@v3 + id: app-token + with: + client-id: ${{ vars.FOG_WORKFLOWS_APPID }} + private-key: ${{ secrets.FOG_WORKFLOWS_PRIVATE_KEY }} + owner: FOGProject + repositories: "fogproject" + + - name: List watched branches + id: discover + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + set -e + all_branches=$(gh api repos/FOGProject/fogproject/branches --paginate --jq '.[].name') + + watched=() + for b in $all_branches; do + case "$b" in + working-1.6|dev-branch|rc-*|feature-*) watched+=("$b") ;; + esac + done + + json=$(printf '%s\n' "${watched[@]:-}" | jq -R . | jq -sc 'map(select(length > 0))') + echo "branches=$json" >> "$GITHUB_OUTPUT" + echo "Watched branches: $json" + + check-version: + needs: discover + if: needs.discover.outputs.branches != '[]' + strategy: + matrix: + branch: ${{ fromJson(needs.discover.outputs.branches) }} + uses: ./.github/workflows/check-fog-version.yml + with: + branch: ${{ matrix.branch }} diff --git a/.github/workflows/check-fog-version.yml b/.github/workflows/check-fog-version.yml index 5c7bfc0..483c156 100644 --- a/.github/workflows/check-fog-version.yml +++ b/.github/workflows/check-fog-version.yml @@ -1,8 +1,14 @@ name: Check FOG version -# Reusable workflow backing the push-triggered stub in FOGProject/fogproject's -# .github/workflows/check-fog-version.yml (see fog-docs' Development section -# for the full picture of why this is split across two repos). +# Reusable workflow backing: +# - the push-triggered stub in FOGProject/fogproject's +# .github/workflows/check-fog-version.yml (instant, per-branch), and +# - check-fog-version-sweep.yml in this repo (daily, all watched branches - +# a failsafe catch-all for anything the push trigger misses, e.g. a +# missed webhook delivery or an Actions outage during the original push). +# Also directly workflow_dispatch-able against a single branch by hand. +# See fog-docs' Development section for the full picture of why this is +# split across two repos. # # Recomputes FOG_VERSION/FOG_CHANNEL using the same formula as fogproject's # .githooks/pre-commit. That hook is client-side and never runs on a PR merged @@ -20,6 +26,12 @@ on: branch: required: true type: string + workflow_dispatch: + inputs: + branch: + description: "fogproject branch to check" + required: true + type: string jobs: check-version: