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

217 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description: 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
argument-hint: <autonomic-pr-url>
allowed-tools: [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`:
```bash
#!/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`.