#!/usr/bin/env bash # reconcile-upstream — sync recipe mirrors from TRUE upstream. Run this FIRST, always. # ---------------------------------------------------------------------------------- # Every recipe we maintain is a MIRROR of a coopcloud recipe. Work done against a stale # mirror is wasted or wrong, in three ways we have actually hit: # # 1. A PR whose changes upstream ALREADY MERGED. mailu #6 (2024.06.57 + redis 8.10, # two internet-facing Roundcube CVEs) sat open and was reported as the fix for # those CVEs — while upstream had merged and released it as 3.1.3+2024.06.57. The # work was done; only our mirror was behind. # 2. A survey that reads the stale mirror and reports "no upgrades available", so a # recipe silently drops out of the weekly run. # 3. Reading the WRONG BRANCH. Several coopcloud recipes keep a stale `main` beside # the real default `master` — gitea's `main` is at 1.24.2-rootless while `master` # has 1.27.1-rootless plus the merged PRs. Reading `main` there says the recipe is # three releases behind and missing two CVSS-9.8 RCE fixes, which reads exactly # like a real finding. open-recipe-pr.sh resolves the default branch itself # (main OR master) — never hand-pick one. # # This is deterministic: it force-syncs each mirror's `main` to upstream's default # branch and closes any mirror PR whose changes are already upstream. No AI judgement. # # reconcile-upstream.sh ... # specific recipes # reconcile-upstream.sh --all # every recipe in used-recipes.md # # Safe to run repeatedly; a mirror already in sync is a no-op. Recipe work lives in # BRANCHES, never on mirror `main`, so force-syncing `main` discards nothing. set -o errexit -o nounset -o pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ORCH="$(dirname "$HERE")" SSH="${SSH:-cc-ci}" TESTENV="${TESTENV:-/srv/cc-ci/.testenv}" RECONCILE="${RECONCILE:-$ORCH/.claude/skills/recipe-upgrade/open-recipe-pr.sh}" USED_RECIPES="${USED_RECIPES:-$HERE/used-recipes.md}" [ -f "$RECONCILE" ] || { echo "ERROR: reconcile helper not found: $RECONCILE" >&2; exit 1; } set -a; . "$TESTENV"; set +a : "${GITEA_USERNAME:?}"; : "${GITEA_PASSWORD:?}"; : "${GITEA_URL:?}" if [ "${1:-}" = "--all" ]; then mapfile -t RECIPES < <(awk '!/^[[:space:]]*#/ && ($2=="weekly" || $2=="external") {print $1}' "$USED_RECIPES") else [ "$#" -gt 0 ] || { echo "usage: reconcile-upstream.sh ... | --all" >&2; exit 2; } RECIPES=("$@") fi synced=0; closed=0; failed=0 for r in "${RECIPES[@]}"; do echo "── $r" if out="$(ssh "$SSH" "GITEA_USERNAME='$GITEA_USERNAME' GITEA_PASSWORD='$GITEA_PASSWORD' GITEA_URL='$GITEA_URL' bash -s $r --reconcile-only" < "$RECONCILE" 2>&1)"; then printf '%s\n' "$out" | grep -E "Force-syncing|already in sync|closed PR|still open|✓" | sed 's/^/ /' || true synced=$((synced + 1)) closed=$((closed + $(printf '%s' "$out" | grep -c "closed PR" || true))) else printf '%s\n' "$out" | tail -3 | sed 's/^/ /' echo " ✗ FAILED — do NOT proceed against this mirror until it reconciles" failed=$((failed + 1)) fi done echo echo "reconcile-upstream: ${synced} mirror(s) synced, ${closed} already-upstream PR(s) closed, ${failed} failed" [ "$failed" -eq 0 ]