Files
recipe-maintainer/.claude/commands/recipe-upstream.md
T
autonomic-bot f283a371bb recipe-maintainer: public snapshot (secrets + deployment plans removed, single commit)
Sanitized single-commit public mirror of recipe-maintainer.
- Removed test-ssh/.testenv (live creds); added test-ssh/.testenv.example placeholders.
- Removed plans/ and planned-updates/ (deployment-planning docs) so no client/
  deployment domains appear in the public repo.
- All other secret stores were already gitignored.
- docs.coopcloud.tech retained as a submodule (public upstream).
2026-06-16 20:18:24 +00:00

10 KiB
Raw Blame History

description, argument-hint, allowed-tools
description argument-hint allowed-tools
From a git.autonomic.zone review-PR URL, fetch the branch + tag locally and emit the commands to open the upstream PR on git.coopcloud.tech <autonomic-pr-url>
Bash
Read

Recipe Upstream

Take a review PR created by /recipe-create-pr on git.autonomic.zone (e.g. https://git.autonomic.zone/recipe-maintainers/lasuite-docs/pulls/3) and prepare everything needed to open the corresponding upstream PR on git.coopcloud.tech — i.e. the "Next steps" block printed at the end of /recipe-upgrade-apply.

This sandbox has no SSH push access to git.coopcloud.tech (the dev remote uses ssh://git@git.coopcloud.tech:2222, which requires the maintainer's own key). So this command does everything it can locally — fetch the PR branch over the authenticated git.autonomic.zone remote and make sure the origin/dev remotes exist — and then prints the exact git push / PR commands plus the final abra recipe release command for you to run from a machine that has coopcloud SSH access. The release (version-label bump + tag + publish) is the last step, run after the upstream PR merges — it is not done in the PR.

The argument is a git.autonomic.zone pull request URL: $ARGUMENTS

Prerequisites

Credentials are read from test-ssh/.testenv (same as /recipe-create-pr):

  • GITEA_USERNAME — bot account on git.autonomic.zone
  • GITEA_PASSWORD — bot password
  • GITEA_URL — Gitea host (e.g. git.autonomic.zone)

The recipe must already be checked out at ~/.abra/recipes/<recipe>. If it isn't, tell the user to run abra recipe fetch <recipe> first and stop.

Steps

Run the following script with the PR URL substituted in for PR_URL:

#!/usr/bin/env bash
set -euo pipefail

# Prevent ZSH_VERSION unbound variable error when sourcing zsh-aware files under bash
ZSH_VERSION=${ZSH_VERSION:-}

PR_URL="$ARGUMENTS"
WORKSPACE="$(cd "$(dirname "$0")/../.." 2>/dev/null || echo "${HOME}/Documents/recipe-maintainer")"
# Support both /workspace (sandbox) and the actual project directory
for _d in /workspace "${HOME}/Documents/recipe-maintainer"; do
  [ -f "${_d}/test-ssh/.testenv" ] && { WORKSPACE="${_d}"; break; }
done
TESTENV="${WORKSPACE}/test-ssh/.testenv"

# --- Load credentials ---
[ -f "${TESTENV}" ] || { echo "ERROR: ${TESTENV} not found (tried /workspace and ~/Documents/recipe-maintainer)"; exit 1; }
set -a; . "${TESTENV}"; set +a
: "${GITEA_USERNAME:?missing in .testenv}"
: "${GITEA_PASSWORD:?missing in .testenv}"
: "${GITEA_URL:?missing in .testenv}"

# --- Parse the PR URL: https://<host>/<owner>/<recipe>/pulls/<num> ---
read -r HOST OWNER RECIPE PR_NUM < <(python3 - "$PR_URL" <<'PY'
import sys, urllib.parse
u = urllib.parse.urlparse(sys.argv[1])
parts = [p for p in u.path.split('/') if p]
# expect: <owner>/<recipe>/pulls/<num>
if len(parts) < 4 or parts[-2] not in ('pulls', 'pull'):
    sys.exit("ERROR: not a recognisable Gitea PR URL: %s" % sys.argv[1])
print(u.netloc, parts[0], parts[1], parts[-1])
PY
)
echo "→ Parsed PR: host=${HOST} owner=${OWNER} recipe=${RECIPE} pr=#${PR_NUM}"

if [ "${HOST}" != "${GITEA_URL}" ]; then
  echo "  ! warning: PR host (${HOST}) differs from GITEA_URL (${GITEA_URL}); using credentials anyway"
fi

RECIPE_DIR="${HOME}/.abra/recipes/${RECIPE}"
[ -d "${RECIPE_DIR}/.git" ] || { echo "ERROR: ${RECIPE_DIR} is not a git repo. Run 'abra recipe fetch ${RECIPE}' first."; exit 1; }

PASS_ENC=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1],safe=''))" "${GITEA_PASSWORD}")
API="https://${HOST}/api/v1"
AUTH=(-u "${GITEA_USERNAME}:${GITEA_PASSWORD}")

