Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f6a362550 | ||
|
|
19f0329d62 | ||
|
|
22d8ea0ae9 | ||
|
|
e37e10c86b |
@@ -264,11 +264,13 @@ ssh cc-ci "GITEA_USERNAME='$GITEA_USERNAME' GITEA_PASSWORD='$GITEA_PASSWORD' GIT
|
||||
```
|
||||
This (a) re-syncs mirror `main` to true upstream main, (b) **closes any open PR already merged
|
||||
upstream** (merging it would be a no-op), then handles the upgrade PR: **if an open upgrade PR already
|
||||
exists for this recipe (branch `upgrade-*`), the new work is pushed onto THAT PR's branch and the PR is
|
||||
updated + re-tested** — one evolving upgrade PR per recipe, not a second parallel one. Only if none
|
||||
exists for this recipe (branch `upgrade-*`), its branch is replaced with one commit directly on the
|
||||
current upstream tip using `--force-with-lease`, then the PR is updated + re-tested** — one evolving
|
||||
upgrade PR per recipe with one commit, not a second parallel PR. Only if none
|
||||
exists is a fresh `upgrade-<version>` PR opened. Unrelated open PRs (e.g. a backup fix) are **NOT**
|
||||
closed or touched — the operator decides on those. The PR's diff is exactly this upgrade against the
|
||||
real current upstream main. Capture the `PR_URL`. Export `RECIPE_PR_BODY` first (image-tag table + the
|
||||
real current upstream main. This squash happens **before `!testme`**, so CI tests the one-commit PR
|
||||
head. Capture the `PR_URL`. Export `RECIPE_PR_BODY` first (image-tag table + the
|
||||
planned operator-action notes) — and **the PR body MUST link the upstream release notes**: for each
|
||||
upgraded image/service, pull its release-notes URL from the registry `cc-ci-plan/upstream/<recipe>.md`
|
||||
(between the current → new version) and include one explicit line per service in the body, e.g.
|
||||
@@ -297,7 +299,9 @@ POST=0 /srv/cc-ci/.claude/skills/recipe-upgrade/testme-on-pr.sh <recipe> <pr-ind
|
||||
Read the failing run (linked from the PR / the harness log on cc-ci) and classify:
|
||||
|
||||
**5a. The UPGRADE itself is broken** (real regression: bad tag, missing migration, config gap) — fix
|
||||
it on the recipe branch, push, and re-run `!testme` (within the 3-run budget). The recipe PR is only
|
||||
it on the local recipe branch, then re-run `open-recipe-pr.sh` from step 3 to squash the corrected
|
||||
tree into one commit on the PR branch **before** re-running `!testme` (within the 3-run budget).
|
||||
Do not push a fix commit directly to the PR branch. The recipe PR is only
|
||||
"working" once `!testme` is GREEN. If still red after 3 runs, leave the PR open (its red `!testme`
|
||||
results stand as the evidence) and report `FAILED — upgrade not green after 3 !testme runs`.
|
||||
|
||||
@@ -359,8 +363,8 @@ Always state explicitly that **nothing was merged** — the PR(s) await operator
|
||||
— never touch the gate. (Interim safeguard while the upgrader runs on the cc-ci host; see `IDEAS.md`.)
|
||||
- **Mirror reflects reality.** Each run force-syncs the `recipe-maintainers/<recipe>` mirror `main` to
|
||||
true upstream main and closes open PRs already merged upstream. It does NOT close superseded/unrelated
|
||||
open PRs. **An existing open upgrade PR is extended (new work pushed onto its branch + re-tested)
|
||||
rather than spawning a second upgrade PR** — one evolving upgrade PR per recipe.
|
||||
open PRs. **An existing open upgrade PR is rewritten with one commit on the current upstream tip
|
||||
using `--force-with-lease`, then re-tested** — one evolving upgrade PR per recipe.
|
||||
- **Prefer a recipe-only PR.** Only open a cc-ci test PR (under `--with-tests`) when the upgrade is
|
||||
correct but a test is genuinely stale — never to paper over a real upgrade regression.
|
||||
- **Never weaken a test**; **bounded** changes (the upgrade + minimal test update, not rewrites).
|
||||
|
||||
@@ -20,11 +20,9 @@
|
||||
# never reflected it.
|
||||
#
|
||||
# Default mode (open a PR — preconditions: HEAD has the upgrade commit(s)):
|
||||
# - If an open UPGRADE PR already exists for this recipe (branch 'upgrade-*'), the new work is added
|
||||
# as a commit ON TOP of THAT PR's branch (a fast-forward — NO force-push) and the PR is updated +
|
||||
# re-tested — ONE evolving upgrade PR per recipe, NOT a second parallel PR. Otherwise a fresh
|
||||
# 'upgrade-<version>' branch + PR is opened. (The only force-push here is the mirror-main sync
|
||||
# above, which intentionally mirrors upstream main; PR branches are never force-pushed.)
|
||||
# - Publish the final recipe tree as ONE commit directly on current upstream main before !testme.
|
||||
# If an open upgrade PR exists, replace its branch with this single commit using
|
||||
# --force-with-lease, then re-test the same PR. Otherwise open a fresh upgrade PR.
|
||||
# - Unrelated open PRs (e.g. a backup fix) are NEVER closed or touched — left for the operator.
|
||||
# - Only PRs already merged into upstream main are closed (no-op merges) — see above.
|
||||
#
|
||||
@@ -165,9 +163,8 @@ if [ "${MODE}" = "--reconcile-only" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- DEFAULT: if an open upgrade PR exists, push the new work onto ITS branch and re-test it, so
|
||||
# there's ONE evolving upgrade PR per recipe rather than parallel ones. Only if none exists do we
|
||||
# open a fresh upgrade-<version> PR. Unrelated open PRs are never touched. ---
|
||||
# --- DEFAULT: publish one commit with the final tree on current upstream main. This collapses
|
||||
# old local commits and merge commits while preserving the exact content to be tested. ---
|
||||
if [ -n "${EXTEND_PR}" ]; then
|
||||
BRANCH="${EXTEND_BRANCH}"
|
||||
echo "→ Extending existing open upgrade PR #${EXTEND_PR} (branch ${BRANCH}) with the new work — it will be re-tested; NOT opening a separate PR."
|
||||
@@ -178,39 +175,38 @@ else
|
||||
BRANCH="upgrade-$(git rev-parse --short HEAD)"
|
||||
fi
|
||||
|
||||
# Land the work on the PR branch WITHOUT force-pushing. If the branch already exists (extend case, or
|
||||
# a stale same-named branch), add a commit ON TOP of its tip — a fast-forward, so the PR's history is
|
||||
# preserved and it re-tests. Otherwise push a fresh branch.
|
||||
# Do not create an empty PR when the proposed tree equals current upstream main.
|
||||
UPSTREAM_TREE=$(git rev-parse "${NEW_MAIN_SHA}^{tree}")
|
||||
WORK_TREE=$(git rev-parse 'HEAD^{tree}')
|
||||
[ "${WORK_TREE}" != "${UPSTREAM_TREE}" ] || {
|
||||
echo "ERROR: proposed recipe tree is identical to upstream main; nothing to PR."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# The PR branch is a single commit on the current upstream tip. Reusing an existing PR requires a
|
||||
# guarded rewrite; the lease rejects concurrent updates instead of overwriting another writer.
|
||||
SQUASHED=$(GIT_AUTHOR_NAME="${GITEA_USERNAME}" GIT_AUTHOR_EMAIL="${GITEA_USERNAME}@git.autonomic.zone" \
|
||||
GIT_COMMITTER_NAME="${GITEA_USERNAME}" GIT_COMMITTER_EMAIL="${GITEA_USERNAME}@git.autonomic.zone" \
|
||||
git commit-tree "${WORK_TREE}" -p "${NEW_MAIN_SHA}" -m "${LATEST_MSG}")
|
||||
if git rev-parse --verify --quiet "refs/remotes/gitea/${BRANCH}" >/dev/null; then
|
||||
EXIST_TIP=$(git rev-parse "refs/remotes/gitea/${BRANCH}")
|
||||
if [ "$(git rev-parse 'HEAD^{tree}')" = "$(git rev-parse "${EXIST_TIP}^{tree}")" ]; then
|
||||
echo "→ '${BRANCH}' already has this exact tree — nothing new to push (will still re-test)."
|
||||
if [ "$(git rev-parse "${EXIST_TIP}^{tree}")" = "${WORK_TREE}" ] &&
|
||||
[ "$(git rev-parse "${EXIST_TIP}^" 2>/dev/null || true)" = "${NEW_MAIN_SHA}" ]; then
|
||||
echo "→ '${BRANCH}' already has this one-commit diff — nothing new to push (will still re-test)."
|
||||
else
|
||||
# Parent the new commit on the branch tip AND (when the branch predates it) on upstream main, so
|
||||
# the recorded HISTORY matches the tree we are pushing. Without the second parent the merge-base
|
||||
# stays stale: git would later treat upstream's post-branch changes as "removed by this PR" and a
|
||||
# merge could revert them (the gitea #5 / forgejo-removal drift, 2026-08-10). No force-push: this
|
||||
# is still a fast-forward from the branch tip.
|
||||
EXTRA_PARENT=()
|
||||
if ! git merge-base --is-ancestor "${NEW_MAIN_SHA}" "${EXIST_TIP}"; then
|
||||
EXTRA_PARENT=(-p "${NEW_MAIN_SHA}")
|
||||
echo " (also parenting on upstream main ${NEW_MAIN_SHA:0:8} — branch predated it)"
|
||||
fi
|
||||
ONTOP=$(GIT_AUTHOR_NAME="${GITEA_USERNAME}" GIT_AUTHOR_EMAIL="${GITEA_USERNAME}@git.autonomic.zone" \
|
||||
GIT_COMMITTER_NAME="${GITEA_USERNAME}" GIT_COMMITTER_EMAIL="${GITEA_USERNAME}@git.autonomic.zone" \
|
||||
git commit-tree "$(git rev-parse 'HEAD^{tree}')" -p "${EXIST_TIP}" "${EXTRA_PARENT[@]}" -m "${LATEST_MSG}")
|
||||
echo "→ Adding the new work on top of '${BRANCH}' (fast-forward, no force-push)..."
|
||||
git push gitea "${ONTOP}:refs/heads/${BRANCH}"
|
||||
echo "→ Replacing '${BRANCH}' with one commit on upstream ${NEW_MAIN_SHA:0:8} (force-with-lease)..."
|
||||
git push --force-with-lease="refs/heads/${BRANCH}:${EXIST_TIP}" gitea \
|
||||
"${SQUASHED}:refs/heads/${BRANCH}"
|
||||
fi
|
||||
else
|
||||
echo "→ Pushing new branch '${BRANCH}'..."
|
||||
git push gitea "HEAD:refs/heads/${BRANCH}"
|
||||
echo "→ Pushing one-commit branch '${BRANCH}' on upstream ${NEW_MAIN_SHA:0:8}..."
|
||||
git push gitea "${SQUASHED}:refs/heads/${BRANCH}"
|
||||
fi
|
||||
|
||||
PR_BODY="${RECIPE_PR_BODY:-$(printf 'Recipe upgrade.\n\nCommits on top of upstream main:\n\n%s\n' "$(git log origin/main..HEAD --pretty='- %h %s')")}"
|
||||
PR_BODY="${RECIPE_PR_BODY:-$(printf 'Recipe upgrade as one commit on upstream %s.\n\n%s\n' "${NEW_MAIN_SHA:0:8}" "$(git diff --stat "${NEW_MAIN_SHA}" "${SQUASHED}")")}"
|
||||
PR_BODY="${PR_BODY}
|
||||
|
||||
Tested green on the cc-ci recipe CI server (full suite, cold, against this PR head). NOT merged — for operator review.
|
||||
CI verification is run against this one-commit PR head. NOT merged — for operator review.
|
||||
|
||||
cc @trav @notplants"
|
||||
|
||||
|
||||
@@ -197,7 +197,8 @@ description `"Upgrade <recipe> on cc-ci"`) with a prompt like:
|
||||
|
||||
> Run the `/recipe-upgrade <recipe>` skill end-to-end — **DEFAULT mode, do NOT pass `--with-tests`**
|
||||
> (`/srv/cc-ci/.claude/skills/recipe-upgrade/SKILL.md`): plan, implement, **directly deploy + inspect
|
||||
> the WIP on cc-ci for live feedback (step 2b, `--chaos`, tear it down after)**, open a recipe PR, and
|
||||
> the WIP on cc-ci for live feedback (step 2b, `--chaos`, tear it down after)**, open a recipe PR as
|
||||
> **one commit based on current upstream main**, and
|
||||
> verify it by posting `!testme` on the PR (results visible in the PR; iterate ≤3×). Use the **existing
|
||||
> tests** — if a test fails because it is genuinely stale, **leave an explanatory comment on the PR**
|
||||
> for the operator and do NOT modify any test. Drive cc-ci over `ssh cc-ci`. Do NOT prompt. Do NOT push
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
|---------|-------|-------------|----------------------|
|
||||
| app | nginx | https://github.com/nginx/nginx | https://nginx.org/en/CHANGES |
|
||||
| git | alpine/git | https://github.com/alpine-docker/git | https://hub.docker.com/r/alpine/git/tags |
|
||||
| ssh | lscr.io/linuxserver/openssh-server | https://github.com/linuxserver/docker-openssh-server | https://github.com/linuxserver/docker-openssh-server/releases |
|
||||
|
||||
## Standing notes
|
||||
- nginx even-numbered minor versions (1.28.x, 1.30.x) are mainline; odd (1.27.x, 1.29.x) are stable. The recipe tracks mainline.
|
||||
- nginx 1.31.7 (14 Oct 2026) is a security patch release with FIVE security fixes: CVE-2026-15176 (buffer overflow in a worker using `alias` when the URI was changed by `rewrite`), CVE-2026-93479 (buffer overflow evaluating string expressions with variables — affects document-root calculation and request logging in http/stream), CVE-2026-95101 (HTTP/3: part of an unknown frame treated as request body — bypasses `client_max_body_size` / injects into proxied request), CVE-2026-97629 (TLS session-ticket key rotation left one key uninitialized — session forgery), CVE-2026-97205 (use-after-free with async file I/O when an SSI page has `include virtual`). Features: geoip MMDB support, backend SSL cert verification by IP, QUIC reconfiguration + binary upgrade with `quic_bpf`. Change: `ngx_http_v2_module` now uses RFC 9218 prioritisation (RFC 7540 priority signals ignored). Bugfixes (X-Accel-Redirect cached response segfault, dav COPY/MOVE with alias segfault, QUIC flood false positives, SSL close_notify handling). NONE affect the custom-html static-file-serving use case (no HTTP/3/QUIC, no AIO/SSI, no `rewrite`+`alias` path mapping, no session-ticket rotation config) — but the 5 CVEs are why this bump is warranted. No breaking config changes; no operator action beyond the image pull.
|
||||
- compose.git-pull.yml is an optional overlay for git-pull functionality; alpine/git version lives there.
|
||||
- compose.sftp.yml and compose.sso.yml are other optional overlays; linuxserver/openssh-server uses `latest` tag and is not version-pinned.
|
||||
- compose.sftp.yml and compose.sso.yml are other optional overlays; linuxserver/openssh-server uses the moving `latest` tag and is pinned by digest (`image:latest@sha256:…`). Because `latest` is rebuilt regularly, its upstream multi-arch index digest moves even when no tag/version changes; re-check the index digest directly with `docker buildx imagetools inspect lscr.io/linuxserver/openssh-server:latest --format '{{.Manifest.Digest}}'` (abra FATAs on `tag@digest` refs and cannot see this). 2026-11-13: pinned `aa4e1b39…` was stale; live index digest is `sha256:161e0b670b31d9539ca1718bc5943e981779331b4e1c9d32e8929d7652a1f22f` (amd64 child `7bd52bcb…`, arm64 `699500b1…`).
|
||||
- nginx 1.31.6 (15 Sep 2026) is a security patch release: CVE-2026-90439 (heap buffer overflow in a worker under certain configurations when using HTTP/3 with OpenSSL 3.5.0 and earlier). Change: QUIC transport parameters extension received in an SSL connection is now always ignored. Bugfixes: binary upgrade with control API socket + perl module; predicate-location evaluation error ignored; nested location lookup with regex/predicate locations; segfault reading config with `geo` … `ranges` + corrupted binary base file. None affect the custom-html static-file-serving use case (no HTTP/3/QUIC, no predicate locations, no perl, no `geo`).
|
||||
- Breaking change in nginx 1.31.0: HTTP/2 and HTTP/3 requests with Connection/Proxy-Connection/Keep-Alive/Transfer-Encoding/Upgrade/TE headers are now rejected.
|
||||
- Breaking change in nginx 1.29.7: keepalive in upstream block is enabled by default; proxy_http_version changed to 1.1; Connection proxy header no longer sent by default.
|
||||
|
||||
Reference in New Issue
Block a user