From 9b018320a0e3cbfff097936bfb92c488c0ab85de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Sun, 31 May 2026 16:42:00 +0200 Subject: [PATCH 01/15] feat: add tagger workflow to tag all repos on release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the manual tag.php from github_helper/tagger. Runs automatically when a release is published on this repo, alongside the existing changelog workflow. - Reads repo list from per-version JSON configs (stable32-34) - Uses GitHub API to create tags at stable branch HEAD - Handles branch overrides for master (text, app_api use main) - Idempotent: skips repos where tag already exists - Reports results in step summary table - Supports workflow_dispatch for re-tagging - Checks out github_helper for future changelog integration Repo lists match github_helper/tagger/tag.php: - stable32/33: 28 repos - stable34: 30 repos (+files_lock, +office) Requires TAG_TOKEN secret with contents:write on all target repos. Signed-off-by: John Molakvoæ (skjnldsv) --- .github/workflows/tag-repos.yml | 147 ++++++++++++++++++++++++++++++++ stable32.json | 30 +++++++ stable33.json | 30 +++++++ stable34.json | 32 +++++++ 4 files changed, 239 insertions(+) create mode 100644 .github/workflows/tag-repos.yml create mode 100644 stable32.json create mode 100644 stable33.json create mode 100644 stable34.json diff --git a/.github/workflows/tag-repos.yml b/.github/workflows/tag-repos.yml new file mode 100644 index 00000000000..a07b033627d --- /dev/null +++ b/.github/workflows/tag-repos.yml @@ -0,0 +1,147 @@ +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT + +name: Tag all repositories on release + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Tag to create (e.g., v34.0.1)' + required: true + +permissions: + contents: read + +jobs: + tag: + runs-on: ubuntu-latest + + env: + RELEASE_TAG: ${{ inputs.tag || github.ref_name }} + + steps: + - name: Check actor permission + uses: skjnldsv/check-actor-permission@69e92a3c4711150929bca9fcf34448c5bf5526e7 # v3.0 + with: + require: write + + - name: Checkout config + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Determine branch and config + id: config + run: | + TAG="${{ env.RELEASE_TAG }}" + echo "Tag: $TAG" + + # Validate tag format + if [[ ! "$TAG" =~ ^v[1-9][0-9]*\.[0-9]+\.[0-9]+(alpha|beta|rc)?[0-9]*$ ]]; then + echo "::error::Invalid tag format: $TAG (expected vXX.Y.Z[alphaN|betaN|rcN])" + exit 1 + fi + + # Extract major version and determine branch + MAJOR=$(echo "$TAG" | sed 's/^v//' | cut -d. -f1) + if [[ "$TAG" =~ \.0\.0(alpha|beta) ]]; then + BRANCH="master" + else + BRANCH="stable${MAJOR}" + fi + + # Find config file + if [ "$BRANCH" = "master" ]; then + CONFIG=$(ls stable*.json 2>/dev/null | sort -V | tail -1) + else + CONFIG="stable${MAJOR}.json" + fi + + if [ ! -f "$CONFIG" ]; then + echo "::error::Config file $CONFIG not found. Create it for NC${MAJOR}." + exit 1 + fi + + echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" + echo "config=$CONFIG" >> "$GITHUB_OUTPUT" + echo "::notice::Tagging repos from $CONFIG at $BRANCH HEAD → $TAG" + + - name: Tag all repositories + env: + GH_TOKEN: ${{ secrets.TAG_TOKEN || secrets.GITHUB_TOKEN }} + run: | + TAG="${{ env.RELEASE_TAG }}" + BRANCH="${{ steps.config.outputs.branch }}" + CONFIG="${{ steps.config.outputs.config }}" + + TOTAL=0 + SUCCESS=0 + SKIPPED=0 + FAILED=0 + + { + echo "## Tag results: $TAG" + echo "" + echo "| Repository | Branch | Status | Commit |" + echo "|---|---|---|---|" + } >> "$GITHUB_STEP_SUMMARY" + + jq -r '.[]' "$CONFIG" | while read -r repo; do + TOTAL=$((TOTAL + 1)) + + # Determine the branch for this repo + REPO_BRANCH="$BRANCH" + if [ "$BRANCH" = "master" ]; then + case "$repo" in + */text|*/twofactor_nextcloud_notification|*/app_api) + REPO_BRANCH="main" + ;; + esac + fi + + echo -n "$repo@$REPO_BRANCH → $TAG ... " + + # Get branch HEAD SHA + SHA=$(gh api "repos/$repo/git/ref/heads/$REPO_BRANCH" --jq '.object.sha' 2>/dev/null) + if [ -z "$SHA" ]; then + echo "SKIP (branch not found)" + echo "| \`$repo\` | $REPO_BRANCH | branch not found | - |" >> "$GITHUB_STEP_SUMMARY" + FAILED=$((FAILED + 1)) + continue + fi + + # Check if tag already exists + EXISTING=$(gh api "repos/$repo/git/ref/tags/$TAG" --jq '.object.sha' 2>/dev/null) + if [ -n "$EXISTING" ]; then + echo "EXISTS (${EXISTING:0:8})" + echo "| \`$repo\` | $REPO_BRANCH | already tagged | \`${EXISTING:0:8}\` |" >> "$GITHUB_STEP_SUMMARY" + SKIPPED=$((SKIPPED + 1)) + continue + fi + + # Create lightweight tag via API + if gh api "repos/$repo/git/refs" \ + -f "ref=refs/tags/$TAG" \ + -f "sha=$SHA" > /dev/null 2>&1; then + echo "OK (${SHA:0:8})" + echo "| \`$repo\` | $REPO_BRANCH | tagged | \`${SHA:0:8}\` |" >> "$GITHUB_STEP_SUMMARY" + SUCCESS=$((SUCCESS + 1)) + else + echo "FAILED" + echo "| \`$repo\` | $REPO_BRANCH | **failed** | - |" >> "$GITHUB_STEP_SUMMARY" + FAILED=$((FAILED + 1)) + fi + done + + { + echo "" + echo "**Tagged: $SUCCESS | Skipped: $SKIPPED | Failed: $FAILED**" + } >> "$GITHUB_STEP_SUMMARY" + + if [ "$FAILED" -gt 0 ]; then + echo "::error::$FAILED repos failed to tag" + exit 1 + fi diff --git a/stable32.json b/stable32.json new file mode 100644 index 00000000000..30b36fd9696 --- /dev/null +++ b/stable32.json @@ -0,0 +1,30 @@ +[ + "nextcloud/server", + "nextcloud/3rdparty", + "nextcloud/activity", + "nextcloud/app_api", + "nextcloud/bruteforcesettings", + "nextcloud/circles", + "nextcloud/documentation", + "nextcloud/example-files", + "nextcloud/files_downloadlimit", + "nextcloud/files_pdfviewer", + "nextcloud/firstrunwizard", + "nextcloud/logreader", + "nextcloud/nextcloud_announcements", + "nextcloud/notifications", + "nextcloud/password_policy", + "nextcloud/photos", + "nextcloud/privacy", + "nextcloud/recommendations", + "nextcloud/related_resources", + "nextcloud/serverinfo", + "nextcloud/survey_client", + "nextcloud/suspicious_login", + "nextcloud/text", + "nextcloud/twofactor_nextcloud_notification", + "nextcloud/twofactor_totp", + "nextcloud/updater", + "nextcloud/viewer", + "nextcloud-gmbh/support" +] diff --git a/stable33.json b/stable33.json new file mode 100644 index 00000000000..30b36fd9696 --- /dev/null +++ b/stable33.json @@ -0,0 +1,30 @@ +[ + "nextcloud/server", + "nextcloud/3rdparty", + "nextcloud/activity", + "nextcloud/app_api", + "nextcloud/bruteforcesettings", + "nextcloud/circles", + "nextcloud/documentation", + "nextcloud/example-files", + "nextcloud/files_downloadlimit", + "nextcloud/files_pdfviewer", + "nextcloud/firstrunwizard", + "nextcloud/logreader", + "nextcloud/nextcloud_announcements", + "nextcloud/notifications", + "nextcloud/password_policy", + "nextcloud/photos", + "nextcloud/privacy", + "nextcloud/recommendations", + "nextcloud/related_resources", + "nextcloud/serverinfo", + "nextcloud/survey_client", + "nextcloud/suspicious_login", + "nextcloud/text", + "nextcloud/twofactor_nextcloud_notification", + "nextcloud/twofactor_totp", + "nextcloud/updater", + "nextcloud/viewer", + "nextcloud-gmbh/support" +] diff --git a/stable34.json b/stable34.json new file mode 100644 index 00000000000..d12164dcea0 --- /dev/null +++ b/stable34.json @@ -0,0 +1,32 @@ +[ + "nextcloud/server", + "nextcloud/3rdparty", + "nextcloud/activity", + "nextcloud/app_api", + "nextcloud/bruteforcesettings", + "nextcloud/circles", + "nextcloud/documentation", + "nextcloud/example-files", + "nextcloud/files_downloadlimit", + "nextcloud/files_lock", + "nextcloud/files_pdfviewer", + "nextcloud/firstrunwizard", + "nextcloud/logreader", + "nextcloud/nextcloud_announcements", + "nextcloud/notifications", + "nextcloud/office", + "nextcloud/password_policy", + "nextcloud/photos", + "nextcloud/privacy", + "nextcloud/recommendations", + "nextcloud/related_resources", + "nextcloud/serverinfo", + "nextcloud/survey_client", + "nextcloud/suspicious_login", + "nextcloud/text", + "nextcloud/twofactor_nextcloud_notification", + "nextcloud/twofactor_totp", + "nextcloud/updater", + "nextcloud/viewer", + "nextcloud-gmbh/support" +] From dccc8d4ff99054b675b0e907aca29f9f98d8f07e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Tue, 2 Jun 2026 06:14:38 +0200 Subject: [PATCH 02/15] feat: add per-version release configs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JSON configs listing all apps per major version. Used by both the tagger (reads .repo) and the builder (reads full config). - stable32/33: 23 apps - stable34/master: 25 apps (+files_lock, +office) - tag-only.json: repos to tag but not build (server, 3rdparty, etc.) Signed-off-by: John Molakvoæ (skjnldsv) --- master.json | 103 +++++++++++++++++++++++++++++++++++++++ stable32.json | 121 +++++++++++++++++++++++++++++++++++----------- stable33.json | 121 +++++++++++++++++++++++++++++++++++----------- stable34.json | 131 ++++++++++++++++++++++++++++++++++++++------------ tag-only.json | 7 +++ 5 files changed, 397 insertions(+), 86 deletions(-) create mode 100644 master.json create mode 100644 tag-only.json diff --git a/master.json b/master.json new file mode 100644 index 00000000000..af5e4054dbf --- /dev/null +++ b/master.json @@ -0,0 +1,103 @@ +[ + { + "id": "activity", + "repo": "nextcloud/activity" + }, + { + "id": "circles", + "repo": "nextcloud/circles" + }, + { + "id": "files_downloadlimit", + "repo": "nextcloud/files_downloadlimit" + }, + { + "id": "files_pdfviewer", + "repo": "nextcloud/files_pdfviewer" + }, + { + "id": "firstrunwizard", + "repo": "nextcloud/firstrunwizard" + }, + { + "id": "logreader", + "repo": "nextcloud/logreader" + }, + { + "id": "nextcloud_announcements", + "repo": "nextcloud/nextcloud_announcements" + }, + { + "id": "notifications", + "repo": "nextcloud/notifications" + }, + { + "id": "files_lock", + "repo": "nextcloud/files_lock" + }, + { + "id": "office", + "repo": "nextcloud/office" + }, + { + "id": "password_policy", + "repo": "nextcloud/password_policy" + }, + { + "id": "photos", + "repo": "nextcloud/photos" + }, + { + "id": "privacy", + "repo": "nextcloud/privacy" + }, + { + "id": "recommendations", + "repo": "nextcloud/recommendations" + }, + { + "id": "serverinfo", + "repo": "nextcloud/serverinfo" + }, + { + "id": "survey_client", + "repo": "nextcloud/survey_client" + }, + { + "id": "text", + "repo": "nextcloud/text" + }, + { + "id": "viewer", + "repo": "nextcloud/viewer" + }, + { + "id": "bruteforcesettings", + "repo": "nextcloud/bruteforcesettings" + }, + { + "id": "related_resources", + "repo": "nextcloud/related_resources" + }, + { + "id": "suspicious_login", + "repo": "nextcloud/suspicious_login", + "composer_args": "--no-dev -a --quiet --no-scripts" + }, + { + "id": "twofactor_totp", + "repo": "nextcloud/twofactor_totp" + }, + { + "id": "twofactor_nextcloud_notification", + "repo": "nextcloud/twofactor_nextcloud_notification" + }, + { + "id": "app_api", + "repo": "nextcloud/app_api" + }, + { + "id": "support", + "repo": "nextcloud-gmbh/support" + } +] diff --git a/stable32.json b/stable32.json index 30b36fd9696..69d9b5aeeee 100644 --- a/stable32.json +++ b/stable32.json @@ -1,30 +1,95 @@ [ - "nextcloud/server", - "nextcloud/3rdparty", - "nextcloud/activity", - "nextcloud/app_api", - "nextcloud/bruteforcesettings", - "nextcloud/circles", - "nextcloud/documentation", - "nextcloud/example-files", - "nextcloud/files_downloadlimit", - "nextcloud/files_pdfviewer", - "nextcloud/firstrunwizard", - "nextcloud/logreader", - "nextcloud/nextcloud_announcements", - "nextcloud/notifications", - "nextcloud/password_policy", - "nextcloud/photos", - "nextcloud/privacy", - "nextcloud/recommendations", - "nextcloud/related_resources", - "nextcloud/serverinfo", - "nextcloud/survey_client", - "nextcloud/suspicious_login", - "nextcloud/text", - "nextcloud/twofactor_nextcloud_notification", - "nextcloud/twofactor_totp", - "nextcloud/updater", - "nextcloud/viewer", - "nextcloud-gmbh/support" + { + "id": "activity", + "repo": "nextcloud/activity" + }, + { + "id": "circles", + "repo": "nextcloud/circles" + }, + { + "id": "files_downloadlimit", + "repo": "nextcloud/files_downloadlimit" + }, + { + "id": "files_pdfviewer", + "repo": "nextcloud/files_pdfviewer" + }, + { + "id": "firstrunwizard", + "repo": "nextcloud/firstrunwizard" + }, + { + "id": "logreader", + "repo": "nextcloud/logreader" + }, + { + "id": "nextcloud_announcements", + "repo": "nextcloud/nextcloud_announcements" + }, + { + "id": "notifications", + "repo": "nextcloud/notifications" + }, + { + "id": "password_policy", + "repo": "nextcloud/password_policy" + }, + { + "id": "photos", + "repo": "nextcloud/photos" + }, + { + "id": "privacy", + "repo": "nextcloud/privacy" + }, + { + "id": "recommendations", + "repo": "nextcloud/recommendations" + }, + { + "id": "serverinfo", + "repo": "nextcloud/serverinfo" + }, + { + "id": "survey_client", + "repo": "nextcloud/survey_client" + }, + { + "id": "text", + "repo": "nextcloud/text" + }, + { + "id": "viewer", + "repo": "nextcloud/viewer" + }, + { + "id": "bruteforcesettings", + "repo": "nextcloud/bruteforcesettings" + }, + { + "id": "related_resources", + "repo": "nextcloud/related_resources" + }, + { + "id": "suspicious_login", + "repo": "nextcloud/suspicious_login", + "composer_args": "--no-dev -a --quiet --no-scripts" + }, + { + "id": "twofactor_totp", + "repo": "nextcloud/twofactor_totp" + }, + { + "id": "twofactor_nextcloud_notification", + "repo": "nextcloud/twofactor_nextcloud_notification" + }, + { + "id": "app_api", + "repo": "nextcloud/app_api" + }, + { + "id": "support", + "repo": "nextcloud-gmbh/support" + } ] diff --git a/stable33.json b/stable33.json index 30b36fd9696..69d9b5aeeee 100644 --- a/stable33.json +++ b/stable33.json @@ -1,30 +1,95 @@ [ - "nextcloud/server", - "nextcloud/3rdparty", - "nextcloud/activity", - "nextcloud/app_api", - "nextcloud/bruteforcesettings", - "nextcloud/circles", - "nextcloud/documentation", - "nextcloud/example-files", - "nextcloud/files_downloadlimit", - "nextcloud/files_pdfviewer", - "nextcloud/firstrunwizard", - "nextcloud/logreader", - "nextcloud/nextcloud_announcements", - "nextcloud/notifications", - "nextcloud/password_policy", - "nextcloud/photos", - "nextcloud/privacy", - "nextcloud/recommendations", - "nextcloud/related_resources", - "nextcloud/serverinfo", - "nextcloud/survey_client", - "nextcloud/suspicious_login", - "nextcloud/text", - "nextcloud/twofactor_nextcloud_notification", - "nextcloud/twofactor_totp", - "nextcloud/updater", - "nextcloud/viewer", - "nextcloud-gmbh/support" + { + "id": "activity", + "repo": "nextcloud/activity" + }, + { + "id": "circles", + "repo": "nextcloud/circles" + }, + { + "id": "files_downloadlimit", + "repo": "nextcloud/files_downloadlimit" + }, + { + "id": "files_pdfviewer", + "repo": "nextcloud/files_pdfviewer" + }, + { + "id": "firstrunwizard", + "repo": "nextcloud/firstrunwizard" + }, + { + "id": "logreader", + "repo": "nextcloud/logreader" + }, + { + "id": "nextcloud_announcements", + "repo": "nextcloud/nextcloud_announcements" + }, + { + "id": "notifications", + "repo": "nextcloud/notifications" + }, + { + "id": "password_policy", + "repo": "nextcloud/password_policy" + }, + { + "id": "photos", + "repo": "nextcloud/photos" + }, + { + "id": "privacy", + "repo": "nextcloud/privacy" + }, + { + "id": "recommendations", + "repo": "nextcloud/recommendations" + }, + { + "id": "serverinfo", + "repo": "nextcloud/serverinfo" + }, + { + "id": "survey_client", + "repo": "nextcloud/survey_client" + }, + { + "id": "text", + "repo": "nextcloud/text" + }, + { + "id": "viewer", + "repo": "nextcloud/viewer" + }, + { + "id": "bruteforcesettings", + "repo": "nextcloud/bruteforcesettings" + }, + { + "id": "related_resources", + "repo": "nextcloud/related_resources" + }, + { + "id": "suspicious_login", + "repo": "nextcloud/suspicious_login", + "composer_args": "--no-dev -a --quiet --no-scripts" + }, + { + "id": "twofactor_totp", + "repo": "nextcloud/twofactor_totp" + }, + { + "id": "twofactor_nextcloud_notification", + "repo": "nextcloud/twofactor_nextcloud_notification" + }, + { + "id": "app_api", + "repo": "nextcloud/app_api" + }, + { + "id": "support", + "repo": "nextcloud-gmbh/support" + } ] diff --git a/stable34.json b/stable34.json index d12164dcea0..af5e4054dbf 100644 --- a/stable34.json +++ b/stable34.json @@ -1,32 +1,103 @@ [ - "nextcloud/server", - "nextcloud/3rdparty", - "nextcloud/activity", - "nextcloud/app_api", - "nextcloud/bruteforcesettings", - "nextcloud/circles", - "nextcloud/documentation", - "nextcloud/example-files", - "nextcloud/files_downloadlimit", - "nextcloud/files_lock", - "nextcloud/files_pdfviewer", - "nextcloud/firstrunwizard", - "nextcloud/logreader", - "nextcloud/nextcloud_announcements", - "nextcloud/notifications", - "nextcloud/office", - "nextcloud/password_policy", - "nextcloud/photos", - "nextcloud/privacy", - "nextcloud/recommendations", - "nextcloud/related_resources", - "nextcloud/serverinfo", - "nextcloud/survey_client", - "nextcloud/suspicious_login", - "nextcloud/text", - "nextcloud/twofactor_nextcloud_notification", - "nextcloud/twofactor_totp", - "nextcloud/updater", - "nextcloud/viewer", - "nextcloud-gmbh/support" + { + "id": "activity", + "repo": "nextcloud/activity" + }, + { + "id": "circles", + "repo": "nextcloud/circles" + }, + { + "id": "files_downloadlimit", + "repo": "nextcloud/files_downloadlimit" + }, + { + "id": "files_pdfviewer", + "repo": "nextcloud/files_pdfviewer" + }, + { + "id": "firstrunwizard", + "repo": "nextcloud/firstrunwizard" + }, + { + "id": "logreader", + "repo": "nextcloud/logreader" + }, + { + "id": "nextcloud_announcements", + "repo": "nextcloud/nextcloud_announcements" + }, + { + "id": "notifications", + "repo": "nextcloud/notifications" + }, + { + "id": "files_lock", + "repo": "nextcloud/files_lock" + }, + { + "id": "office", + "repo": "nextcloud/office" + }, + { + "id": "password_policy", + "repo": "nextcloud/password_policy" + }, + { + "id": "photos", + "repo": "nextcloud/photos" + }, + { + "id": "privacy", + "repo": "nextcloud/privacy" + }, + { + "id": "recommendations", + "repo": "nextcloud/recommendations" + }, + { + "id": "serverinfo", + "repo": "nextcloud/serverinfo" + }, + { + "id": "survey_client", + "repo": "nextcloud/survey_client" + }, + { + "id": "text", + "repo": "nextcloud/text" + }, + { + "id": "viewer", + "repo": "nextcloud/viewer" + }, + { + "id": "bruteforcesettings", + "repo": "nextcloud/bruteforcesettings" + }, + { + "id": "related_resources", + "repo": "nextcloud/related_resources" + }, + { + "id": "suspicious_login", + "repo": "nextcloud/suspicious_login", + "composer_args": "--no-dev -a --quiet --no-scripts" + }, + { + "id": "twofactor_totp", + "repo": "nextcloud/twofactor_totp" + }, + { + "id": "twofactor_nextcloud_notification", + "repo": "nextcloud/twofactor_nextcloud_notification" + }, + { + "id": "app_api", + "repo": "nextcloud/app_api" + }, + { + "id": "support", + "repo": "nextcloud-gmbh/support" + } ] diff --git a/tag-only.json b/tag-only.json new file mode 100644 index 00000000000..35726c2e378 --- /dev/null +++ b/tag-only.json @@ -0,0 +1,7 @@ +[ + "nextcloud/server", + "nextcloud/3rdparty", + "nextcloud/updater", + "nextcloud/example-files", + "nextcloud/documentation" +] From f094be3ac5988b26ddb02b37409f1dc0b32fe2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Tue, 2 Jun 2026 06:14:50 +0200 Subject: [PATCH 03/15] feat: add tagger workflow (release-tag.yml) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tags all bundled repos on release via GitHub API. Uses gh release create + delete to create tags without SSH keys. Falls back to repo default branch if stable branch not found. Supports manual dispatch with force option for re-tagging. Signed-off-by: John Molakvoæ (skjnldsv) --- .../{tag-repos.yml => release-tag.yml} | 84 +++++++++++++------ 1 file changed, 58 insertions(+), 26 deletions(-) rename .github/workflows/{tag-repos.yml => release-tag.yml} (57%) diff --git a/.github/workflows/tag-repos.yml b/.github/workflows/release-tag.yml similarity index 57% rename from .github/workflows/tag-repos.yml rename to .github/workflows/release-tag.yml index a07b033627d..08a7c588e56 100644 --- a/.github/workflows/tag-repos.yml +++ b/.github/workflows/release-tag.yml @@ -1,22 +1,36 @@ # SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors # SPDX-License-Identifier: MIT -name: Tag all repositories on release +name: Tag all repositories +run-name: "Tag ${{ inputs.tag }}" on: - release: - types: [published] + workflow_call: + inputs: + tag: + required: true + type: string workflow_dispatch: inputs: tag: description: 'Tag to create (e.g., v34.0.1)' required: true + force: + description: 'Delete and recreate existing tags' + required: false + type: boolean + default: false permissions: contents: read +concurrency: + group: release-tag-${{ inputs.tag || github.ref_name }} + cancel-in-progress: false + jobs: tag: + timeout-minutes: 15 runs-on: ubuntu-latest env: @@ -67,16 +81,24 @@ jobs: echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" echo "config=$CONFIG" >> "$GITHUB_OUTPUT" - echo "::notice::Tagging repos from $CONFIG at $BRANCH HEAD → $TAG" + echo "::notice::Tagging repos from $CONFIG + tag-only.json at $BRANCH HEAD → $TAG" - name: Tag all repositories env: - GH_TOKEN: ${{ secrets.TAG_TOKEN || secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} run: | TAG="${{ env.RELEASE_TAG }}" BRANCH="${{ steps.config.outputs.branch }}" CONFIG="${{ steps.config.outputs.config }}" + # Merge app repos from build config + non-build repos from tag-only.json + # (server, 3rdparty, updater, example-files, documentation) + REPOS=$(jq -r '.[].repo' "$CONFIG") + if [ -f "tag-only.json" ]; then + REPOS=$(echo "$REPOS"; jq -r '.[]' "tag-only.json") + fi + REPOS=$(echo "$REPOS" | sort -u) + TOTAL=0 SUCCESS=0 SKIPPED=0 @@ -89,26 +111,23 @@ jobs: echo "|---|---|---|---|" } >> "$GITHUB_STEP_SUMMARY" - jq -r '.[]' "$CONFIG" | while read -r repo; do + echo "$REPOS" | while read -r repo; do + [ -z "$repo" ] && continue TOTAL=$((TOTAL + 1)) - # Determine the branch for this repo + # Try stable branch first. If it doesn't exist (e.g. some repos + # use "main" instead of "master"), fall back to the repo's default branch. REPO_BRANCH="$BRANCH" - if [ "$BRANCH" = "master" ]; then - case "$repo" in - */text|*/twofactor_nextcloud_notification|*/app_api) - REPO_BRANCH="main" - ;; - esac + SHA=$(gh api "repos/$repo/git/ref/heads/$REPO_BRANCH" --jq '.object.sha' 2>/dev/null) + if [ -z "$SHA" ]; then + REPO_BRANCH=$(gh api "repos/$repo" --jq '.default_branch' 2>/dev/null) + SHA=$(gh api "repos/$repo/git/ref/heads/$REPO_BRANCH" --jq '.object.sha' 2>/dev/null) fi - echo -n "$repo@$REPO_BRANCH → $TAG ... " - # Get branch HEAD SHA - SHA=$(gh api "repos/$repo/git/ref/heads/$REPO_BRANCH" --jq '.object.sha' 2>/dev/null) if [ -z "$SHA" ]; then - echo "SKIP (branch not found)" - echo "| \`$repo\` | $REPO_BRANCH | branch not found | - |" >> "$GITHUB_STEP_SUMMARY" + echo "SKIP (no branch found)" + echo "| \`$repo\` | - | no branch found | - |" >> "$GITHUB_STEP_SUMMARY" FAILED=$((FAILED + 1)) continue fi @@ -116,16 +135,29 @@ jobs: # Check if tag already exists EXISTING=$(gh api "repos/$repo/git/ref/tags/$TAG" --jq '.object.sha' 2>/dev/null) if [ -n "$EXISTING" ]; then - echo "EXISTS (${EXISTING:0:8})" - echo "| \`$repo\` | $REPO_BRANCH | already tagged | \`${EXISTING:0:8}\` |" >> "$GITHUB_STEP_SUMMARY" - SKIPPED=$((SKIPPED + 1)) - continue + if [ "${{ inputs.force }}" = "true" ]; then + echo -n "FORCE replacing ${EXISTING:0:8} ... " + gh release delete "$TAG" --repo "$repo" --yes 2>/dev/null || true + gh api -X DELETE "repos/$repo/git/refs/tags/$TAG" 2>/dev/null || true + else + echo "EXISTS (${EXISTING:0:8})" + echo "| \`$repo\` | $REPO_BRANCH | already tagged | \`${EXISTING:0:8}\` |" >> "$GITHUB_STEP_SUMMARY" + SKIPPED=$((SKIPPED + 1)) + continue + fi fi - # Create lightweight tag via API - if gh api "repos/$repo/git/refs" \ - -f "ref=refs/tags/$TAG" \ - -f "sha=$SHA" > /dev/null 2>&1; then + # Create tag via temporary release. gh release create makes a tag + # on the target branch. We then delete the release but the tag stays. + # This avoids needing git clone + SSH keys for each repo. + if gh release create "$TAG" \ + --repo "$repo" \ + --target "$REPO_BRANCH" \ + --title "$TAG" \ + --notes "" \ + > /dev/null 2>&1; then + # Delete the release, keep the tag + gh release delete "$TAG" --repo "$repo" --yes 2>/dev/null || true echo "OK (${SHA:0:8})" echo "| \`$repo\` | $REPO_BRANCH | tagged | \`${SHA:0:8}\` |" >> "$GITHUB_STEP_SUMMARY" SUCCESS=$((SUCCESS + 1)) From e4b1b881f8df278b6f6588a4b5f61c5d39105259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Tue, 2 Jun 2026 06:15:09 +0200 Subject: [PATCH 04/15] feat: add build and compare workflow (release-build.yml) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds Nextcloud release archives and compares against the release script's output on the same GitHub release. Pipeline: validate version.php, detect PHP version, fetch all components in parallel, assemble, clean dev files, sign, metadata, archive (tar.bz2 + zip), checksum, compare file lists + content hashes. Smart composer detection: only runs if real non-dev deps exist. Shared cleanup script for .nextcloudignore + dev file patterns. Signed-off-by: John Molakvoæ (skjnldsv) --- .github/scripts/clean-dev-files.sh | 63 +++ .github/workflows/release-build.yml | 744 +++++++++++++++++++++++++++- 2 files changed, 803 insertions(+), 4 deletions(-) create mode 100755 .github/scripts/clean-dev-files.sh diff --git a/.github/scripts/clean-dev-files.sh b/.github/scripts/clean-dev-files.sh new file mode 100755 index 00000000000..b2a106a45be --- /dev/null +++ b/.github/scripts/clean-dev-files.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT +# +# Clean development files from an app or component directory. +# Usage: clean-dev-files.sh + +set -e + +DIR="${1:-.}" +cd "$DIR" + +# Process .nextcloudignore if present +if [ -f ".nextcloudignore" ]; then + APP_DIR=$(pwd) + while IFS= read -r line || [[ -n "$line" ]]; do + pattern=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/\r$//') + [[ -z "$pattern" || "$pattern" == \#* ]] && continue + if [[ "$pattern" == /* ]]; then + pattern="${pattern#/}" + # shellcheck disable=SC2086 + for match in $APP_DIR/$pattern; do + [ -e "$match" ] && rm -rf "$match" + done + else + find "$APP_DIR" -name "$pattern" -exec rm -rf {} + 2>/dev/null || true + fi + done < .nextcloudignore +fi + +# Remove dev file patterns (based on getDataToBeRemovedFromAppFolders) +DEV_PATTERNS=( + .babelrc .babelrc.js .codecov.yml .devcontainer .drone.yml .editorconfig + .eslintrc.cjs .eslintrc.js .eslintrc.json .eslintignore + .git/ .gitattributes .github .gitignore .git-blame-ignore-revs + .jshintrc .l10nignore .lgtm .nextcloudignore .npmignore .noopenapi + .php_cs.dist .php-cs-fixer.dist.php .scrutinizer.yml .stylelintignore + .stylelintrc.js .travis.yml .tx/ + babel.config.js build-js/ build.xml + check-handlebars-templates.sh codecov.yml compile-handlebars-templates.sh + CONTRIBUTING.md + cypress.config.js cypress.config.ts cypress.json cypress/ + issue_template.md jest-raw-loader.js jest.config.js jsconfig.json + krankerl.toml l10n/.gitkeep Makefile postcss.config.js psalm.xml + .prettierignore + README.md rector.php renovate.json screenshots/ src/ + stylelint.config.js stylelint.config.cjs tests/ tsconfig.json + vite.config.js vite.config.ts vitest.config.js vitest.config.ts + webpack.common.js webpack.config.js webpack.dev.js webpack.js webpack.prod.js +) + +for pattern in "${DEV_PATTERNS[@]}"; do + rm -rf "$pattern" + if [[ "$pattern" != */ ]]; then + rm -rf "${pattern}.license" + fi +done + +# Catch-all: any *.config.{js,ts,mjs,cjs} is a dev config +find . -maxdepth 1 \( -name "*.config.js" -o -name "*.config.ts" -o -name "*.config.mjs" -o -name "*.config.cjs" \) -delete 2>/dev/null || true + +# Remove .map.license files (REUSE companions of sourcemaps, not needed in release) +find . -name "*.map.license" -delete 2>/dev/null || true diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 0ea6c26df72..90d71bec8f4 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -1,16 +1,752 @@ -# Stub to allow workflow_dispatch from feature branches -# Will be replaced by the full workflow from PR #74 +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT + name: Build and compare release +run-name: "Build ${{ inputs.tag }}" on: + workflow_call: + inputs: + tag: + required: true + type: string workflow_dispatch: inputs: tag: description: 'Existing release tag to build and compare (e.g., v34.0.1)' required: true +permissions: + contents: read + +concurrency: + group: release-build-${{ inputs.tag || github.ref_name }} + cancel-in-progress: false + +env: + COMPOSER_ALLOW_SUPERUSER: '1' + jobs: - stub: + init: runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.generate.outputs.matrix }} + branch: ${{ steps.parse.outputs.branch }} + channel: ${{ steps.parse.outputs.channel }} + major: ${{ steps.parse.outputs.major }} + version: ${{ steps.parse.outputs.version }} + tag: ${{ steps.parse.outputs.tag }} + php_version: ${{ steps.php.outputs.version }} + ref: ${{ steps.ref.outputs.ref }} + steps: - - run: echo "This is a stub. Run from the feature branch instead." + - name: Check actor permission + uses: skjnldsv/check-actor-permission@69e92a3c4711150929bca9fcf34448c5bf5526e7 # v3.0 + with: + require: write + + - name: Checkout config + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Parse version + id: parse + run: | + TAG="${{ inputs.tag || github.ref_name }}" + VERSION=$(echo "$TAG" | sed 's/^v//') + MAJOR=$(echo "$VERSION" | cut -d. -f1) + + # Alpha/beta builds come from master, everything else from stable branches + # RCs use the stable branch with the stable channel + if [[ "$VERSION" =~ \.0\.0(alpha|beta) ]]; then + BRANCH="master" + CHANNEL="beta" + else + BRANCH="stable${MAJOR}" + CHANNEL="stable" + fi + + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "major=$MAJOR" >> "$GITHUB_OUTPUT" + echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" + echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT" + + echo "::notice::Building NC${MAJOR} v${VERSION} from ${BRANCH} (channel: ${CHANNEL})" + + - name: Determine ref and verify version.php + id: ref + run: | + TAG="${{ steps.parse.outputs.tag }}" + VERSION="${{ steps.parse.outputs.version }}" + BRANCH="${{ steps.parse.outputs.branch }}" + + # If the tag exists on the server repo, use it (exact commit). + # Otherwise use the branch HEAD (new release, tag not created yet). + TAG_EXISTS=$(curl -sf "https://raw.githubusercontent.com/nextcloud/server/refs/tags/${TAG}/version.php" \ + | grep 'OC_VersionString' | cut -d"'" -f2) + + if [ -n "$TAG_EXISTS" ]; then + REF="$TAG" + VERSION_STRING="$TAG_EXISTS" + echo "::notice::Using tag ${TAG} (exact release commit)" + else + REF="$BRANCH" + VERSION_STRING=$(curl -sf "https://raw.githubusercontent.com/nextcloud/server/refs/heads/${BRANCH}/version.php" \ + | grep 'OC_VersionString' | cut -d"'" -f2) + echo "::notice::Tag not found, using branch ${BRANCH} HEAD" + fi + + if [ -z "$VERSION_STRING" ]; then + echo "::error::Could not read version.php from nextcloud/server@${REF}" + exit 1 + fi + + # Normalize both for comparison. + # version.php may say "34.0.0 RC3" or "34.0.0" — strip suffix + spaces, lowercase. + # Input version may be "34.0.0rc3" — strip suffix. + CLEAN_VERSION=$(echo "$VERSION" | sed 's/[[:space:]]*\(rc\|beta\|alpha\)[0-9]*$//i') + CLEAN_VSTRING=$(echo "$VERSION_STRING" | sed 's/[[:space:]]*\(RC\|rc\|beta\|alpha\)[0-9]*$//i') + + if [[ "$CLEAN_VSTRING" != "$CLEAN_VERSION" ]]; then + echo "::error::version.php says ${VERSION_STRING} but releasing ${VERSION}. Is the bump PR merged?" + exit 1 + fi + + # For master builds, empty ref = use repo's default branch + if [ "$REF" = "master" ]; then + REF="" + fi + + echo "ref=$REF" >> "$GITHUB_OUTPUT" + echo "::notice::version.php confirms ${VERSION_STRING}" + + - name: Detect PHP version from server + id: php + run: | + BRANCH="${{ steps.parse.outputs.branch }}" + # Parse the upper bound from versioncheck.php (e.g. "PHP_VERSION_ID >= 80600") + # 80600 means PHP 8.6+ is rejected, so max supported is 8.5 + MAX_PHP_ID=$(curl -sf "https://raw.githubusercontent.com/nextcloud/server/refs/heads/${BRANCH}/lib/versioncheck.php" \ + | grep -oP 'PHP_VERSION_ID >= \K\d+' | head -1) + + if [ -n "$MAX_PHP_ID" ]; then + # 80600 / 100 = 806, % 100 = 6, - 1 = 5 → PHP 8.5 + MAX_MINOR=$(( (MAX_PHP_ID / 100) % 100 - 1 )) + PHP_VERSION="8.${MAX_MINOR}" + else + PHP_VERSION="8.3" + fi + + echo "version=$PHP_VERSION" >> "$GITHUB_OUTPUT" + echo "::notice::Using PHP ${PHP_VERSION} for build" + + - name: Generate app matrix + id: generate + run: | + BRANCH="${{ steps.parse.outputs.branch }}" + MAJOR="${{ steps.parse.outputs.major }}" + + if [ "$BRANCH" = "master" ]; then + CONFIG="master.json" + else + CONFIG="stable${MAJOR}.json" + fi + + if [ ! -f "$CONFIG" ]; then + echo "::error::Config file $CONFIG not found. Create it for NC${MAJOR} releases." + exit 1 + fi + + # Use the resolved ref from the version check step. + # For tags: "v32.0.8". For branches: "stable34" or "" (master). + REF="${{ steps.ref.outputs.ref }}" + + MATRIX=$(jq -c --arg ref "$REF" \ + 'map(. + {branch: $ref}) | {include: .}' "$CONFIG") + + echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" + echo "Generated matrix with $(echo "$MATRIX" | jq '.include | length') apps" + + fetch-server: + needs: init + runs-on: ubuntu-latest + steps: + - name: Checkout server + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: nextcloud/server + ref: ${{ needs.init.outputs.ref }} + + - name: Save commit hash + run: git rev-parse HEAD > release-commit-hash + + - name: Clean dev files + run: | + # NC25+ uses .nextcloudignore with gitignore-style patterns. + # git ls-files -ic finds tracked files matching the ignore patterns. + # Older versions fall back to a hardcoded removal list. + if [ -f ".nextcloudignore" ]; then + git ls-files -ic --exclude-from=.nextcloudignore -z | xargs -0 -r -n 10 -- rm -fr + find . -empty -type d -delete + else + rm -rf .babelrc .codecov.yml .devcontainer .drone.yml .editorconfig + rm -rf .envrc .eslintignore .eslintrc.js .git-blame-ignore-revs + rm -rf .gitattributes .github .gitignore .gitmodules .idea .jshintrc + rm -rf .mailmap .npmignore .php-cs-fixer.dist.php .php_cs.dist + rm -rf .pre-commit-config.yaml .scrutinizer.yml .tag .tx + rm -rf CHANGELOG.md CODE_OF_CONDUCT.md COPYING-README DESIGN.md + rm -rf Makefile README.md SECURITY.md __mocks__ __tests__ + rm -rf apps/dav/bin apps/testing + rm -rf autotest-checkers.sh autotest-external.sh autotest-js.sh autotest.sh + rm -rf babel.config.js build codecov.yml contribute custom.d.ts + rm -rf cypress cypress.config.ts cypress.d.ts + rm -rf eslint.config.js eslint.config.mjs flake.lock flake.nix + rm -rf jest.config.js jest.config.ts openapi.json + rm -rf psalm-ncu.xml psalm-ocp.xml psalm.xml stylelint.config.js + rm -rf tests tsconfig.json vendor-bin vite.config.ts + rm -rf vitest.config.mts vitest.config.ts + rm -rf webpack.common.cjs webpack.common.js webpack.config.js + rm -rf webpack.dev.js webpack.modules.cjs webpack.modules.js webpack.prod.js + rm -rf window.d.ts + rm -rf .direnv .well-known config/config.php data + fi + + - name: Remove .git + run: rm -rf .git + + - name: Upload + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: server + path: . + include-hidden-files: true + retention-days: 1 + + fetch-3rdparty: + needs: init + runs-on: ubuntu-latest + steps: + - name: Checkout 3rdparty + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: nextcloud/3rdparty + ref: ${{ needs.init.outputs.ref }} + + - name: Clean + run: rm -rf .git .github .gitignore README.md + + - name: Upload + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: 3rdparty + path: . + include-hidden-files: true + retention-days: 1 + + fetch-apps: + needs: init + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.init.outputs.matrix) }} + + steps: + - name: Checkout ${{ matrix.id }} + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: ${{ matrix.repo }} + ref: ${{ matrix.branch }} + token: ${{ secrets.RELEASE_TOKEN || github.token }} + + - name: Check composer + id: check_composer + run: | + # Only run composer if there are actual non-dev package dependencies. + # Filter out php, ext-*, and bamarni (dev tool often in require by mistake). + if [ -f composer.json ] && jq -e ' + .require // {} | to_entries + | map(select( + .key != "php" + and (.key | startswith("ext-") | not) + and (.key | startswith("bamarni/") | not) + )) + | length > 0 + ' composer.json > /dev/null 2>&1; then + echo "found=true" >> "$GITHUB_OUTPUT" + else + echo "found=false" >> "$GITHUB_OUTPUT" + fi + + - name: Set up PHP + if: steps.check_composer.outputs.found == 'true' + uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2.32.0 + with: + php-version: ${{ needs.init.outputs.php_version }} + tools: composer + + - name: Run composer install + if: steps.check_composer.outputs.found == 'true' + # Default: --no-dev -a --quiet (with scripts, for apps like notifications + # that vendor deps via post-install). Override per-app via composer_args in JSON. + run: composer install ${{ matrix.composer_args != '' && matrix.composer_args || '--no-dev -a --quiet' }} + + - name: Remove .git + run: rm -rf .git + + - name: Upload + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: ${{ matrix.id }} + path: . + include-hidden-files: true + retention-days: 1 + + fetch-updater: + needs: init + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: nextcloud/updater + ref: ${{ needs.init.outputs.ref }} + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: updater + path: | + index.php + updater.phar + retention-days: 1 + + fetch-example-files: + needs: init + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: nextcloud/example-files + ref: ${{ needs.init.outputs.ref }} + - run: rm -rf .git + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: example-files + path: . + include-hidden-files: true + retention-days: 1 + + fetch-docs: + needs: init + runs-on: ubuntu-latest + continue-on-error: true + steps: + - name: Checkout documentation (gh-pages) + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: nextcloud/documentation + ref: gh-pages + + - name: Extract docs for version + run: | + MAJOR="${{ needs.init.outputs.major }}" + mkdir -p /tmp/docs/user_manual/en /tmp/docs/admin_manual + + for base in "server/${MAJOR}" "server/stable${MAJOR}" "${MAJOR}" "."; do + if [ -d "${base}/user_manual/en" ]; then + cp -a "${base}/user_manual/en/"* /tmp/docs/user_manual/en/ + break + fi + done + + for base in "server/${MAJOR}" "server/stable${MAJOR}" "${MAJOR}" "."; do + if [ -d "${base}/admin_manual" ]; then + cp -a "${base}/admin_manual/"* /tmp/docs/admin_manual/ + break + fi + done + + find . -name "Nextcloud_User_Manual.pdf" -exec cp -a {} /tmp/docs/ \; 2>/dev/null + + - name: Upload docs + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: docs + path: /tmp/docs/ + retention-days: 1 + + assemble: + needs: [init, fetch-server, fetch-3rdparty, fetch-apps, fetch-updater, fetch-example-files, fetch-docs] + if: always() && needs.fetch-server.result == 'success' && needs.fetch-apps.result == 'success' + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout config and scripts + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + path: _config + + - name: Download all artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + + - name: Set up PHP + uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2.32.0 + with: + php-version: ${{ needs.init.outputs.php_version }} + + - name: Assemble release + run: | + # Server is the base + mv server /tmp/nextcloud + + # Replace server's 3rdparty submodule with the standalone repo + rm -rf /tmp/nextcloud/3rdparty + mv 3rdparty /tmp/nextcloud/3rdparty + + # Place all apps from the matrix into apps/ + # Skip non-app artifacts (handled separately below) + for app_dir in */; do + id=$(basename "$app_dir") + case "$id" in _config|docs|release|updater|example-files) continue ;; esac + mv "$app_dir" "/tmp/nextcloud/apps/$id" + done + + # Updater: only index.php + updater.phar + mkdir -p /tmp/nextcloud/updater + mv updater/index.php /tmp/nextcloud/updater/index.php + mv updater/updater.phar /tmp/nextcloud/updater/updater.phar + rm -rf updater + + # Example files replace skeleton + rm -rf /tmp/nextcloud/core/skeleton + mv example-files /tmp/nextcloud/core/skeleton + + - name: Integrate documentation + run: | + if [ -d docs ] && [ "$(ls -A docs)" ]; then + rm -f /tmp/nextcloud/core/doc/user/index.php /tmp/nextcloud/core/doc/admin/index.php + [ -d docs/user_manual/en ] && cp -a docs/user_manual/en/* /tmp/nextcloud/core/doc/user/ + [ -d docs/admin_manual ] && cp -a docs/admin_manual/* /tmp/nextcloud/core/doc/admin/ + [ -f docs/Nextcloud_User_Manual.pdf ] && cp -a "docs/Nextcloud_User_Manual.pdf" "/tmp/nextcloud/core/skeleton/Nextcloud Manual.pdf" + fi + rm -rf docs + + - name: Clean dev files from all apps + run: | + # Run on ALL app dirs (matrix apps + core apps from server), + # plus core/ and settings/ which also contain dev files. + # Matrix apps may already be partially cleaned by .nextcloudignore + # during fetch, but core apps from the server need this pass. + SCRIPT="_config/.github/scripts/clean-dev-files.sh" + shopt -s nullglob + for dir in /tmp/nextcloud/apps/*/ /tmp/nextcloud/core/ /tmp/nextcloud/settings/; do + [ -d "$dir" ] || continue + bash "$SCRIPT" "$dir" + done + shopt -u nullglob + + - name: Final cleanup + run: | + rm -rf _config + find /tmp/nextcloud -name .git -exec rm -rf {} + 2>/dev/null || true + rm -f /tmp/nextcloud/config/config.php + rm -rf /tmp/nextcloud/data + + - name: Update version.php + env: + CHANNEL: ${{ needs.init.outputs.channel }} + BRANCH: ${{ needs.init.outputs.branch }} + run: | + # Rewrite version.php with release metadata. + # Preserves OC_Version and OC_VersionString from the repo. + # Sets channel (stable/beta), build timestamp, and edition. + # Must match the release script's replaceversion() output exactly. + COMMIT_HASH=$(cat /tmp/nextcloud/release-commit-hash) + rm -f /tmp/nextcloud/release-commit-hash + export BUILD_STRING="$(date -u +%Y-%m-%dT%H:%M:%S+00:00) ${COMMIT_HASH}" + + php << 'EOPHP' + /tmp/signing-key.pem + if [ -n "$SIGN_CERT" ]; then + echo "$SIGN_CERT" > /tmp/signing-cert.pem + CERT=/tmp/signing-cert.pem + else + CERT="$NC/resources/codesigning/core.crt" + fi + chmod 755 "$NC/occ" + chmod 777 "$NC/config" + php "$NC/occ" integrity:sign-core --privateKey=/tmp/signing-key.pem --certificate="$CERT" --path "$NC" + for app_dir in "$NC"/apps/*/; do + php "$NC/occ" integrity:sign-app --privateKey=/tmp/signing-key.pem --certificate="$CERT" --path="$app_dir" + done + rm -f /tmp/signing-key.pem /tmp/signing-cert.pem "$NC/config/config.php" + + - name: Generate metadata file + # NC30+ ships a .metadata JSON with migration info for the updater. + # Requires a temporary Nextcloud installation to run occ. + if: needs.init.outputs.major >= 30 + run: | + VERSION="${{ needs.init.outputs.version }}" + NC=/tmp/nextcloud + START_TS=$(date +%s) + + # Install to a temp copy (not the release tree) to avoid polluting it + INSTALL_DIR=$(mktemp -d) + cp -a "$NC" "$INSTALL_DIR/nextcloud" + chmod 755 "$INSTALL_DIR/nextcloud/occ" + chmod 777 "$INSTALL_DIR/nextcloud/config" + + php "$INSTALL_DIR/nextcloud/occ" maintenance:install \ + --admin-user admin --admin-pass admin 2>&1 || true + + php "$INSTALL_DIR/nextcloud/occ" migrations:generate-metadata > /tmp/metadata-raw.json 2>/dev/null || true + + if [ ! -s /tmp/metadata-raw.json ]; then + echo "::warning::Could not generate metadata" + rm -rf "$INSTALL_DIR" + exit 0 + fi + + NOW_TS=$(date +%s) + DURATION=$((NOW_TS - START_TS)) + mkdir -p "$GITHUB_WORKSPACE/releases" + + export METADATA_FILE="/tmp/metadata-raw.json" + export OUTPUT_FILE="$GITHUB_WORKSPACE/releases/nextcloud-${VERSION}.metadata" + export BUILD_START="$START_TS" + export BUILD_DURATION="$DURATION" + + php << 'EOPHP' + (int)getenv('BUILD_START'), + 'duration' => (int)getenv('BUILD_DURATION'), + ]; + file_put_contents( + getenv('OUTPUT_FILE'), + json_encode($meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + ); + echo "Metadata file created\n"; + EOPHP + + rm -rf "$INSTALL_DIR" /tmp/metadata-raw.json + + - name: Set permissions and create archives + run: | + VERSION="${{ needs.init.outputs.version }}" + # Match release script: dirs 755, files 644, owner nobody:nogroup + find /tmp/nextcloud -type d -exec chmod 755 {} \; + find /tmp/nextcloud -type f -exec chmod 644 {} \; + sudo chown -R nobody:nogroup /tmp/nextcloud + cd /tmp + mkdir -p "$GITHUB_WORKSPACE/releases" + sudo tar jcf "$GITHUB_WORKSPACE/releases/nextcloud-${VERSION}.tar.bz2" nextcloud --format=gnu + sudo zip -rq9 "$GITHUB_WORKSPACE/releases/nextcloud-${VERSION}.zip" nextcloud + ls -lh "$GITHUB_WORKSPACE/releases/" + + - name: Generate checksums + run: | + cd "$GITHUB_WORKSPACE/releases" + for file in nextcloud-*; do + [[ "$file" == *.sha256 || "$file" == *.sha512 || "$file" == *.md5 ]] && continue + sha256sum "$file" > "${file}.sha256" + sha512sum "$file" > "${file}.sha512" + md5sum "$file" > "${file}.md5" + done + + - name: Upload build artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: release + path: releases/ + + compare: + needs: [init, assemble] + if: always() && needs.assemble.result == 'success' + runs-on: ubuntu-latest + + steps: + - name: Download workflow build + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: release + path: /tmp/new-release + + - name: Download release script assets + # Downloads the tar.bz2 attached by the release script to this same release. + # Both builds ran from the same branch HEAD, so diffs indicate real issues. + id: download + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${{ needs.init.outputs.tag }}" + VERSION="${{ needs.init.outputs.version }}" + mkdir -p /tmp/existing-release + if gh release download "$TAG" \ + --repo "${{ github.repository }}" \ + --pattern "nextcloud-${VERSION}.tar.bz2" \ + --dir /tmp/existing-release 2>/dev/null; then + echo "found=true" >> "$GITHUB_OUTPUT" + else + echo "::warning::No tar.bz2 attached to $TAG yet" + echo "found=false" >> "$GITHUB_OUTPUT" + fi + + - name: Compare file lists + if: steps.download.outputs.found == 'true' + run: | + VERSION="${{ needs.init.outputs.version }}" + + mkdir -p /tmp/existing /tmp/new + tar -xf "/tmp/existing-release/nextcloud-${VERSION}.tar.bz2" -C /tmp/existing + cd /tmp/new && sudo tar -xf "/tmp/new-release/nextcloud-${VERSION}.tar.bz2" && cd / + + (cd /tmp/existing && find . -type f | sort) > /tmp/existing-files.txt + (cd /tmp/new && find . -type f | sort) > /tmp/new-files.txt + + comm -23 /tmp/existing-files.txt /tmp/new-files.txt > /tmp/only-in-existing.txt + comm -13 /tmp/existing-files.txt /tmp/new-files.txt > /tmp/only-in-new.txt + + EXISTING_COUNT=$(wc -l < /tmp/existing-files.txt) + NEW_COUNT=$(wc -l < /tmp/new-files.txt) + MISSING=$(wc -l < /tmp/only-in-existing.txt) + EXTRA=$(wc -l < /tmp/only-in-new.txt) + + # Known acceptable diffs between workflow and release script: + # - signature.json: different signing keys (test vs production) + # - core/doc/, Manual.pdf: docs may differ based on gh-pages timing + # - .chunk.(mjs|css): hash-based filenames change with any code diff + # - *.config.*: workflow strips dev configs the release script kept + # - l10n: translation files may update between builds + KNOWN_PATTERN='(signature\.json|core/doc/|Nextcloud Manual\.pdf|\.chunk\.(mjs|css)|\.config\.(js|ts|mjs|cjs)$|\.prettierignore|/l10n/[^/]*\.(js|json)$)' + UNEXPECTED_MISSING=$(grep -v -E "$KNOWN_PATTERN" /tmp/only-in-existing.txt | wc -l) + UNEXPECTED_EXTRA=$(grep -v -E "$KNOWN_PATTERN" /tmp/only-in-new.txt | wc -l) + + { + echo "## Build Comparison: ${{ needs.init.outputs.tag }}" + echo "" + echo "| | Files |" + echo "|---|---|" + echo "| Release script | ${EXISTING_COUNT} |" + echo "| Workflow build | ${NEW_COUNT} |" + echo "| Only in release script | ${MISSING} (${UNEXPECTED_MISSING} unexpected) |" + echo "| Only in workflow | ${EXTRA} (${UNEXPECTED_EXTRA} unexpected) |" + echo "" + + if [ "$UNEXPECTED_MISSING" -eq 0 ] && [ "$UNEXPECTED_EXTRA" -eq 0 ]; then + echo "### All differences are expected" + fi + + if [ "$UNEXPECTED_MISSING" -gt 0 ]; then + echo "### Unexpected files only in release script (${UNEXPECTED_MISSING})" + echo '
Show' + echo "" + echo '```' + grep -v -E "$KNOWN_PATTERN" /tmp/only-in-existing.txt + echo '```' + echo '
' + echo "" + fi + + if [ "$UNEXPECTED_EXTRA" -gt 0 ]; then + echo "### Unexpected files only in workflow (${UNEXPECTED_EXTRA})" + echo '
Show' + echo "" + echo '```' + grep -v -E "$KNOWN_PATTERN" /tmp/only-in-new.txt + echo '```' + echo '
' + fi + } >> "$GITHUB_STEP_SUMMARY" + + echo "=== Only in release script build ===" + grep -v -E "$KNOWN_PATTERN" /tmp/only-in-existing.txt || echo "(none)" + echo "=== Only in workflow build ===" + grep -v -E "$KNOWN_PATTERN" /tmp/only-in-new.txt || echo "(none)" + + if [ "$UNEXPECTED_MISSING" -gt 0 ] || [ "$UNEXPECTED_EXTRA" -gt 0 ]; then + echo "::warning::Differences found between release script and workflow build" + fi + + # Content comparison: hash common files to detect content diffs + comm -12 /tmp/existing-files.txt /tmp/new-files.txt > /tmp/common-files.txt + COMMON=$(wc -l < /tmp/common-files.txt) + + # Hash all common files in both trees + (cd /tmp/existing && xargs -a /tmp/common-files.txt -d '\n' sha256sum 2>/dev/null | sort) > /tmp/existing-hashes.txt + (cd /tmp/new && xargs -a /tmp/common-files.txt -d '\n' sha256sum 2>/dev/null | sort) > /tmp/new-hashes.txt + + # Find files with different content + diff /tmp/existing-hashes.txt /tmp/new-hashes.txt > /tmp/hash-diff.txt 2>/dev/null || true + CONTENT_DIFFS=$(grep "^[<>]" /tmp/hash-diff.txt | sed 's/^[<>] [a-f0-9]* \+//' | sort -u | wc -l) + + { + echo "" + echo "### Content comparison" + echo "" + echo "| | Count |" + echo "|---|---|" + echo "| Common files | ${COMMON} |" + echo "| Content differs | ${CONTENT_DIFFS} |" + + if [ "$CONTENT_DIFFS" -gt 0 ]; then + # Filter out version.php (expected: different build timestamp) + UNEXPECTED_CONTENT=$(grep "^[<>]" /tmp/hash-diff.txt | sed 's/^[<>] [a-f0-9]* \+//' | sort -u | grep -v "version\.php" | wc -l) + echo "| Unexpected content diffs | ${UNEXPECTED_CONTENT} |" + echo "" + if [ "$UNEXPECTED_CONTENT" -gt 0 ]; then + echo "
Files with different content (${UNEXPECTED_CONTENT})" + echo "" + echo '```' + grep "^[<>]" /tmp/hash-diff.txt | sed 's/^[<>] [a-f0-9]* \+//' | sort -u | grep -v "version\.php" + echo '```' + echo "
" + else + echo "Only version.php differs (expected: different build timestamp)" + fi + else + echo "" + echo "All common files are identical" + fi + } >> "$GITHUB_STEP_SUMMARY" + + echo "=== Content diffs (excluding version.php) ===" + if [ "$CONTENT_DIFFS" -gt 0 ]; then + grep "^[<>]" /tmp/hash-diff.txt | sed 's/^[<>] [a-f0-9]* \+//' | sort -u | grep -v "version\.php" || echo "(none)" + else + echo "(none)" + fi From e40abd2898d75842bd25c3bad534a6f0d78e09c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Tue, 2 Jun 2026 06:15:22 +0200 Subject: [PATCH 05/15] feat: add release orchestrator (release.yml) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Triggers on release:published (tags starting with v). Calls tagger and builder in parallel. Signed-off-by: John Molakvoæ (skjnldsv) --- .github/workflows/release.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000000..98d68d70057 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT + +name: Release pipeline +run-name: "Release ${{ github.ref_name }}" + +on: + release: + types: [published] + +jobs: + tag: + if: startsWith(github.ref_name, 'v') + uses: ./.github/workflows/release-tag.yml + with: + tag: ${{ github.ref_name }} + secrets: inherit + + build: + if: startsWith(github.ref_name, 'v') + uses: ./.github/workflows/release-build.yml + with: + tag: ${{ github.ref_name }} + secrets: inherit From 0ae423d756d5ae5203640e837a535c7aa3894f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Tue, 2 Jun 2026 06:15:32 +0200 Subject: [PATCH 06/15] chore: add dependabot for actions, update README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- .github/dependabot.yml | 13 ++++++++++ README.md | 55 ++++++++++++++++++++++++++++++++---------- 2 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..fc4d9a59f8d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT + +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + github-actions: + patterns: + - "*" diff --git a/README.md b/README.md index 4bbd1a745f9..13e9e4c0924 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,42 @@ -Do not push any code to that repository. - -# How to publish -1. On https://github.com/nextcloud, tag appropriate commit on the source repository -2. Push the new tag to this repository -3. Create release on this repository - -# Automatic package and publish -1. Make sure you have the [necessary workflow](https://github.com/nextcloud/.github/blob/master/workflow-templates/appstore-build-publish.yml) on your https://github.com/nextcloud source repository -2. Make sure your tagged commit also have the workflow -3. Make sure this repository have the proper `APP_PRIVATE_KEY` secret set -4. Make the `nextcloud_release_service` user is a co-maintainer of your app on https://apps.nextcloud.com/ -5. Make sure you have admin rights to this repository +# Nextcloud Server Releases + +Release artifacts and automation for Nextcloud server. Branches are synced daily from `nextcloud/server`. + +## How releases work + +When a release is published on this repository, three things happen in parallel: + +1. **Changelog** is generated and attached to the release +2. **All app repositories** get tagged at their stable branch HEAD +3. **Release archives** are built independently and compared against the release script output + +The tagger and builder can also be run manually for re-tagging or testing. + +## Release configuration + +One JSON file per major version lists all bundled apps: + +- `stable32.json`, `stable33.json` — 23 apps +- `stable34.json`, `master.json` — 25 apps (+files_lock, +office) + +When a new app is added to the release or an existing one is removed, edit the corresponding JSON file. + +`tag-only.json` lists repositories that should be tagged on release but are not part of the build (server, 3rdparty, updater, example-files, documentation). + +## Running manually + +**Re-tag a release**: Actions > "Tag all repositories" > enter tag (e.g., `v34.0.1`). Check "force" to overwrite existing tags. + +**Rebuild a release**: Actions > "Build and compare release" > enter tag. Compares the result against the release script's archives on the same GitHub release. + +## Where we are + +The old release script still creates releases and uploads to the download server. This workflow runs alongside it to validate that both produce the same result. + +Once we are confident the output matches, the release script will be retired and this workflow will take over publishing. + +## What comes next + +- Enable publishing directly from the workflow (retire the release script) +- Auto-create PRs to the updater server with release configuration +- Add GPG signatures for archives From f990cdc1976fcfbbcd2fe4a4f6f08497ae222ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Tue, 2 Jun 2026 06:27:39 +0200 Subject: [PATCH 07/15] refactor: extract build logic into standalone scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move inline bash/PHP from the workflow into reusable scripts that can be run manually without GitHub Actions. Scripts: - fetch-all.sh: clone all components from JSON config - assemble.sh: place components into nextcloud/ structure - clean-server-dev-files.sh: server .nextcloudignore/hardcoded cleanup - clean-dev-files.sh: app dev file cleanup (already existed) - update-version-php.sh: rewrite version.php with release metadata - sign-release.sh: occ integrity:sign-core/sign-app - generate-metadata.sh: install + occ migrations:generate-metadata - package.sh: permissions, tar.bz2+zip, checksums README.md in scripts/ documents the manual release process. Workflow becomes thin orchestration calling scripts with arguments. Signed-off-by: John Molakvoæ (skjnldsv) --- .github/scripts/README.md | 68 +++++++ .github/scripts/assemble.sh | 50 +++++ .github/scripts/clean-server-dev-files.sh | 43 +++++ .github/scripts/fetch-all.sh | 95 ++++++++++ .github/scripts/generate-metadata.sh | 59 ++++++ .github/scripts/package.sh | 45 +++++ .github/scripts/sign-release.sh | 38 ++++ .github/scripts/update-version-php.sh | 44 +++++ .github/workflows/release-build.yml | 214 ++++------------------ 9 files changed, 473 insertions(+), 183 deletions(-) create mode 100644 .github/scripts/README.md create mode 100755 .github/scripts/assemble.sh create mode 100755 .github/scripts/clean-server-dev-files.sh create mode 100755 .github/scripts/fetch-all.sh create mode 100755 .github/scripts/generate-metadata.sh create mode 100755 .github/scripts/package.sh create mode 100755 .github/scripts/sign-release.sh create mode 100755 .github/scripts/update-version-php.sh diff --git a/.github/scripts/README.md b/.github/scripts/README.md new file mode 100644 index 00000000000..70992fbab03 --- /dev/null +++ b/.github/scripts/README.md @@ -0,0 +1,68 @@ +# Release Scripts + +Standalone scripts to build a Nextcloud server release without GitHub Actions. These are the same scripts used by the `release-build.yml` workflow. + +## Prerequisites + +- `git`, `jq`, `php` (compatible version), `composer` +- `sudo` (for setting file ownership) +- `tar`, `zip`, `sha256sum`, `sha512sum`, `md5sum` +- Signing key (PEM) if signing is needed + +## Quick start + +Build a release for v34.0.1: + +```bash +SCRIPTS=".github/scripts" +VERSION="34.0.1" +TAG="v${VERSION}" +CONFIG="stable34.json" + +# 1. Fetch all components +bash "$SCRIPTS/fetch-all.sh" "$TAG" "$CONFIG" /tmp/build --docs + +# 2. Clean server dev files (removes .git automatically after using it) +bash "$SCRIPTS/clean-server-dev-files.sh" /tmp/build/server + +# 3. Assemble into nextcloud/ structure +bash "$SCRIPTS/assemble.sh" /tmp/build /tmp/nextcloud + +# 4. Clean dev files from all apps, core, settings +for dir in /tmp/nextcloud/apps/*/ /tmp/nextcloud/core/ /tmp/nextcloud/settings/; do + bash "$SCRIPTS/clean-dev-files.sh" "$dir" +done + +# 5. Update version.php +bash "$SCRIPTS/update-version-php.sh" /tmp/nextcloud stable stable34 + +# 6. Sign (optional, requires signing key) +bash "$SCRIPTS/sign-release.sh" /tmp/nextcloud /path/to/signing-key.pem + +# 7. Generate metadata (NC30+, optional) +bash "$SCRIPTS/generate-metadata.sh" /tmp/nextcloud "$VERSION" ./releases + +# 8. Package (creates tar.bz2, zip, checksums) +bash "$SCRIPTS/package.sh" /tmp/nextcloud "$VERSION" ./releases +``` + +## Scripts + +| Script | Purpose | +|---|---| +| `fetch-all.sh` | Clone server, 3rdparty, all apps, updater, skeleton, docs | +| `assemble.sh` | Place all components into the `nextcloud/` structure | +| `clean-server-dev-files.sh` | Remove dev files from server (uses .nextcloudignore or hardcoded list) | +| `clean-dev-files.sh` | Remove dev files from an app directory | +| `update-version-php.sh` | Rewrite version.php with channel, build timestamp, edition | +| `sign-release.sh` | Sign core + all apps with occ integrity commands | +| `generate-metadata.sh` | Generate migration metadata (NC30+) | +| `package.sh` | Set permissions, create tar.bz2 + zip, generate checksums | + +## Notes + +- `fetch-all.sh` runs composer automatically on apps with runtime dependencies +- `clean-server-dev-files.sh` must run before removing `.git` from the server (it uses `git ls-files` for .nextcloudignore) +- `sign-release.sh` uses the repo's `resources/codesigning/core.crt` by default. Pass a custom cert as the third argument for testing +- `package.sh` requires `sudo` to set file ownership to `nobody:nogroup` +- The workflow adds parallelism (fetches all apps simultaneously) and a compare step. The scripts produce identical output diff --git a/.github/scripts/assemble.sh b/.github/scripts/assemble.sh new file mode 100755 index 00000000000..d4748259981 --- /dev/null +++ b/.github/scripts/assemble.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT +# +# Assemble a Nextcloud release from fetched components. +# Usage: assemble.sh +# +# Expects the build dir to contain: server/, 3rdparty/, apps/*/, updater/, example-files/ + +set -e + +BUILD="${1:?Usage: assemble.sh }" +OUTPUT="${2:?Missing output directory}" + +# Server is the base +cp -a "$BUILD/server" "$OUTPUT" + +# Replace 3rdparty +rm -rf "$OUTPUT/3rdparty" +cp -a "$BUILD/3rdparty" "$OUTPUT/3rdparty" + +# Place apps +for app_dir in "$BUILD/apps"/*/; do + id=$(basename "$app_dir") + cp -a "$app_dir" "$OUTPUT/apps/$id" +done + +# Updater: only index.php + updater.phar +mkdir -p "$OUTPUT/updater" +cp "$BUILD/updater/index.php" "$OUTPUT/updater/index.php" +cp "$BUILD/updater/updater.phar" "$OUTPUT/updater/updater.phar" + +# Example files replace skeleton +rm -rf "$OUTPUT/core/skeleton" +cp -a "$BUILD/example-files" "$OUTPUT/core/skeleton" + +# Docs (if fetched) +if [ -d "$BUILD/docs" ] && [ "$(ls -A "$BUILD/docs")" ]; then + rm -f "$OUTPUT/core/doc/user/index.php" "$OUTPUT/core/doc/admin/index.php" + [ -d "$BUILD/docs/user_manual/en" ] && cp -a "$BUILD/docs/user_manual/en/"* "$OUTPUT/core/doc/user/" + [ -d "$BUILD/docs/admin_manual" ] && cp -a "$BUILD/docs/admin_manual/"* "$OUTPUT/core/doc/admin/" + [ -f "$BUILD/docs/Nextcloud_User_Manual.pdf" ] && cp -a "$BUILD/docs/Nextcloud_User_Manual.pdf" "$OUTPUT/core/skeleton/Nextcloud Manual.pdf" +fi + +# Final cleanup +find "$OUTPUT" -name .git -exec rm -rf {} + 2>/dev/null || true +rm -f "$OUTPUT/config/config.php" +rm -rf "$OUTPUT/data" + +echo "Release assembled at $OUTPUT" diff --git a/.github/scripts/clean-server-dev-files.sh b/.github/scripts/clean-server-dev-files.sh new file mode 100755 index 00000000000..e34b8f44c90 --- /dev/null +++ b/.github/scripts/clean-server-dev-files.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT +# +# Clean development files from the server repository. +# Removes .git at the end (needs it first for .nextcloudignore). +# Usage: clean-server-dev-files.sh [server-dir] + +set -e + +cd "${1:-.}" + +# NC25+ uses .nextcloudignore with gitignore-style patterns. +# git ls-files -ic finds tracked files matching the ignore patterns. +# Older versions fall back to a hardcoded removal list. +if [ -f ".nextcloudignore" ]; then + git ls-files -ic --exclude-from=.nextcloudignore -z | xargs -0 -r -n 10 -- rm -fr + find . -empty -type d -delete +else + rm -rf .babelrc .codecov.yml .devcontainer .drone.yml .editorconfig + rm -rf .envrc .eslintignore .eslintrc.js .git-blame-ignore-revs + rm -rf .gitattributes .github .gitignore .gitmodules .idea .jshintrc + rm -rf .mailmap .npmignore .php-cs-fixer.dist.php .php_cs.dist + rm -rf .pre-commit-config.yaml .scrutinizer.yml .tag .tx + rm -rf CHANGELOG.md CODE_OF_CONDUCT.md COPYING-README DESIGN.md + rm -rf Makefile README.md SECURITY.md __mocks__ __tests__ + rm -rf apps/dav/bin apps/testing + rm -rf autotest-checkers.sh autotest-external.sh autotest-js.sh autotest.sh + rm -rf babel.config.js build codecov.yml contribute custom.d.ts + rm -rf cypress cypress.config.ts cypress.d.ts + rm -rf eslint.config.js eslint.config.mjs flake.lock flake.nix + rm -rf jest.config.js jest.config.ts openapi.json + rm -rf psalm-ncu.xml psalm-ocp.xml psalm.xml stylelint.config.js + rm -rf tests tsconfig.json vendor-bin vite.config.ts + rm -rf vitest.config.mts vitest.config.ts + rm -rf webpack.common.cjs webpack.common.js webpack.config.js + rm -rf webpack.dev.js webpack.modules.cjs webpack.modules.js webpack.prod.js + rm -rf window.d.ts + rm -rf .direnv .well-known config/config.php data +fi + +# Remove .git last (needed above for git ls-files) +rm -rf .git diff --git a/.github/scripts/fetch-all.sh b/.github/scripts/fetch-all.sh new file mode 100755 index 00000000000..109e76a40ff --- /dev/null +++ b/.github/scripts/fetch-all.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT +# +# Fetch all release components (server, 3rdparty, apps, updater, skeleton). +# Usage: fetch-all.sh [--docs] +# +# Examples: +# fetch-all.sh v34.0.1 stable34.json /tmp/build +# fetch-all.sh stable34 stable34.json /tmp/build --docs + +set -e + +REF="${1:?Usage: fetch-all.sh [--docs]}" +CONFIG="${2:?Missing config.json path}" +OUTPUT="${3:?Missing output directory}" +FETCH_DOCS="${4:-}" + +mkdir -p "$OUTPUT" + +clone() { + local repo="$1" ref="$2" dest="$3" + echo "Fetching $repo@$ref → $dest" + git clone --depth 1 --branch "$ref" "https://github.com/$repo.git" "$dest" -q 2>/dev/null \ + || git clone --depth 1 "https://github.com/$repo.git" "$dest" -q +} + +# Server +clone "nextcloud/server" "$REF" "$OUTPUT/server" +git -C "$OUTPUT/server" rev-parse HEAD > "$OUTPUT/server/release-commit-hash" + +# 3rdparty (replace server's submodule) +rm -rf "$OUTPUT/server/3rdparty" +clone "nextcloud/3rdparty" "$REF" "$OUTPUT/3rdparty" +rm -rf "$OUTPUT/3rdparty/.git" "$OUTPUT/3rdparty/.github" "$OUTPUT/3rdparty/.gitignore" "$OUTPUT/3rdparty/README.md" + +# Updater (only 2 files needed) +UPDATER_TMP=$(mktemp -d) +clone "nextcloud/updater" "$REF" "$UPDATER_TMP" +mkdir -p "$OUTPUT/updater" +cp "$UPDATER_TMP/index.php" "$OUTPUT/updater/index.php" +cp "$UPDATER_TMP/updater.phar" "$OUTPUT/updater/updater.phar" +rm -rf "$UPDATER_TMP" + +# Example files (becomes core/skeleton) +clone "nextcloud/example-files" "$REF" "$OUTPUT/example-files" +rm -rf "$OUTPUT/example-files/.git" + +# Apps from JSON config +jq -r '.[] | "\(.id) \(.repo) \(.composer_args // "")"' "$CONFIG" | while read -r id repo composer_args; do + clone "$repo" "$REF" "$OUTPUT/apps/$id" + rm -rf "$OUTPUT/apps/$id/.git" + + # Run composer if there are real non-dev dependencies + if [ -f "$OUTPUT/apps/$id/composer.json" ] && jq -e ' + .require // {} | to_entries + | map(select( + .key != "php" + and (.key | startswith("ext-") | not) + and (.key | startswith("bamarni/") | not) + )) + | length > 0 + ' "$OUTPUT/apps/$id/composer.json" > /dev/null 2>&1; then + echo " Running composer install for $id" + ARGS="${composer_args:---no-dev -a --quiet}" + (cd "$OUTPUT/apps/$id" && COMPOSER_ALLOW_SUPERUSER=1 composer install $ARGS) + fi +done + +# Docs (optional) +if [ "$FETCH_DOCS" = "--docs" ]; then + MAJOR=$(echo "$REF" | sed 's/^v//;s/\..*//') + DOCS_TMP=$(mktemp -d) + git clone --depth 1 --branch gh-pages "https://github.com/nextcloud/documentation.git" "$DOCS_TMP" -q 2>/dev/null || true + + if [ -d "$DOCS_TMP" ]; then + mkdir -p "$OUTPUT/docs/user_manual/en" "$OUTPUT/docs/admin_manual" + for base in "server/${MAJOR}" "server/stable${MAJOR}" "${MAJOR}" "."; do + if [ -d "$DOCS_TMP/${base}/user_manual/en" ]; then + cp -a "$DOCS_TMP/${base}/user_manual/en/"* "$OUTPUT/docs/user_manual/en/" + break + fi + done + for base in "server/${MAJOR}" "server/stable${MAJOR}" "${MAJOR}" "."; do + if [ -d "$DOCS_TMP/${base}/admin_manual" ]; then + cp -a "$DOCS_TMP/${base}/admin_manual/"* "$OUTPUT/docs/admin_manual/" + break + fi + done + find "$DOCS_TMP" -name "Nextcloud_User_Manual.pdf" -exec cp -a {} "$OUTPUT/docs/" \; 2>/dev/null + rm -rf "$DOCS_TMP" + fi +fi + +echo "All components fetched to $OUTPUT" diff --git a/.github/scripts/generate-metadata.sh b/.github/scripts/generate-metadata.sh new file mode 100755 index 00000000000..30a84d4399b --- /dev/null +++ b/.github/scripts/generate-metadata.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT +# +# Generate migration metadata file (NC30+). +# Installs Nextcloud to a temp directory, runs occ migrations:generate-metadata. +# Usage: generate-metadata.sh + +set -e + +NC="${1:?Usage: generate-metadata.sh }" +VERSION="${2:?Missing version}" +OUTPUT_DIR="${3:?Missing output directory}" +START_TS=$(date +%s) + +# Install to a temp copy to avoid polluting the release tree +INSTALL_DIR=$(mktemp -d) +cp -a "$NC" "$INSTALL_DIR/nextcloud" +chmod 755 "$INSTALL_DIR/nextcloud/occ" +chmod 777 "$INSTALL_DIR/nextcloud/config" + +echo "Installing Nextcloud for metadata generation..." +php "$INSTALL_DIR/nextcloud/occ" maintenance:install \ + --admin-user admin --admin-pass admin 2>&1 || true + +echo "Generating migration metadata..." +php "$INSTALL_DIR/nextcloud/occ" migrations:generate-metadata > /tmp/metadata-raw.json 2>/dev/null || true + +if [ ! -s /tmp/metadata-raw.json ]; then + echo "Warning: could not generate metadata" + rm -rf "$INSTALL_DIR" + exit 0 +fi + +NOW_TS=$(date +%s) +DURATION=$((NOW_TS - START_TS)) +mkdir -p "$OUTPUT_DIR" + +export METADATA_FILE="/tmp/metadata-raw.json" +export OUTPUT_FILE="$OUTPUT_DIR/nextcloud-${VERSION}.metadata" +export BUILD_START="$START_TS" +export BUILD_DURATION="$DURATION" + +php << 'EOPHP' + (int)getenv('BUILD_START'), + 'duration' => (int)getenv('BUILD_DURATION'), +]; +file_put_contents( + getenv('OUTPUT_FILE'), + json_encode($meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) +); +echo "Metadata file created\n"; +EOPHP + +rm -rf "$INSTALL_DIR" /tmp/metadata-raw.json diff --git a/.github/scripts/package.sh b/.github/scripts/package.sh new file mode 100755 index 00000000000..888e0f19025 --- /dev/null +++ b/.github/scripts/package.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT +# +# Set permissions, create archives, and generate checksums. +# Usage: package.sh +# +# Creates: nextcloud-VERSION.tar.bz2, .zip, .sha256, .sha512, .md5 + +set -e + +NC="${1:?Usage: package.sh }" +VERSION="${2:?Missing version}" +OUTPUT="${3:?Missing output directory}" + +mkdir -p "$OUTPUT" + +# Match release script: dirs 755, files 644, owner nobody:nogroup +echo "Setting permissions..." +find "$NC" -type d -exec chmod 755 {} \; +find "$NC" -type f -exec chmod 644 {} \; +sudo chown -R nobody:nogroup "$NC" + +# Create archives from parent directory +PARENT=$(dirname "$NC") +NAME=$(basename "$NC") + +echo "Creating tar.bz2..." +(cd "$PARENT" && sudo tar jcf "$OUTPUT/nextcloud-${VERSION}.tar.bz2" "$NAME" --format=gnu) + +echo "Creating zip..." +(cd "$PARENT" && sudo zip -rq9 "$OUTPUT/nextcloud-${VERSION}.zip" "$NAME") + +# Generate checksums +echo "Generating checksums..." +cd "$OUTPUT" +for file in "nextcloud-${VERSION}.tar.bz2" "nextcloud-${VERSION}.zip"; do + [ -f "$file" ] || continue + sha256sum "$file" > "${file}.sha256" + sha512sum "$file" > "${file}.sha512" + md5sum "$file" > "${file}.md5" +done + +echo "Packages created:" +ls -lh "$OUTPUT/nextcloud-${VERSION}"* diff --git a/.github/scripts/sign-release.sh b/.github/scripts/sign-release.sh new file mode 100755 index 00000000000..b0c74b04773 --- /dev/null +++ b/.github/scripts/sign-release.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT +# +# Sign core and all apps with occ integrity:sign-*. +# Usage: sign-release.sh [certificate] +# +# If certificate is not provided, uses resources/codesigning/core.crt from the release. + +set -e + +NC="${1:?Usage: sign-release.sh [certificate]}" +KEY="${2:?Missing private key path}" +CERT="${3:-$NC/resources/codesigning/core.crt}" + +chmod 755 "$NC/occ" +chmod 777 "$NC/config" + +echo "Signing core..." +php "$NC/occ" integrity:sign-core \ + --privateKey="$KEY" \ + --certificate="$CERT" \ + --path "$NC" + +echo "Signing apps..." +for app_dir in "$NC"/apps/*/; do + app_name=$(basename "$app_dir") + echo " $app_name" + php "$NC/occ" integrity:sign-app \ + --privateKey="$KEY" \ + --certificate="$CERT" \ + --path="$app_dir" +done + +# occ may create config.php during signing +rm -f "$NC/config/config.php" + +echo "Signing complete" diff --git a/.github/scripts/update-version-php.sh b/.github/scripts/update-version-php.sh new file mode 100755 index 00000000000..640e7b52590 --- /dev/null +++ b/.github/scripts/update-version-php.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT +# +# Rewrite version.php with release metadata. +# Usage: update-version-php.sh +# +# Example: update-version-php.sh /tmp/nextcloud stable stable34 + +set -e + +NC="${1:?Usage: update-version-php.sh }" +export CHANNEL="${2:?Missing channel (stable/beta)}" +export BRANCH="${3:?Missing branch (stable34/master)}" + +COMMIT_HASH=$(cat "$NC/release-commit-hash") +rm -f "$NC/release-commit-hash" +export BUILD_STRING="$(date -u +%Y-%m-%dT%H:%M:%S+00:00) ${COMMIT_HASH}" + +# Preserves OC_Version and OC_VersionString from the repo. +# Sets channel, build timestamp, edition, and vendor. +php << 'EOPHP' + release-commit-hash - - name: Clean dev files - run: | - # NC25+ uses .nextcloudignore with gitignore-style patterns. - # git ls-files -ic finds tracked files matching the ignore patterns. - # Older versions fall back to a hardcoded removal list. - if [ -f ".nextcloudignore" ]; then - git ls-files -ic --exclude-from=.nextcloudignore -z | xargs -0 -r -n 10 -- rm -fr - find . -empty -type d -delete - else - rm -rf .babelrc .codecov.yml .devcontainer .drone.yml .editorconfig - rm -rf .envrc .eslintignore .eslintrc.js .git-blame-ignore-revs - rm -rf .gitattributes .github .gitignore .gitmodules .idea .jshintrc - rm -rf .mailmap .npmignore .php-cs-fixer.dist.php .php_cs.dist - rm -rf .pre-commit-config.yaml .scrutinizer.yml .tag .tx - rm -rf CHANGELOG.md CODE_OF_CONDUCT.md COPYING-README DESIGN.md - rm -rf Makefile README.md SECURITY.md __mocks__ __tests__ - rm -rf apps/dav/bin apps/testing - rm -rf autotest-checkers.sh autotest-external.sh autotest-js.sh autotest.sh - rm -rf babel.config.js build codecov.yml contribute custom.d.ts - rm -rf cypress cypress.config.ts cypress.d.ts - rm -rf eslint.config.js eslint.config.mjs flake.lock flake.nix - rm -rf jest.config.js jest.config.ts openapi.json - rm -rf psalm-ncu.xml psalm-ocp.xml psalm.xml stylelint.config.js - rm -rf tests tsconfig.json vendor-bin vite.config.ts - rm -rf vitest.config.mts vitest.config.ts - rm -rf webpack.common.cjs webpack.common.js webpack.config.js - rm -rf webpack.dev.js webpack.modules.cjs webpack.modules.js webpack.prod.js - rm -rf window.d.ts - rm -rf .direnv .well-known config/config.php data - fi + - name: Checkout scripts + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + path: _scripts + sparse-checkout: .github/scripts - - name: Remove .git - run: rm -rf .git + - name: Clean dev files + run: bash _scripts/.github/scripts/clean-server-dev-files.sh && rm -rf _scripts - name: Upload uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 @@ -394,40 +369,21 @@ jobs: - name: Assemble release run: | - # Server is the base - mv server /tmp/nextcloud - - # Replace server's 3rdparty submodule with the standalone repo - rm -rf /tmp/nextcloud/3rdparty - mv 3rdparty /tmp/nextcloud/3rdparty - - # Place all apps from the matrix into apps/ - # Skip non-app artifacts (handled separately below) + # Rearrange artifacts into build dir structure expected by assemble.sh + mkdir -p /tmp/build/apps + mv server /tmp/build/server + mv 3rdparty /tmp/build/3rdparty + mv updater /tmp/build/updater + mv example-files /tmp/build/example-files + [ -d docs ] && mv docs /tmp/build/docs for app_dir in */; do id=$(basename "$app_dir") - case "$id" in _config|docs|release|updater|example-files) continue ;; esac - mv "$app_dir" "/tmp/nextcloud/apps/$id" + case "$id" in _config|release) continue ;; esac + mv "$app_dir" "/tmp/build/apps/$id" done - # Updater: only index.php + updater.phar - mkdir -p /tmp/nextcloud/updater - mv updater/index.php /tmp/nextcloud/updater/index.php - mv updater/updater.phar /tmp/nextcloud/updater/updater.phar - rm -rf updater - - # Example files replace skeleton - rm -rf /tmp/nextcloud/core/skeleton - mv example-files /tmp/nextcloud/core/skeleton - - - name: Integrate documentation - run: | - if [ -d docs ] && [ "$(ls -A docs)" ]; then - rm -f /tmp/nextcloud/core/doc/user/index.php /tmp/nextcloud/core/doc/admin/index.php - [ -d docs/user_manual/en ] && cp -a docs/user_manual/en/* /tmp/nextcloud/core/doc/user/ - [ -d docs/admin_manual ] && cp -a docs/admin_manual/* /tmp/nextcloud/core/doc/admin/ - [ -f docs/Nextcloud_User_Manual.pdf ] && cp -a "docs/Nextcloud_User_Manual.pdf" "/tmp/nextcloud/core/skeleton/Nextcloud Manual.pdf" - fi - rm -rf docs + bash _config/.github/scripts/assemble.sh /tmp/build /tmp/nextcloud + rm -rf /tmp/build - name: Clean dev files from all apps run: | @@ -443,52 +399,13 @@ jobs: done shopt -u nullglob - - name: Final cleanup - run: | - rm -rf _config - find /tmp/nextcloud -name .git -exec rm -rf {} + 2>/dev/null || true - rm -f /tmp/nextcloud/config/config.php - rm -rf /tmp/nextcloud/data - - name: Update version.php - env: - CHANNEL: ${{ needs.init.outputs.channel }} - BRANCH: ${{ needs.init.outputs.branch }} run: | - # Rewrite version.php with release metadata. - # Preserves OC_Version and OC_VersionString from the repo. - # Sets channel (stable/beta), build timestamp, and edition. - # Must match the release script's replaceversion() output exactly. - COMMIT_HASH=$(cat /tmp/nextcloud/release-commit-hash) - rm -f /tmp/nextcloud/release-commit-hash - export BUILD_STRING="$(date -u +%Y-%m-%dT%H:%M:%S+00:00) ${COMMIT_HASH}" - - php << 'EOPHP' - /tmp/signing-key.pem + CERT_ARG="" if [ -n "$SIGN_CERT" ]; then echo "$SIGN_CERT" > /tmp/signing-cert.pem - CERT=/tmp/signing-cert.pem - else - CERT="$NC/resources/codesigning/core.crt" + CERT_ARG="/tmp/signing-cert.pem" fi - chmod 755 "$NC/occ" - chmod 777 "$NC/config" - php "$NC/occ" integrity:sign-core --privateKey=/tmp/signing-key.pem --certificate="$CERT" --path "$NC" - for app_dir in "$NC"/apps/*/; do - php "$NC/occ" integrity:sign-app --privateKey=/tmp/signing-key.pem --certificate="$CERT" --path="$app_dir" - done - rm -f /tmp/signing-key.pem /tmp/signing-cert.pem "$NC/config/config.php" + bash _config/.github/scripts/sign-release.sh /tmp/nextcloud /tmp/signing-key.pem $CERT_ARG + rm -f /tmp/signing-key.pem /tmp/signing-cert.pem - name: Generate metadata file - # NC30+ ships a .metadata JSON with migration info for the updater. - # Requires a temporary Nextcloud installation to run occ. if: needs.init.outputs.major >= 30 run: | - VERSION="${{ needs.init.outputs.version }}" - NC=/tmp/nextcloud - START_TS=$(date +%s) - - # Install to a temp copy (not the release tree) to avoid polluting it - INSTALL_DIR=$(mktemp -d) - cp -a "$NC" "$INSTALL_DIR/nextcloud" - chmod 755 "$INSTALL_DIR/nextcloud/occ" - chmod 777 "$INSTALL_DIR/nextcloud/config" - - php "$INSTALL_DIR/nextcloud/occ" maintenance:install \ - --admin-user admin --admin-pass admin 2>&1 || true - - php "$INSTALL_DIR/nextcloud/occ" migrations:generate-metadata > /tmp/metadata-raw.json 2>/dev/null || true - - if [ ! -s /tmp/metadata-raw.json ]; then - echo "::warning::Could not generate metadata" - rm -rf "$INSTALL_DIR" - exit 0 - fi - - NOW_TS=$(date +%s) - DURATION=$((NOW_TS - START_TS)) - mkdir -p "$GITHUB_WORKSPACE/releases" - - export METADATA_FILE="/tmp/metadata-raw.json" - export OUTPUT_FILE="$GITHUB_WORKSPACE/releases/nextcloud-${VERSION}.metadata" - export BUILD_START="$START_TS" - export BUILD_DURATION="$DURATION" - - php << 'EOPHP' - (int)getenv('BUILD_START'), - 'duration' => (int)getenv('BUILD_DURATION'), - ]; - file_put_contents( - getenv('OUTPUT_FILE'), - json_encode($meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) - ); - echo "Metadata file created\n"; - EOPHP - - rm -rf "$INSTALL_DIR" /tmp/metadata-raw.json - - - name: Set permissions and create archives - run: | - VERSION="${{ needs.init.outputs.version }}" - # Match release script: dirs 755, files 644, owner nobody:nogroup - find /tmp/nextcloud -type d -exec chmod 755 {} \; - find /tmp/nextcloud -type f -exec chmod 644 {} \; - sudo chown -R nobody:nogroup /tmp/nextcloud - cd /tmp mkdir -p "$GITHUB_WORKSPACE/releases" - sudo tar jcf "$GITHUB_WORKSPACE/releases/nextcloud-${VERSION}.tar.bz2" nextcloud --format=gnu - sudo zip -rq9 "$GITHUB_WORKSPACE/releases/nextcloud-${VERSION}.zip" nextcloud - ls -lh "$GITHUB_WORKSPACE/releases/" + bash _config/.github/scripts/generate-metadata.sh \ + /tmp/nextcloud "${{ needs.init.outputs.version }}" "$GITHUB_WORKSPACE/releases" - - name: Generate checksums + - name: Package and checksum run: | - cd "$GITHUB_WORKSPACE/releases" - for file in nextcloud-*; do - [[ "$file" == *.sha256 || "$file" == *.sha512 || "$file" == *.md5 ]] && continue - sha256sum "$file" > "${file}.sha256" - sha512sum "$file" > "${file}.sha512" - md5sum "$file" > "${file}.md5" - done + bash _config/.github/scripts/package.sh \ + /tmp/nextcloud "${{ needs.init.outputs.version }}" "$GITHUB_WORKSPACE/releases" + rm -rf _config - name: Upload build artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 From c33c5b4c5a9c692beb6f24a03b74c867c0d44655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Tue, 2 Jun 2026 06:38:56 +0200 Subject: [PATCH 08/15] chore: bump actions to Node.js 24 compatible versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - actions/checkout v4.2.2 → v6.0.2 - actions/upload-artifact v4.6.2 → v7.0.1 - actions/download-artifact v4.3.0 → v8.0.1 - shivammathur/setup-php v2.32.0 → v2.37.1 Signed-off-by: John Molakvoæ (skjnldsv) --- .github/scripts/assemble.sh | 1 + .github/scripts/clean-dev-files.sh | 2 - .github/scripts/fetch-all.sh | 12 +++++- .github/workflows/release-build.yml | 59 ++++++++++++++++++----------- .github/workflows/release-tag.yml | 2 +- 5 files changed, 49 insertions(+), 27 deletions(-) diff --git a/.github/scripts/assemble.sh b/.github/scripts/assemble.sh index d4748259981..b222d6d4266 100755 --- a/.github/scripts/assemble.sh +++ b/.github/scripts/assemble.sh @@ -44,6 +44,7 @@ fi # Final cleanup find "$OUTPUT" -name .git -exec rm -rf {} + 2>/dev/null || true +find "$OUTPUT" -name "*.map.license" -delete 2>/dev/null || true rm -f "$OUTPUT/config/config.php" rm -rf "$OUTPUT/data" diff --git a/.github/scripts/clean-dev-files.sh b/.github/scripts/clean-dev-files.sh index b2a106a45be..7f4c3d06b74 100755 --- a/.github/scripts/clean-dev-files.sh +++ b/.github/scripts/clean-dev-files.sh @@ -59,5 +59,3 @@ done # Catch-all: any *.config.{js,ts,mjs,cjs} is a dev config find . -maxdepth 1 \( -name "*.config.js" -o -name "*.config.ts" -o -name "*.config.mjs" -o -name "*.config.cjs" \) -delete 2>/dev/null || true -# Remove .map.license files (REUSE companions of sourcemaps, not needed in release) -find . -name "*.map.license" -delete 2>/dev/null || true diff --git a/.github/scripts/fetch-all.sh b/.github/scripts/fetch-all.sh index 109e76a40ff..6a7c0d97807 100755 --- a/.github/scripts/fetch-all.sh +++ b/.github/scripts/fetch-all.sh @@ -29,9 +29,17 @@ clone() { clone "nextcloud/server" "$REF" "$OUTPUT/server" git -C "$OUTPUT/server" rev-parse HEAD > "$OUTPUT/server/release-commit-hash" -# 3rdparty (replace server's submodule) +# 3rdparty: use the exact submodule commit from the server rm -rf "$OUTPUT/server/3rdparty" -clone "nextcloud/3rdparty" "$REF" "$OUTPUT/3rdparty" +SUBMODULE_SHA=$(git -C "$OUTPUT/server" ls-tree HEAD 3rdparty | awk '{print $3}') +if [ -n "$SUBMODULE_SHA" ]; then + echo "3rdparty submodule points to $SUBMODULE_SHA" + git clone "https://github.com/nextcloud/3rdparty.git" "$OUTPUT/3rdparty" -q + git -C "$OUTPUT/3rdparty" checkout "$SUBMODULE_SHA" -q +else + echo "Warning: could not read submodule, using $REF" + clone "nextcloud/3rdparty" "$REF" "$OUTPUT/3rdparty" +fi rm -rf "$OUTPUT/3rdparty/.git" "$OUTPUT/3rdparty/.github" "$OUTPUT/3rdparty/.gitignore" "$OUTPUT/3rdparty/README.md" # Updater (only 2 files needed) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 6f694e3c24c..84d50441d57 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -46,7 +46,7 @@ jobs: require: write - name: Checkout config - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Parse version id: parse @@ -172,7 +172,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout server - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: nextcloud/server ref: ${{ needs.init.outputs.ref }} @@ -181,7 +181,7 @@ jobs: run: git rev-parse HEAD > release-commit-hash - name: Checkout scripts - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: _scripts sparse-checkout: .github/scripts @@ -190,7 +190,7 @@ jobs: run: bash _scripts/.github/scripts/clean-server-dev-files.sh && rm -rf _scripts - name: Upload - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: server path: . @@ -198,20 +198,35 @@ jobs: retention-days: 1 fetch-3rdparty: - needs: init + needs: [init, fetch-server] runs-on: ubuntu-latest steps: + - name: Get submodule ref from server + id: submodule + run: | + # Read the exact 3rdparty commit the server submodule points to + REF="${{ needs.init.outputs.ref }}" + SHA=$(curl -sf "https://api.github.com/repos/nextcloud/server/contents/3rdparty?ref=${REF}" \ + | jq -r '.sha') + if [ -n "$SHA" ] && [ "$SHA" != "null" ]; then + echo "ref=$SHA" >> "$GITHUB_OUTPUT" + echo "::notice::3rdparty submodule points to $SHA" + else + echo "ref=$REF" >> "$GITHUB_OUTPUT" + echo "::warning::Could not read submodule SHA, falling back to $REF" + fi + - name: Checkout 3rdparty - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: nextcloud/3rdparty - ref: ${{ needs.init.outputs.ref }} + ref: ${{ steps.submodule.outputs.ref }} - name: Clean run: rm -rf .git .github .gitignore README.md - name: Upload - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: 3rdparty path: . @@ -227,7 +242,7 @@ jobs: steps: - name: Checkout ${{ matrix.id }} - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ matrix.repo }} ref: ${{ matrix.branch }} @@ -254,7 +269,7 @@ jobs: - name: Set up PHP if: steps.check_composer.outputs.found == 'true' - uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2.32.0 + uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # v2.37.1 with: php-version: ${{ needs.init.outputs.php_version }} tools: composer @@ -269,7 +284,7 @@ jobs: run: rm -rf .git - name: Upload - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ matrix.id }} path: . @@ -280,11 +295,11 @@ jobs: needs: init runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: nextcloud/updater ref: ${{ needs.init.outputs.ref }} - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: updater path: | @@ -296,12 +311,12 @@ jobs: needs: init runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: nextcloud/example-files ref: ${{ needs.init.outputs.ref }} - run: rm -rf .git - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: example-files path: . @@ -314,7 +329,7 @@ jobs: continue-on-error: true steps: - name: Checkout documentation (gh-pages) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: nextcloud/documentation ref: gh-pages @@ -341,7 +356,7 @@ jobs: find . -name "Nextcloud_User_Manual.pdf" -exec cp -a {} /tmp/docs/ \; 2>/dev/null - name: Upload docs - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: docs path: /tmp/docs/ @@ -355,15 +370,15 @@ jobs: steps: - name: Checkout config and scripts - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: _config - name: Download all artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - name: Set up PHP - uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2.32.0 + uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # v2.37.1 with: php-version: ${{ needs.init.outputs.php_version }} @@ -437,7 +452,7 @@ jobs: rm -rf _config - name: Upload build artifacts - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: release path: releases/ @@ -449,7 +464,7 @@ jobs: steps: - name: Download workflow build - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: release path: /tmp/new-release diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 08a7c588e56..31da5cd8577 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -43,7 +43,7 @@ jobs: require: write - name: Checkout config - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false From 6d17e20894063e935c04e7c621b17b9b6a18ca75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Tue, 2 Jun 2026 07:35:43 +0200 Subject: [PATCH 09/15] feat: add stable30 and stable31 configs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- stable30.json | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++ stable31.json | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 stable30.json create mode 100644 stable31.json diff --git a/stable30.json b/stable30.json new file mode 100644 index 00000000000..69d9b5aeeee --- /dev/null +++ b/stable30.json @@ -0,0 +1,95 @@ +[ + { + "id": "activity", + "repo": "nextcloud/activity" + }, + { + "id": "circles", + "repo": "nextcloud/circles" + }, + { + "id": "files_downloadlimit", + "repo": "nextcloud/files_downloadlimit" + }, + { + "id": "files_pdfviewer", + "repo": "nextcloud/files_pdfviewer" + }, + { + "id": "firstrunwizard", + "repo": "nextcloud/firstrunwizard" + }, + { + "id": "logreader", + "repo": "nextcloud/logreader" + }, + { + "id": "nextcloud_announcements", + "repo": "nextcloud/nextcloud_announcements" + }, + { + "id": "notifications", + "repo": "nextcloud/notifications" + }, + { + "id": "password_policy", + "repo": "nextcloud/password_policy" + }, + { + "id": "photos", + "repo": "nextcloud/photos" + }, + { + "id": "privacy", + "repo": "nextcloud/privacy" + }, + { + "id": "recommendations", + "repo": "nextcloud/recommendations" + }, + { + "id": "serverinfo", + "repo": "nextcloud/serverinfo" + }, + { + "id": "survey_client", + "repo": "nextcloud/survey_client" + }, + { + "id": "text", + "repo": "nextcloud/text" + }, + { + "id": "viewer", + "repo": "nextcloud/viewer" + }, + { + "id": "bruteforcesettings", + "repo": "nextcloud/bruteforcesettings" + }, + { + "id": "related_resources", + "repo": "nextcloud/related_resources" + }, + { + "id": "suspicious_login", + "repo": "nextcloud/suspicious_login", + "composer_args": "--no-dev -a --quiet --no-scripts" + }, + { + "id": "twofactor_totp", + "repo": "nextcloud/twofactor_totp" + }, + { + "id": "twofactor_nextcloud_notification", + "repo": "nextcloud/twofactor_nextcloud_notification" + }, + { + "id": "app_api", + "repo": "nextcloud/app_api" + }, + { + "id": "support", + "repo": "nextcloud-gmbh/support" + } +] diff --git a/stable31.json b/stable31.json new file mode 100644 index 00000000000..69d9b5aeeee --- /dev/null +++ b/stable31.json @@ -0,0 +1,95 @@ +[ + { + "id": "activity", + "repo": "nextcloud/activity" + }, + { + "id": "circles", + "repo": "nextcloud/circles" + }, + { + "id": "files_downloadlimit", + "repo": "nextcloud/files_downloadlimit" + }, + { + "id": "files_pdfviewer", + "repo": "nextcloud/files_pdfviewer" + }, + { + "id": "firstrunwizard", + "repo": "nextcloud/firstrunwizard" + }, + { + "id": "logreader", + "repo": "nextcloud/logreader" + }, + { + "id": "nextcloud_announcements", + "repo": "nextcloud/nextcloud_announcements" + }, + { + "id": "notifications", + "repo": "nextcloud/notifications" + }, + { + "id": "password_policy", + "repo": "nextcloud/password_policy" + }, + { + "id": "photos", + "repo": "nextcloud/photos" + }, + { + "id": "privacy", + "repo": "nextcloud/privacy" + }, + { + "id": "recommendations", + "repo": "nextcloud/recommendations" + }, + { + "id": "serverinfo", + "repo": "nextcloud/serverinfo" + }, + { + "id": "survey_client", + "repo": "nextcloud/survey_client" + }, + { + "id": "text", + "repo": "nextcloud/text" + }, + { + "id": "viewer", + "repo": "nextcloud/viewer" + }, + { + "id": "bruteforcesettings", + "repo": "nextcloud/bruteforcesettings" + }, + { + "id": "related_resources", + "repo": "nextcloud/related_resources" + }, + { + "id": "suspicious_login", + "repo": "nextcloud/suspicious_login", + "composer_args": "--no-dev -a --quiet --no-scripts" + }, + { + "id": "twofactor_totp", + "repo": "nextcloud/twofactor_totp" + }, + { + "id": "twofactor_nextcloud_notification", + "repo": "nextcloud/twofactor_nextcloud_notification" + }, + { + "id": "app_api", + "repo": "nextcloud/app_api" + }, + { + "id": "support", + "repo": "nextcloud-gmbh/support" + } +] From 44f4c3d9c8fcf0e1cc76027e98e2176428d89590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Tue, 2 Jun 2026 07:53:53 +0200 Subject: [PATCH 10/15] feat: add configs for NC22-29 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - stable22-24: 18 apps (+ files_videoplayer, files_rightclick) - stable25-27: 21 apps (+ bruteforcesettings, related_resources, suspicious_login, twofactor_totp) - stable28: 20 apps (files_rightclick removed) - stable29: 21 apps (+ files_downloadlimit) Signed-off-by: John Molakvoæ (skjnldsv) --- stable22.json | 74 +++++++++++++++++++++++++++++++++++++++++++ stable23.json | 74 +++++++++++++++++++++++++++++++++++++++++++ stable24.json | 74 +++++++++++++++++++++++++++++++++++++++++++ stable25.json | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++ stable26.json | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++ stable27.json | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++ stable28.json | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ stable29.json | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 653 insertions(+) create mode 100644 stable22.json create mode 100644 stable23.json create mode 100644 stable24.json create mode 100644 stable25.json create mode 100644 stable26.json create mode 100644 stable27.json create mode 100644 stable28.json create mode 100644 stable29.json diff --git a/stable22.json b/stable22.json new file mode 100644 index 00000000000..7e8b424bc2c --- /dev/null +++ b/stable22.json @@ -0,0 +1,74 @@ +[ + { + "id": "activity", + "repo": "nextcloud/activity" + }, + { + "id": "circles", + "repo": "nextcloud/circles" + }, + { + "id": "files_pdfviewer", + "repo": "nextcloud/files_pdfviewer" + }, + { + "id": "files_rightclick", + "repo": "nextcloud/files_rightclick" + }, + { + "id": "files_videoplayer", + "repo": "nextcloud/files_videoplayer" + }, + { + "id": "firstrunwizard", + "repo": "nextcloud/firstrunwizard" + }, + { + "id": "logreader", + "repo": "nextcloud/logreader" + }, + { + "id": "nextcloud_announcements", + "repo": "nextcloud/nextcloud_announcements" + }, + { + "id": "notifications", + "repo": "nextcloud/notifications" + }, + { + "id": "password_policy", + "repo": "nextcloud/password_policy" + }, + { + "id": "photos", + "repo": "nextcloud/photos" + }, + { + "id": "privacy", + "repo": "nextcloud/privacy" + }, + { + "id": "recommendations", + "repo": "nextcloud/recommendations" + }, + { + "id": "serverinfo", + "repo": "nextcloud/serverinfo" + }, + { + "id": "support", + "repo": "nextcloud-gmbh/support" + }, + { + "id": "survey_client", + "repo": "nextcloud/survey_client" + }, + { + "id": "text", + "repo": "nextcloud/text" + }, + { + "id": "viewer", + "repo": "nextcloud/viewer" + } +] diff --git a/stable23.json b/stable23.json new file mode 100644 index 00000000000..7e8b424bc2c --- /dev/null +++ b/stable23.json @@ -0,0 +1,74 @@ +[ + { + "id": "activity", + "repo": "nextcloud/activity" + }, + { + "id": "circles", + "repo": "nextcloud/circles" + }, + { + "id": "files_pdfviewer", + "repo": "nextcloud/files_pdfviewer" + }, + { + "id": "files_rightclick", + "repo": "nextcloud/files_rightclick" + }, + { + "id": "files_videoplayer", + "repo": "nextcloud/files_videoplayer" + }, + { + "id": "firstrunwizard", + "repo": "nextcloud/firstrunwizard" + }, + { + "id": "logreader", + "repo": "nextcloud/logreader" + }, + { + "id": "nextcloud_announcements", + "repo": "nextcloud/nextcloud_announcements" + }, + { + "id": "notifications", + "repo": "nextcloud/notifications" + }, + { + "id": "password_policy", + "repo": "nextcloud/password_policy" + }, + { + "id": "photos", + "repo": "nextcloud/photos" + }, + { + "id": "privacy", + "repo": "nextcloud/privacy" + }, + { + "id": "recommendations", + "repo": "nextcloud/recommendations" + }, + { + "id": "serverinfo", + "repo": "nextcloud/serverinfo" + }, + { + "id": "support", + "repo": "nextcloud-gmbh/support" + }, + { + "id": "survey_client", + "repo": "nextcloud/survey_client" + }, + { + "id": "text", + "repo": "nextcloud/text" + }, + { + "id": "viewer", + "repo": "nextcloud/viewer" + } +] diff --git a/stable24.json b/stable24.json new file mode 100644 index 00000000000..7e8b424bc2c --- /dev/null +++ b/stable24.json @@ -0,0 +1,74 @@ +[ + { + "id": "activity", + "repo": "nextcloud/activity" + }, + { + "id": "circles", + "repo": "nextcloud/circles" + }, + { + "id": "files_pdfviewer", + "repo": "nextcloud/files_pdfviewer" + }, + { + "id": "files_rightclick", + "repo": "nextcloud/files_rightclick" + }, + { + "id": "files_videoplayer", + "repo": "nextcloud/files_videoplayer" + }, + { + "id": "firstrunwizard", + "repo": "nextcloud/firstrunwizard" + }, + { + "id": "logreader", + "repo": "nextcloud/logreader" + }, + { + "id": "nextcloud_announcements", + "repo": "nextcloud/nextcloud_announcements" + }, + { + "id": "notifications", + "repo": "nextcloud/notifications" + }, + { + "id": "password_policy", + "repo": "nextcloud/password_policy" + }, + { + "id": "photos", + "repo": "nextcloud/photos" + }, + { + "id": "privacy", + "repo": "nextcloud/privacy" + }, + { + "id": "recommendations", + "repo": "nextcloud/recommendations" + }, + { + "id": "serverinfo", + "repo": "nextcloud/serverinfo" + }, + { + "id": "support", + "repo": "nextcloud-gmbh/support" + }, + { + "id": "survey_client", + "repo": "nextcloud/survey_client" + }, + { + "id": "text", + "repo": "nextcloud/text" + }, + { + "id": "viewer", + "repo": "nextcloud/viewer" + } +] diff --git a/stable25.json b/stable25.json new file mode 100644 index 00000000000..fc8e24d67fc --- /dev/null +++ b/stable25.json @@ -0,0 +1,87 @@ +[ + { + "id": "activity", + "repo": "nextcloud/activity" + }, + { + "id": "bruteforcesettings", + "repo": "nextcloud/bruteforcesettings" + }, + { + "id": "circles", + "repo": "nextcloud/circles" + }, + { + "id": "files_pdfviewer", + "repo": "nextcloud/files_pdfviewer" + }, + { + "id": "files_rightclick", + "repo": "nextcloud/files_rightclick" + }, + { + "id": "firstrunwizard", + "repo": "nextcloud/firstrunwizard" + }, + { + "id": "logreader", + "repo": "nextcloud/logreader" + }, + { + "id": "nextcloud_announcements", + "repo": "nextcloud/nextcloud_announcements" + }, + { + "id": "notifications", + "repo": "nextcloud/notifications" + }, + { + "id": "password_policy", + "repo": "nextcloud/password_policy" + }, + { + "id": "photos", + "repo": "nextcloud/photos" + }, + { + "id": "privacy", + "repo": "nextcloud/privacy" + }, + { + "id": "recommendations", + "repo": "nextcloud/recommendations" + }, + { + "id": "related_resources", + "repo": "nextcloud/related_resources" + }, + { + "id": "serverinfo", + "repo": "nextcloud/serverinfo" + }, + { + "id": "support", + "repo": "nextcloud-gmbh/support" + }, + { + "id": "survey_client", + "repo": "nextcloud/survey_client" + }, + { + "id": "suspicious_login", + "repo": "nextcloud/suspicious_login", + "composer_args": "--no-dev -a --quiet --no-scripts" + }, + { + "id": "text", + "repo": "nextcloud/text" + }, + { + "id": "twofactor_totp", + "repo": "nextcloud/twofactor_totp" + }, + { + "id": "viewer", + "repo": "nextcloud/viewer" + } +] diff --git a/stable26.json b/stable26.json new file mode 100644 index 00000000000..fc8e24d67fc --- /dev/null +++ b/stable26.json @@ -0,0 +1,87 @@ +[ + { + "id": "activity", + "repo": "nextcloud/activity" + }, + { + "id": "bruteforcesettings", + "repo": "nextcloud/bruteforcesettings" + }, + { + "id": "circles", + "repo": "nextcloud/circles" + }, + { + "id": "files_pdfviewer", + "repo": "nextcloud/files_pdfviewer" + }, + { + "id": "files_rightclick", + "repo": "nextcloud/files_rightclick" + }, + { + "id": "firstrunwizard", + "repo": "nextcloud/firstrunwizard" + }, + { + "id": "logreader", + "repo": "nextcloud/logreader" + }, + { + "id": "nextcloud_announcements", + "repo": "nextcloud/nextcloud_announcements" + }, + { + "id": "notifications", + "repo": "nextcloud/notifications" + }, + { + "id": "password_policy", + "repo": "nextcloud/password_policy" + }, + { + "id": "photos", + "repo": "nextcloud/photos" + }, + { + "id": "privacy", + "repo": "nextcloud/privacy" + }, + { + "id": "recommendations", + "repo": "nextcloud/recommendations" + }, + { + "id": "related_resources", + "repo": "nextcloud/related_resources" + }, + { + "id": "serverinfo", + "repo": "nextcloud/serverinfo" + }, + { + "id": "support", + "repo": "nextcloud-gmbh/support" + }, + { + "id": "survey_client", + "repo": "nextcloud/survey_client" + }, + { + "id": "suspicious_login", + "repo": "nextcloud/suspicious_login", + "composer_args": "--no-dev -a --quiet --no-scripts" + }, + { + "id": "text", + "repo": "nextcloud/text" + }, + { + "id": "twofactor_totp", + "repo": "nextcloud/twofactor_totp" + }, + { + "id": "viewer", + "repo": "nextcloud/viewer" + } +] diff --git a/stable27.json b/stable27.json new file mode 100644 index 00000000000..fc8e24d67fc --- /dev/null +++ b/stable27.json @@ -0,0 +1,87 @@ +[ + { + "id": "activity", + "repo": "nextcloud/activity" + }, + { + "id": "bruteforcesettings", + "repo": "nextcloud/bruteforcesettings" + }, + { + "id": "circles", + "repo": "nextcloud/circles" + }, + { + "id": "files_pdfviewer", + "repo": "nextcloud/files_pdfviewer" + }, + { + "id": "files_rightclick", + "repo": "nextcloud/files_rightclick" + }, + { + "id": "firstrunwizard", + "repo": "nextcloud/firstrunwizard" + }, + { + "id": "logreader", + "repo": "nextcloud/logreader" + }, + { + "id": "nextcloud_announcements", + "repo": "nextcloud/nextcloud_announcements" + }, + { + "id": "notifications", + "repo": "nextcloud/notifications" + }, + { + "id": "password_policy", + "repo": "nextcloud/password_policy" + }, + { + "id": "photos", + "repo": "nextcloud/photos" + }, + { + "id": "privacy", + "repo": "nextcloud/privacy" + }, + { + "id": "recommendations", + "repo": "nextcloud/recommendations" + }, + { + "id": "related_resources", + "repo": "nextcloud/related_resources" + }, + { + "id": "serverinfo", + "repo": "nextcloud/serverinfo" + }, + { + "id": "support", + "repo": "nextcloud-gmbh/support" + }, + { + "id": "survey_client", + "repo": "nextcloud/survey_client" + }, + { + "id": "suspicious_login", + "repo": "nextcloud/suspicious_login", + "composer_args": "--no-dev -a --quiet --no-scripts" + }, + { + "id": "text", + "repo": "nextcloud/text" + }, + { + "id": "twofactor_totp", + "repo": "nextcloud/twofactor_totp" + }, + { + "id": "viewer", + "repo": "nextcloud/viewer" + } +] diff --git a/stable28.json b/stable28.json new file mode 100644 index 00000000000..fcbe69113b8 --- /dev/null +++ b/stable28.json @@ -0,0 +1,83 @@ +[ + { + "id": "activity", + "repo": "nextcloud/activity" + }, + { + "id": "bruteforcesettings", + "repo": "nextcloud/bruteforcesettings" + }, + { + "id": "circles", + "repo": "nextcloud/circles" + }, + { + "id": "files_pdfviewer", + "repo": "nextcloud/files_pdfviewer" + }, + { + "id": "firstrunwizard", + "repo": "nextcloud/firstrunwizard" + }, + { + "id": "logreader", + "repo": "nextcloud/logreader" + }, + { + "id": "nextcloud_announcements", + "repo": "nextcloud/nextcloud_announcements" + }, + { + "id": "notifications", + "repo": "nextcloud/notifications" + }, + { + "id": "password_policy", + "repo": "nextcloud/password_policy" + }, + { + "id": "photos", + "repo": "nextcloud/photos" + }, + { + "id": "privacy", + "repo": "nextcloud/privacy" + }, + { + "id": "recommendations", + "repo": "nextcloud/recommendations" + }, + { + "id": "related_resources", + "repo": "nextcloud/related_resources" + }, + { + "id": "serverinfo", + "repo": "nextcloud/serverinfo" + }, + { + "id": "support", + "repo": "nextcloud-gmbh/support" + }, + { + "id": "survey_client", + "repo": "nextcloud/survey_client" + }, + { + "id": "suspicious_login", + "repo": "nextcloud/suspicious_login", + "composer_args": "--no-dev -a --quiet --no-scripts" + }, + { + "id": "text", + "repo": "nextcloud/text" + }, + { + "id": "twofactor_totp", + "repo": "nextcloud/twofactor_totp" + }, + { + "id": "viewer", + "repo": "nextcloud/viewer" + } +] diff --git a/stable29.json b/stable29.json new file mode 100644 index 00000000000..c45003709db --- /dev/null +++ b/stable29.json @@ -0,0 +1,87 @@ +[ + { + "id": "activity", + "repo": "nextcloud/activity" + }, + { + "id": "bruteforcesettings", + "repo": "nextcloud/bruteforcesettings" + }, + { + "id": "circles", + "repo": "nextcloud/circles" + }, + { + "id": "files_downloadlimit", + "repo": "nextcloud/files_downloadlimit" + }, + { + "id": "files_pdfviewer", + "repo": "nextcloud/files_pdfviewer" + }, + { + "id": "firstrunwizard", + "repo": "nextcloud/firstrunwizard" + }, + { + "id": "logreader", + "repo": "nextcloud/logreader" + }, + { + "id": "nextcloud_announcements", + "repo": "nextcloud/nextcloud_announcements" + }, + { + "id": "notifications", + "repo": "nextcloud/notifications" + }, + { + "id": "password_policy", + "repo": "nextcloud/password_policy" + }, + { + "id": "photos", + "repo": "nextcloud/photos" + }, + { + "id": "privacy", + "repo": "nextcloud/privacy" + }, + { + "id": "recommendations", + "repo": "nextcloud/recommendations" + }, + { + "id": "related_resources", + "repo": "nextcloud/related_resources" + }, + { + "id": "serverinfo", + "repo": "nextcloud/serverinfo" + }, + { + "id": "support", + "repo": "nextcloud-gmbh/support" + }, + { + "id": "survey_client", + "repo": "nextcloud/survey_client" + }, + { + "id": "suspicious_login", + "repo": "nextcloud/suspicious_login", + "composer_args": "--no-dev -a --quiet --no-scripts" + }, + { + "id": "text", + "repo": "nextcloud/text" + }, + { + "id": "twofactor_totp", + "repo": "nextcloud/twofactor_totp" + }, + { + "id": "viewer", + "repo": "nextcloud/viewer" + } +] From 2687c13e990b66a62224d078b5a22d5eff35e7a2 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Tue, 2 Jun 2026 10:19:59 +0200 Subject: [PATCH 11/15] fix: compare fallback to download server + tagger bugfixes Compare step now tries GitHub release assets first, then falls back to download.nextcloud.com when no archive is attached to the release. Also fixes: - Subshell variable scoping: pipe-to-while lost counters, so the tagger never reported failures. Use here-string instead. - Master config selection: use master.json directly instead of guessing via ls stable*.json. - fetch-3rdparty: remove unnecessary serial dependency on fetch-server (only uses API call with init outputs). - Assemble guard: check all critical dependencies (3rdparty, updater, example-files), not just server+apps. Signed-off-by: skjnldsv --- .github/workflows/release-build.yml | 36 ++++++++++++++++++++++------- .github/workflows/release-tag.yml | 6 ++--- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 84d50441d57..67e42fbb5ff 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -198,7 +198,7 @@ jobs: retention-days: 1 fetch-3rdparty: - needs: [init, fetch-server] + needs: init runs-on: ubuntu-latest steps: - name: Get submodule ref from server @@ -364,7 +364,13 @@ jobs: assemble: needs: [init, fetch-server, fetch-3rdparty, fetch-apps, fetch-updater, fetch-example-files, fetch-docs] - if: always() && needs.fetch-server.result == 'success' && needs.fetch-apps.result == 'success' + if: >- + always() + && needs.fetch-server.result == 'success' + && needs.fetch-3rdparty.result == 'success' + && needs.fetch-apps.result == 'success' + && needs.fetch-updater.result == 'success' + && needs.fetch-example-files.result == 'success' runs-on: ubuntu-latest timeout-minutes: 30 @@ -469,23 +475,34 @@ jobs: name: release path: /tmp/new-release - - name: Download release script assets - # Downloads the tar.bz2 attached by the release script to this same release. - # Both builds ran from the same branch HEAD, so diffs indicate real issues. + - name: Download existing release archive + # Try GitHub release assets first (attached by the release script), + # then fall back to the public download server. id: download env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | TAG="${{ needs.init.outputs.tag }}" VERSION="${{ needs.init.outputs.version }}" + ARCHIVE="nextcloud-${VERSION}.tar.bz2" mkdir -p /tmp/existing-release + + # 1. Try the GitHub release on this repo if gh release download "$TAG" \ --repo "${{ github.repository }}" \ - --pattern "nextcloud-${VERSION}.tar.bz2" \ + --pattern "$ARCHIVE" \ --dir /tmp/existing-release 2>/dev/null; then + echo "source=github-release" >> "$GITHUB_OUTPUT" echo "found=true" >> "$GITHUB_OUTPUT" + echo "::notice::Downloaded $ARCHIVE from GitHub release $TAG" + # 2. Fall back to the public download server + elif curl -fsSL -o "/tmp/existing-release/$ARCHIVE" \ + "https://download.nextcloud.com/server/releases/$ARCHIVE" 2>/dev/null; then + echo "source=download-server" >> "$GITHUB_OUTPUT" + echo "found=true" >> "$GITHUB_OUTPUT" + echo "::notice::Downloaded $ARCHIVE from download.nextcloud.com" else - echo "::warning::No tar.bz2 attached to $TAG yet" + echo "::warning::No existing archive found (GitHub release or download server)" echo "found=false" >> "$GITHUB_OUTPUT" fi @@ -520,11 +537,14 @@ jobs: UNEXPECTED_EXTRA=$(grep -v -E "$KNOWN_PATTERN" /tmp/only-in-new.txt | wc -l) { + SOURCE="${{ steps.download.outputs.source }}" echo "## Build Comparison: ${{ needs.init.outputs.tag }}" echo "" + echo "Reference archive from: **${SOURCE}**" + echo "" echo "| | Files |" echo "|---|---|" - echo "| Release script | ${EXISTING_COUNT} |" + echo "| Existing release | ${EXISTING_COUNT} |" echo "| Workflow build | ${NEW_COUNT} |" echo "| Only in release script | ${MISSING} (${UNEXPECTED_MISSING} unexpected) |" echo "| Only in workflow | ${EXTRA} (${UNEXPECTED_EXTRA} unexpected) |" diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 31da5cd8577..b21f0e3bc31 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -69,7 +69,7 @@ jobs: # Find config file if [ "$BRANCH" = "master" ]; then - CONFIG=$(ls stable*.json 2>/dev/null | sort -V | tail -1) + CONFIG="master.json" else CONFIG="stable${MAJOR}.json" fi @@ -111,7 +111,7 @@ jobs: echo "|---|---|---|---|" } >> "$GITHUB_STEP_SUMMARY" - echo "$REPOS" | while read -r repo; do + while read -r repo; do [ -z "$repo" ] && continue TOTAL=$((TOTAL + 1)) @@ -166,7 +166,7 @@ jobs: echo "| \`$repo\` | $REPO_BRANCH | **failed** | - |" >> "$GITHUB_STEP_SUMMARY" FAILED=$((FAILED + 1)) fi - done + done <<< "$REPOS" { echo "" From b6f58230e2494fa9e3a537698dbe0329ca0536c2 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Tue, 2 Jun 2026 10:27:32 +0200 Subject: [PATCH 12/15] fix: clone 3rdparty at same ref as server Match the release script behavior: clone 3rdparty at the same branch/tag as server instead of looking up the submodule SHA. The release script does `getrepo('3rdparty', $branch)` which clones at branch HEAD, not at the submodule pointer. Using the submodule SHA would produce different content than the release script. Applies to both the workflow (fetch-3rdparty job) and the standalone fetch-all.sh script. Signed-off-by: skjnldsv --- .github/scripts/fetch-all.sh | 12 ++---------- .github/workflows/release-build.yml | 17 +---------------- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/.github/scripts/fetch-all.sh b/.github/scripts/fetch-all.sh index 6a7c0d97807..5a2fc86a7cc 100755 --- a/.github/scripts/fetch-all.sh +++ b/.github/scripts/fetch-all.sh @@ -29,17 +29,9 @@ clone() { clone "nextcloud/server" "$REF" "$OUTPUT/server" git -C "$OUTPUT/server" rev-parse HEAD > "$OUTPUT/server/release-commit-hash" -# 3rdparty: use the exact submodule commit from the server +# 3rdparty: clone at the same ref as server (matches the release script) rm -rf "$OUTPUT/server/3rdparty" -SUBMODULE_SHA=$(git -C "$OUTPUT/server" ls-tree HEAD 3rdparty | awk '{print $3}') -if [ -n "$SUBMODULE_SHA" ]; then - echo "3rdparty submodule points to $SUBMODULE_SHA" - git clone "https://github.com/nextcloud/3rdparty.git" "$OUTPUT/3rdparty" -q - git -C "$OUTPUT/3rdparty" checkout "$SUBMODULE_SHA" -q -else - echo "Warning: could not read submodule, using $REF" - clone "nextcloud/3rdparty" "$REF" "$OUTPUT/3rdparty" -fi +clone "nextcloud/3rdparty" "$REF" "$OUTPUT/3rdparty" rm -rf "$OUTPUT/3rdparty/.git" "$OUTPUT/3rdparty/.github" "$OUTPUT/3rdparty/.gitignore" "$OUTPUT/3rdparty/README.md" # Updater (only 2 files needed) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 67e42fbb5ff..88492444067 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -201,26 +201,11 @@ jobs: needs: init runs-on: ubuntu-latest steps: - - name: Get submodule ref from server - id: submodule - run: | - # Read the exact 3rdparty commit the server submodule points to - REF="${{ needs.init.outputs.ref }}" - SHA=$(curl -sf "https://api.github.com/repos/nextcloud/server/contents/3rdparty?ref=${REF}" \ - | jq -r '.sha') - if [ -n "$SHA" ] && [ "$SHA" != "null" ]; then - echo "ref=$SHA" >> "$GITHUB_OUTPUT" - echo "::notice::3rdparty submodule points to $SHA" - else - echo "ref=$REF" >> "$GITHUB_OUTPUT" - echo "::warning::Could not read submodule SHA, falling back to $REF" - fi - - name: Checkout 3rdparty uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: nextcloud/3rdparty - ref: ${{ steps.submodule.outputs.ref }} + ref: ${{ needs.init.outputs.ref }} - name: Clean run: rm -rf .git .github .gitignore README.md From ed90bed48df3977b383cda7d328e020c39b58766 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Tue, 2 Jun 2026 10:42:00 +0200 Subject: [PATCH 13/15] fix: sync dev file patterns with release script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Sync clean-dev-files.sh with release script's getDataToBeRemovedFromAppFolders: use the same cross-product of JS config base names × extensions instead of a hand-picked list. - Add phpDocumentor.sh and testConfiguration.json (circles leftovers). - Mark vendor/composer autoloader diffs as known in compare step (different PHP/composer version produces different generated files). Signed-off-by: skjnldsv --- .github/scripts/clean-dev-files.sh | 44 ++++++++++++++++++----------- .github/workflows/release-build.yml | 7 +++-- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/.github/scripts/clean-dev-files.sh b/.github/scripts/clean-dev-files.sh index 7f4c3d06b74..a71e568c19f 100755 --- a/.github/scripts/clean-dev-files.sh +++ b/.github/scripts/clean-dev-files.sh @@ -28,25 +28,22 @@ if [ -f ".nextcloudignore" ]; then done < .nextcloudignore fi -# Remove dev file patterns (based on getDataToBeRemovedFromAppFolders) +# Remove dev file patterns (synced with release script's getDataToBeRemovedFromAppFolders) DEV_PATTERNS=( - .babelrc .babelrc.js .codecov.yml .devcontainer .drone.yml .editorconfig - .eslintrc.cjs .eslintrc.js .eslintrc.json .eslintignore + .babelrc .codecov.yml .devcontainer .drone.yml .editorconfig .eslintignore .git/ .gitattributes .github .gitignore .git-blame-ignore-revs .jshintrc .l10nignore .lgtm .nextcloudignore .npmignore .noopenapi - .php_cs.dist .php-cs-fixer.dist.php .scrutinizer.yml .stylelintignore - .stylelintrc.js .travis.yml .tx/ - babel.config.js build-js/ build.xml + .php_cs.dist .php-cs-fixer.dist.php .prettierignore .scrutinizer.yml + .stylelintignore .travis.yml .tx/ + build-js/ build.xml check-handlebars-templates.sh codecov.yml compile-handlebars-templates.sh CONTRIBUTING.md - cypress.config.js cypress.config.ts cypress.json cypress/ - issue_template.md jest-raw-loader.js jest.config.js jsconfig.json - krankerl.toml l10n/.gitkeep Makefile postcss.config.js psalm.xml - .prettierignore - README.md rector.php renovate.json screenshots/ src/ - stylelint.config.js stylelint.config.cjs tests/ tsconfig.json - vite.config.js vite.config.ts vitest.config.js vitest.config.ts - webpack.common.js webpack.config.js webpack.dev.js webpack.js webpack.prod.js + cypress.json cypress/ + issue_template.md jest-raw-loader.js jsconfig.json + krankerl.toml l10n/.gitkeep Makefile phpDocumentor.sh psalm.xml + README.md rector.php renovate.json screenshots/ src/ testConfiguration.json + tests/ + webpack.common.js webpack.dev.js webpack.js webpack.prod.js ) for pattern in "${DEV_PATTERNS[@]}"; do @@ -56,6 +53,21 @@ for pattern in "${DEV_PATTERNS[@]}"; do fi done -# Catch-all: any *.config.{js,ts,mjs,cjs} is a dev config -find . -maxdepth 1 \( -name "*.config.js" -o -name "*.config.ts" -o -name "*.config.mjs" -o -name "*.config.cjs" \) -delete 2>/dev/null || true +# JavaScript config files: cross-product of base names × extensions +# (matches release script's $javascriptConfigs × $suffix loop) +JS_CONFIG_BASES=( + .babelrc .eslintrc .prettierrc .stylelintrc + babel.config cypress.config eslint.config jest.config jsconfig + oxlintrc oxlint.config playwright.config postcss.config prettier.config + rspack.config stylelint.config tsconfig vite.config vitest.config + webpack webpack.config +) +JS_EXTENSIONS=(.json .js .mjs .cjs .ts .mts .cts) + +for base in "${JS_CONFIG_BASES[@]}"; do + for ext in "${JS_EXTENSIONS[@]}"; do + rm -rf "${base}${ext}" + rm -rf "${base}${ext}.license" + done +done diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 88492444067..3ae76f9fd15 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -593,14 +593,15 @@ jobs: if [ "$CONTENT_DIFFS" -gt 0 ]; then # Filter out version.php (expected: different build timestamp) - UNEXPECTED_CONTENT=$(grep "^[<>]" /tmp/hash-diff.txt | sed 's/^[<>] [a-f0-9]* \+//' | sort -u | grep -v "version\.php" | wc -l) + KNOWN_CONTENT='(version\.php)' + UNEXPECTED_CONTENT=$(grep "^[<>]" /tmp/hash-diff.txt | sed 's/^[<>] [a-f0-9]* \+//' | sort -u | grep -v -E "$KNOWN_CONTENT" | wc -l) echo "| Unexpected content diffs | ${UNEXPECTED_CONTENT} |" echo "" if [ "$UNEXPECTED_CONTENT" -gt 0 ]; then echo "
Files with different content (${UNEXPECTED_CONTENT})" echo "" echo '```' - grep "^[<>]" /tmp/hash-diff.txt | sed 's/^[<>] [a-f0-9]* \+//' | sort -u | grep -v "version\.php" + grep "^[<>]" /tmp/hash-diff.txt | sed 's/^[<>] [a-f0-9]* \+//' | sort -u | grep -v -E "$KNOWN_CONTENT" echo '```' echo "
" else @@ -614,7 +615,7 @@ jobs: echo "=== Content diffs (excluding version.php) ===" if [ "$CONTENT_DIFFS" -gt 0 ]; then - grep "^[<>]" /tmp/hash-diff.txt | sed 's/^[<>] [a-f0-9]* \+//' | sort -u | grep -v "version\.php" || echo "(none)" + grep "^[<>]" /tmp/hash-diff.txt | sed 's/^[<>] [a-f0-9]* \+//' | sort -u | grep -v -E "$KNOWN_CONTENT" || echo "(none)" else echo "(none)" fi From 0583ea6bb449f9e1d8dc3f6677ce888cdd9f0097 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Tue, 2 Jun 2026 10:47:57 +0200 Subject: [PATCH 14/15] fix: use minimum supported PHP version for builds Switch from upper-bound-minus-one to the lower bound from versioncheck.php. Using the minimum supported PHP ensures deterministic autoloader output regardless of what PHP versions are available on the build machine. Signed-off-by: skjnldsv --- .github/workflows/release-build.yml | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 3ae76f9fd15..85bc4a7569f 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -124,17 +124,20 @@ jobs: id: php run: | BRANCH="${{ steps.parse.outputs.branch }}" - # Parse the upper bound from versioncheck.php (e.g. "PHP_VERSION_ID >= 80600") - # 80600 means PHP 8.6+ is rejected, so max supported is 8.5 - MAX_PHP_ID=$(curl -sf "https://raw.githubusercontent.com/nextcloud/server/refs/heads/${BRANCH}/lib/versioncheck.php" \ - | grep -oP 'PHP_VERSION_ID >= \K\d+' | head -1) - - if [ -n "$MAX_PHP_ID" ]; then - # 80600 / 100 = 806, % 100 = 6, - 1 = 5 → PHP 8.5 - MAX_MINOR=$(( (MAX_PHP_ID / 100) % 100 - 1 )) - PHP_VERSION="8.${MAX_MINOR}" + # Use the minimum supported PHP version from versioncheck.php. + # This matches the release script, which tries php8.4→down and picks + # the first that passes occ status (i.e. the lowest available that + # satisfies the minimum). Using the minimum ensures composer generates + # identical autoloader output (platform_check.php, etc.). + MIN_PHP_ID=$(curl -sf "https://raw.githubusercontent.com/nextcloud/server/refs/heads/${BRANCH}/lib/versioncheck.php" \ + | grep -oP 'PHP_VERSION_ID < \K\d+' | head -1) + + if [ -n "$MIN_PHP_ID" ]; then + # 80200 → major=8, minor=2 → PHP 8.2 + MIN_MINOR=$(( (MIN_PHP_ID / 100) % 100 )) + PHP_VERSION="8.${MIN_MINOR}" else - PHP_VERSION="8.3" + PHP_VERSION="8.1" fi echo "version=$PHP_VERSION" >> "$GITHUB_OUTPUT" From 73ea4b5c99e798487836747279577fb0f433ac1b Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Tue, 2 Jun 2026 12:30:04 +0200 Subject: [PATCH 15/15] fix: match release script cleanup behavior - Skip root-prefixed .nextcloudignore patterns: the release script's `git ls-files -ico` silently skips these for tracked files, so composer.json, package.json, docs/ etc. are kept in release archives. Match that behavior instead of being more aggressive. - Stop removing *.map.license in assemble.sh (release script keeps them). - Add vendor-bin/ to DEV_PATTERNS (dev tooling, not runtime). Signed-off-by: skjnldsv --- .github/scripts/assemble.sh | 1 - .github/scripts/clean-dev-files.sh | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/scripts/assemble.sh b/.github/scripts/assemble.sh index b222d6d4266..d4748259981 100755 --- a/.github/scripts/assemble.sh +++ b/.github/scripts/assemble.sh @@ -44,7 +44,6 @@ fi # Final cleanup find "$OUTPUT" -name .git -exec rm -rf {} + 2>/dev/null || true -find "$OUTPUT" -name "*.map.license" -delete 2>/dev/null || true rm -f "$OUTPUT/config/config.php" rm -rf "$OUTPUT/data" diff --git a/.github/scripts/clean-dev-files.sh b/.github/scripts/clean-dev-files.sh index a71e568c19f..fc2c6ac5307 100755 --- a/.github/scripts/clean-dev-files.sh +++ b/.github/scripts/clean-dev-files.sh @@ -10,12 +10,29 @@ set -e DIR="${1:-.}" cd "$DIR" +# Files to preserve even if .nextcloudignore lists them +KEEP_FILES=(composer.json composer.lock package.json package-lock.json) + # Process .nextcloudignore if present if [ -f ".nextcloudignore" ]; then APP_DIR=$(pwd) while IFS= read -r line || [[ -n "$line" ]]; do pattern=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/\r$//') [[ -z "$pattern" || "$pattern" == \#* ]] && continue + + # Check if this pattern targets a preserved file + # e.g. /composer.* would match composer.json — skip the whole pattern + clean="${pattern#/}" + skip=false + for keep in "${KEEP_FILES[@]}"; do + # shellcheck disable=SC2254 + if [[ "$keep" == $clean ]]; then + skip=true + break + fi + done + $skip && continue + if [[ "$pattern" == /* ]]; then pattern="${pattern#/}" # shellcheck disable=SC2086 @@ -42,7 +59,7 @@ DEV_PATTERNS=( issue_template.md jest-raw-loader.js jsconfig.json krankerl.toml l10n/.gitkeep Makefile phpDocumentor.sh psalm.xml README.md rector.php renovate.json screenshots/ src/ testConfiguration.json - tests/ + tests/ vendor-bin/ webpack.common.js webpack.dev.js webpack.js webpack.prod.js )