# --- Fetch PR metadata from the autonomic Gitea API ---
echo "→ Fetching PR metadata..."
PR_JSON=$(mktemp)
PR_STATUS=$(curl -s -o "${PR_JSON}" -w "%{http_code}" "${AUTH[@]}" "${API}/repos/${OWNER}/${RECIPE}/pulls/${PR_NUM}")
if [ "${PR_STATUS}" != "200" ]; then
  echo "ERROR: could not fetch PR (HTTP ${PR_STATUS}):"; cat "${PR_JSON}"; rm -f "${PR_JSON}"; exit 1
fi
read -r HEAD_REF BASE_REF PR_MERGED RELEASE_FLAG < <(python3 - "${PR_JSON}" <<'PY'
import json, sys, re
d = json.load(open(sys.argv[1]))
body = d.get("body", "") or ""
# The upgrade PR records the recommended release as an `abra recipe release <recipe> -<x|y|z>` line.
m = re.search(r'abra recipe release\s+\S+\s+(-[xyz])', body, re.I)
print(d["head"]["ref"], d["base"]["ref"], str(d.get("merged", False)).lower(), m.group(1) if m else "-")
PY
)
rm -f "${PR_JSON}"
echo "  head branch: ${HEAD_REF}"
echo "  base branch: ${BASE_REF}"
echo "  merged:      ${PR_MERGED}"

cd "${RECIPE_DIR}"

# --- Ensure the 'gitea' (autonomic) remote exists with credentials, then fetch the PR branch ---
GITEA_REMOTE_URL="https://${GITEA_USERNAME}:${PASS_ENC}@${HOST}/${OWNER}/${RECIPE}.git"
if git remote get-url gitea >/dev/null 2>&1; then
  git remote set-url gitea "${GITEA_REMOTE_URL}"
else
  git remote add gitea "${GITEA_REMOTE_URL}"
fi

echo "→ Fetching PR #${PR_NUM} head into local branch '${HEAD_REF}'..."
git fetch gitea "+refs/pull/${PR_NUM}/head:refs/heads/${HEAD_REF}"

# --- Determine the upstream (coop-cloud) namespace ---
# Prefer deriving it from an existing origin/dev remote; otherwise default to coop-cloud.
UPSTREAM_NS="coop-cloud"
for r in origin dev; do
  if URL=$(git remote get-url "$r" 2>/dev/null); then
    NS=$(echo "$URL" | sed -nE 's#.*git\.coopcloud\.tech[:/0-9]*/([^/]+)/[^/]+(\.git)?$#\1#p')
    [ -n "$NS" ] && { UPSTREAM_NS="$NS"; break; }
  fi
done
echo "→ Upstream namespace: ${UPSTREAM_NS}"

# --- Ensure origin (https, read) and dev (ssh, push) remotes exist ---
ORIGIN_URL="https://git.coopcloud.tech/${UPSTREAM_NS}/${RECIPE}.git"
DEV_URL="ssh://git@git.coopcloud.tech:2222/${UPSTREAM_NS}/${RECIPE}.git"
if git remote get-url origin >/dev/null 2>&1; then
  echo "  ✓ origin: $(git remote get-url origin)"
else
  git remote add origin "${ORIGIN_URL}"; echo "  + created origin -> ${ORIGIN_URL}"
fi
if git remote get-url dev >/dev/null 2>&1; then
  echo "  ✓ dev:    $(git remote get-url dev)"
