From fbd4b8128294bad632bae29b03a781822989182c Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Mon, 8 Jun 2026 13:50:37 +0200 Subject: [PATCH 1/5] fix(updater): guard against multiline version match in latest.feature The first_stable and prerelease update paths extract the current latest version from latest.feature with: grep -A4 'latest stable release' | grep 'Version "' | grep -oP ... The real updater_server latest.feature holds several "latest stable"/ "latest beta" scenarios (e.g. PHP-version-pinned ones), so the grep returned multiple version strings. The resulting multiline variable was interpolated into a sed `s|...|...|` expression, embedding a newline and producing: sed: -e expression #1, char 72: unterminated `s' command This only surfaced in production because the test fixture's latest.feature had a single stable + single beta scenario, so grep matched once and the bug stayed hidden. Fix: pipe both extractions through `head -1` (the first scenario is the primary entry). Expand the base test fixture with the extra PHP-version and error scenarios from the real repo so the multi-match case is covered, and regenerate the affected expected outputs. Signed-off-by: Barthelemy Briand Signed-off-by: skjnldsv --- .github/scripts/update-updater-server.sh | 10 +++++-- .../tests/integration/features/latest.feature | 29 +++++++++++++++++++ .../tests/integration/features/latest.feature | 29 +++++++++++++++++++ .../tests/integration/features/latest.feature | 29 +++++++++++++++++++ .../tests/integration/features/latest.feature | 29 +++++++++++++++++++ .../tests/integration/features/latest.feature | 29 +++++++++++++++++++ .../tests/integration/features/latest.feature | 29 +++++++++++++++++++ .../tests/integration/features/latest.feature | 29 +++++++++++++++++++ 8 files changed, 210 insertions(+), 3 deletions(-) diff --git a/.github/scripts/update-updater-server.sh b/.github/scripts/update-updater-server.sh index d6974679126..783c977ff2a 100755 --- a/.github/scripts/update-updater-server.sh +++ b/.github/scripts/update-updater-server.sh @@ -467,9 +467,12 @@ ${NEW_SIG_BLOCK} SCENARIO # Update latest.feature - # Find current latest stable version and replace + # Find current latest stable version and replace. + # head -1: latest.feature may hold several "latest stable" scenarios + # (e.g. a PHP-version-pinned one); only the first is the primary entry. + # Without it the var goes multiline and breaks the sed below. CURRENT_LATEST_STABLE=$(grep -A4 'latest stable release' "$LATEST_FEATURE" \ - | grep 'Version "' | grep -oP 'Version "\K[^"]+') + | grep 'Version "' | grep -oP 'Version "\K[^"]+' | head -1) CURRENT_LATEST_STABLE_URL=$(echo "$CURRENT_LATEST_STABLE" | sed 's/ //g' | tr '[:upper:]' '[:lower:]') if [[ -n "$CURRENT_LATEST_STABLE" ]]; then @@ -541,8 +544,9 @@ ${NEW_SIG_BLOCK} SCENARIO # Update latest.feature — beta now points to this pre-release + # head -1: see CURRENT_LATEST_STABLE note — guard against multiline. CURRENT_LATEST_BETA=$(grep -A4 'latest beta release' "$LATEST_FEATURE" \ - | grep 'Version "' | grep -oP 'Version "\K[^"]+') + | grep 'Version "' | grep -oP 'Version "\K[^"]+' | head -1) CURRENT_LATEST_BETA_URL=$(echo "$CURRENT_LATEST_BETA" | sed 's/ //g' | tr '[:upper:]' '[:lower:]') if [[ -n "$CURRENT_LATEST_BETA" ]]; then diff --git a/tests/updater-script/base/tests/integration/features/latest.feature b/tests/updater-script/base/tests/integration/features/latest.feature index 0ab7f5422bb..7c03cc5912e 100644 --- a/tests/updater-script/base/tests/integration/features/latest.feature +++ b/tests/updater-script/base/tests/integration/features/latest.feature @@ -13,3 +13,32 @@ Feature: Testing the latest endpoint Then The JSON response is non-empty And Version "34.0.0 RC5" is the latest release And URL to download is "https://download.nextcloud.com/server/prereleases/nextcloud-34.0.0rc5.zip" + + Scenario: Get latest stable version with PHP 8.0 + Given I want to know the latest stable release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.16" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.16.zip" + + Scenario: Get latest beta version with PHP 8.0 + Given I want to know the latest beta release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.16" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.16.zip" + + Scenario: Get latest version with invalid PHP version + Given I want to know the latest beta release + And I use PHP "test" + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid PHP version provided" + + Scenario: Get latest version with invalid channel + Given I want to know the latest whatever release + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid channel provided" diff --git a/tests/updater-script/scenarios/first-beta/expected/tests/integration/features/latest.feature b/tests/updater-script/scenarios/first-beta/expected/tests/integration/features/latest.feature index b8fd4f2f394..8f576f09dc1 100644 --- a/tests/updater-script/scenarios/first-beta/expected/tests/integration/features/latest.feature +++ b/tests/updater-script/scenarios/first-beta/expected/tests/integration/features/latest.feature @@ -13,3 +13,32 @@ Feature: Testing the latest endpoint Then The JSON response is non-empty And Version "35.0.0 beta 1" is the latest release And URL to download is "https://download.nextcloud.com/server/prereleases/nextcloud-35.0.0beta1.zip" + + Scenario: Get latest stable version with PHP 8.0 + Given I want to know the latest stable release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.16" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.16.zip" + + Scenario: Get latest beta version with PHP 8.0 + Given I want to know the latest beta release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.16" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.16.zip" + + Scenario: Get latest version with invalid PHP version + Given I want to know the latest beta release + And I use PHP "test" + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid PHP version provided" + + Scenario: Get latest version with invalid channel + Given I want to know the latest whatever release + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid channel provided" diff --git a/tests/updater-script/scenarios/first-stable/expected/tests/integration/features/latest.feature b/tests/updater-script/scenarios/first-stable/expected/tests/integration/features/latest.feature index 42d01adbb07..2c2f41869ff 100644 --- a/tests/updater-script/scenarios/first-stable/expected/tests/integration/features/latest.feature +++ b/tests/updater-script/scenarios/first-stable/expected/tests/integration/features/latest.feature @@ -13,3 +13,32 @@ Feature: Testing the latest endpoint Then The JSON response is non-empty And Version "34.0.0" is the latest release And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-34.0.0.zip" + + Scenario: Get latest stable version with PHP 8.0 + Given I want to know the latest stable release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.16" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.16.zip" + + Scenario: Get latest beta version with PHP 8.0 + Given I want to know the latest beta release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.16" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.16.zip" + + Scenario: Get latest version with invalid PHP version + Given I want to know the latest beta release + And I use PHP "test" + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid PHP version provided" + + Scenario: Get latest version with invalid channel + Given I want to know the latest whatever release + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid channel provided" diff --git a/tests/updater-script/scenarios/patch-release-01/expected/tests/integration/features/latest.feature b/tests/updater-script/scenarios/patch-release-01/expected/tests/integration/features/latest.feature index 059d74d7f42..d493e841611 100644 --- a/tests/updater-script/scenarios/patch-release-01/expected/tests/integration/features/latest.feature +++ b/tests/updater-script/scenarios/patch-release-01/expected/tests/integration/features/latest.feature @@ -13,3 +13,32 @@ Feature: Testing the latest endpoint Then The JSON response is non-empty And Version "34.0.0 RC5" is the latest release And URL to download is "https://download.nextcloud.com/server/prereleases/nextcloud-34.0.0rc5.zip" + + Scenario: Get latest stable version with PHP 8.0 + Given I want to know the latest stable release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.16" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.16.zip" + + Scenario: Get latest beta version with PHP 8.0 + Given I want to know the latest beta release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.16" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.16.zip" + + Scenario: Get latest version with invalid PHP version + Given I want to know the latest beta release + And I use PHP "test" + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid PHP version provided" + + Scenario: Get latest version with invalid channel + Given I want to know the latest whatever release + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid channel provided" diff --git a/tests/updater-script/scenarios/patch-release-02/expected/tests/integration/features/latest.feature b/tests/updater-script/scenarios/patch-release-02/expected/tests/integration/features/latest.feature index 00317b1d8bb..c71d4577b4f 100644 --- a/tests/updater-script/scenarios/patch-release-02/expected/tests/integration/features/latest.feature +++ b/tests/updater-script/scenarios/patch-release-02/expected/tests/integration/features/latest.feature @@ -13,3 +13,32 @@ Feature: Testing the latest endpoint Then The JSON response is non-empty And Version "34.0.0 RC5" is the latest release And URL to download is "https://download.nextcloud.com/server/prereleases/nextcloud-34.0.0rc5.zip" + + Scenario: Get latest stable version with PHP 8.0 + Given I want to know the latest stable release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.16" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.16.zip" + + Scenario: Get latest beta version with PHP 8.0 + Given I want to know the latest beta release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.16" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.16.zip" + + Scenario: Get latest version with invalid PHP version + Given I want to know the latest beta release + And I use PHP "test" + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid PHP version provided" + + Scenario: Get latest version with invalid channel + Given I want to know the latest whatever release + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid channel provided" diff --git a/tests/updater-script/scenarios/patch-release/expected/tests/integration/features/latest.feature b/tests/updater-script/scenarios/patch-release/expected/tests/integration/features/latest.feature index f55e0f0f27d..b13123ddc59 100644 --- a/tests/updater-script/scenarios/patch-release/expected/tests/integration/features/latest.feature +++ b/tests/updater-script/scenarios/patch-release/expected/tests/integration/features/latest.feature @@ -13,3 +13,32 @@ Feature: Testing the latest endpoint Then The JSON response is non-empty And Version "34.0.0 RC5" is the latest release And URL to download is "https://download.nextcloud.com/server/prereleases/nextcloud-34.0.0rc5.zip" + + Scenario: Get latest stable version with PHP 8.0 + Given I want to know the latest stable release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.16" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.16.zip" + + Scenario: Get latest beta version with PHP 8.0 + Given I want to know the latest beta release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.16" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.16.zip" + + Scenario: Get latest version with invalid PHP version + Given I want to know the latest beta release + And I use PHP "test" + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid PHP version provided" + + Scenario: Get latest version with invalid channel + Given I want to know the latest whatever release + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid channel provided" diff --git a/tests/updater-script/scenarios/rc-bump/expected/tests/integration/features/latest.feature b/tests/updater-script/scenarios/rc-bump/expected/tests/integration/features/latest.feature index 0f012d8181e..61be4844ebb 100644 --- a/tests/updater-script/scenarios/rc-bump/expected/tests/integration/features/latest.feature +++ b/tests/updater-script/scenarios/rc-bump/expected/tests/integration/features/latest.feature @@ -13,3 +13,32 @@ Feature: Testing the latest endpoint Then The JSON response is non-empty And Version "34.0.0 RC6" is the latest release And URL to download is "https://download.nextcloud.com/server/prereleases/nextcloud-34.0.0rc6.zip" + + Scenario: Get latest stable version with PHP 8.0 + Given I want to know the latest stable release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.16" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.16.zip" + + Scenario: Get latest beta version with PHP 8.0 + Given I want to know the latest beta release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.16" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.16.zip" + + Scenario: Get latest version with invalid PHP version + Given I want to know the latest beta release + And I use PHP "test" + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid PHP version provided" + + Scenario: Get latest version with invalid channel + Given I want to know the latest whatever release + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid channel provided" From 3a5df576a3a405d3dc1956910e6139f9d0af6783 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Mon, 8 Jun 2026 13:53:28 +0200 Subject: [PATCH 2/5] fix(updater): set git identity before committing in cloned repo CI runners have no default git identity, so the PR-creation step failed with 'empty ident name not allowed' (exit 128) after the feature files were generated. Derive the identity from the token owner (GH_TOKEN == RELEASE_TOKEN) via 'gh api user' so commits are authored by that bot, falling back to github-actions[bot] when the token is not a user token. Signed-off-by: Barthelemy Briand Signed-off-by: skjnldsv --- .github/scripts/update-updater-server.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/scripts/update-updater-server.sh b/.github/scripts/update-updater-server.sh index 783c977ff2a..0bf23155187 100755 --- a/.github/scripts/update-updater-server.sh +++ b/.github/scripts/update-updater-server.sh @@ -586,6 +586,22 @@ if [[ "$DEPLOY" -ne 100 ]]; then COMMIT_MSG="${COMMIT_MSG} (${DEPLOY}% rollout)" fi +# Ensure a git identity exists (CI runners have none by default). +# Derive it from the token owner (GH_TOKEN == RELEASE_TOKEN) so commits +# are authored by the bot the token belongs to. Fall back to +# github-actions[bot] if the token isn't a user token (e.g. github.token). +if [[ -z "$(git config user.email)" ]]; then + BOT_LOGIN=$(gh api user --jq '.login' 2>/dev/null || true) + if [[ -n "$BOT_LOGIN" ]]; then + BOT_ID=$(gh api user --jq '.id' 2>/dev/null || true) + git config user.name "$BOT_LOGIN" + git config user.email "${BOT_ID}+${BOT_LOGIN}@users.noreply.github.com" + else + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + fi +fi + git checkout -b "$BRANCH" git add config/ tests/ git commit --signoff -m "$COMMIT_MSG" From afb212428fa7b0fb027574cb63cdf42b673e349f Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Mon, 8 Jun 2026 13:56:23 +0200 Subject: [PATCH 3/5] fix(updater): authenticate git push via gh The gh-cloned updater_server remote carries no push credentials, so 'git push' failed with 'could not read Username for github.com'. Run 'gh auth setup-git' before pushing so git reuses GH_TOKEN (RELEASE_TOKEN). Signed-off-by: Barthelemy Briand Signed-off-by: skjnldsv --- .github/scripts/update-updater-server.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/scripts/update-updater-server.sh b/.github/scripts/update-updater-server.sh index 0bf23155187..0bb3c9e17b2 100755 --- a/.github/scripts/update-updater-server.sh +++ b/.github/scripts/update-updater-server.sh @@ -605,6 +605,10 @@ fi git checkout -b "$BRANCH" git add config/ tests/ git commit --signoff -m "$COMMIT_MSG" + +# Route git's github.com auth through gh (GH_TOKEN == RELEASE_TOKEN); +# the gh-cloned remote has no push credentials on its own. +gh auth setup-git git push -u origin "$BRANCH" BODY="Automated PR from the release pipeline. From 52f73ef95a4a5d2cd4a805734bf5b5b643762cf3 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Mon, 8 Jun 2026 14:01:38 +0200 Subject: [PATCH 4/5] fix(updater): pass --head to gh pr create With --repo set, gh cannot infer the head branch from the local checkout and aborts with 'you must first push the current branch ... or use the --head flag'. Pass --head "$BRANCH" explicitly. Signed-off-by: Barthelemy Briand Signed-off-by: skjnldsv --- .github/scripts/update-updater-server.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/scripts/update-updater-server.sh b/.github/scripts/update-updater-server.sh index 0bb3c9e17b2..eef2c219a84 100755 --- a/.github/scripts/update-updater-server.sh +++ b/.github/scripts/update-updater-server.sh @@ -623,6 +623,7 @@ Generated by \`update-updater-server.sh ${TAG}\`." gh pr create \ --repo "$UPDATER_REPO" \ --base master \ + --head "$BRANCH" \ --title "$COMMIT_MSG" \ --body "$BODY" From c646cb67cf74b6e0bc775ff9a2a176c30668e7df Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Mon, 8 Jun 2026 14:03:08 +0200 Subject: [PATCH 5/5] fix(updater): force-push the release branch Re-running for the same release produces a fresh commit, so a leftover branch from a prior run is non-fast-forward and the push is rejected. Force-push to keep retries idempotent. Signed-off-by: Barthelemy Briand Signed-off-by: skjnldsv --- .github/scripts/update-updater-server.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/scripts/update-updater-server.sh b/.github/scripts/update-updater-server.sh index eef2c219a84..5e215e4e444 100755 --- a/.github/scripts/update-updater-server.sh +++ b/.github/scripts/update-updater-server.sh @@ -609,7 +609,9 @@ git commit --signoff -m "$COMMIT_MSG" # Route git's github.com auth through gh (GH_TOKEN == RELEASE_TOKEN); # the gh-cloned remote has no push credentials on its own. gh auth setup-git -git push -u origin "$BRANCH" +# Force-push so re-runs for the same release overwrite a stale branch +# (each run produces a fresh commit, so a leftover branch is non-ff). +git push --force -u origin "$BRANCH" BODY="Automated PR from the release pipeline.