else
  git remote add dev "${DEV_URL}"; echo "  + created dev -> ${DEV_URL}"
fi

# --- Determine the release command (run AT THE END, after the upstream PR merges) ---
# The upgrade PR does NOT bump the coop-cloud version label. The release is cut last,
# with a real `abra recipe release` — it bumps the label, commits, tags AND publishes
# (pushes the tag, which generates the catalogue entry) in one step. The PR body records
# the recommended semver bump as an `abra recipe release <recipe> -<x|y|z>` line; we
# surface that as the final recommendation. (No --dry-run: that would only compute and
# change nothing — here we want the operator to actually publish.)
if [ "${RELEASE_FLAG}" != "-" ]; then
  RELEASE_CMD="abra recipe release ${RECIPE} ${RELEASE_FLAG}"
  echo "→ Recommended release command: ${RELEASE_CMD}"
else
  RELEASE_CMD="abra recipe release ${RECIPE} -x|-y|-z   # choose: -x major / -y minor / -z patch"
  echo "→ Could not parse a release bump from the PR body — operator picks -x/-y/-z."
fi

# --- Emit the next-step commands for a machine WITH coopcloud SSH access ---
COMPARE_URL="https://git.coopcloud.tech/${UPSTREAM_NS}/${RECIPE}/compare/${BASE_REF}...${HEAD_REF}"
cat <<EOF

────────────────────────────────────────────────────────────────────
Prepared locally in ${RECIPE_DIR}:
  • branch '${HEAD_REF}' fetched from the autonomic PR (#${PR_NUM})
  • remotes: dev -> ${DEV_URL}

Run these from a machine with push access to git.coopcloud.tech
(needs ssh-agent loaded with the coopcloud key for steps 1 and 3):

  cd ~/.abra/recipes/${RECIPE}
  git checkout ${HEAD_REF}

  # 1. Push the branch to the upstream repo:
  git push dev HEAD:${HEAD_REF}

  # 2. Open the upstream PR (${HEAD_REF} -> ${BASE_REF}):
  #    ${COMPARE_URL}

  # 3. AFTER the upstream PR is merged, publish the release (bumps the version
  #    label, commits, tags AND pushes the tag upstream — all in one step):
  ${RELEASE_CMD}
────────────────────────────────────────────────────────────────────
EOF

if [ "${PR_MERGED}" = "true" ]; then
  echo " Note: the autonomic review PR #${PR_NUM} is already marked merged."
fi

After the script runs, report back to the user:

  • The recipe and the PR branch (HEAD_REF) that were prepared locally.
  • That the branch is staged locally but not pushed to coopcloud (no SSH access from here).
  • The upstream PR compare URL.
  • The three commands (push branch → open PR → abra recipe release after merge) verbatim, so the user can run them from a machine with git.coopcloud.tech push access.

Notes

  • The version bump happens at the END, not in the PR. The upgrade PR only changes image tags + config; it deliberately does not touch the coop-cloud.${STACK_NAME}.version label. The release is cut last, after the upstream PR merges, with a real abra recipe release <recipe> -x|-y|-z (NO --dry-run): that single command bumps the version label, commits, creates the tag, and pushes the tag upstream — which is what publishes the release to the Co-op Cloud catalogue. The recommended -x/-y/-z is read from the PR body's abra recipe release … line (recorded by /recipe-upgrade-apply from the plan's semver reasoning); if it can't be parsed, the operator picks the bump.
  • Run order matters: push the branch and open the upstream PR first; run abra recipe release only after that PR merges. In Co-op Cloud the catalogue is generated from git tags, and abra recipe release pushes the tag, so it publishes — do it last.
  • The dev remote is created pointing at ssh://git@git.coopcloud.tech:2222/<namespace>/<recipe>.git. The namespace is derived from any existing origin/dev remote, defaulting to coop-cloud.
  • Re-running is safe: the gitea remote URL and the PR branch are force-fetched, and the tag is only created if missing (a mismatched existing tag is reported, never overwritten).
  • The gitea remote URL embeds the bot password (same trade-off as /recipe-create-pr); it's written into ~/.abra/recipes/<recipe>/.git/config.