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/.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-dev-files.sh b/.github/scripts/clean-dev-files.sh new file mode 100755 index 00000000000..fc2c6ac5307 --- /dev/null +++ b/.github/scripts/clean-dev-files.sh @@ -0,0 +1,90 @@ +#!/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" + +# 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 + 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 (synced with release script's getDataToBeRemovedFromAppFolders) +DEV_PATTERNS=( + .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 .prettierignore .scrutinizer.yml + .stylelintignore .travis.yml .tx/ + build-js/ build.xml + check-handlebars-templates.sh codecov.yml compile-handlebars-templates.sh + CONTRIBUTING.md + 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/ vendor-bin/ + webpack.common.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 + +# 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/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..5a2fc86a7cc --- /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: clone at the same ref as server (matches the release script) +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' +> "$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 }}" + # 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.1" + 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + repository: nextcloud/server + ref: ${{ needs.init.outputs.ref }} + + - name: Save commit hash + run: git rev-parse HEAD > release-commit-hash + + - name: Checkout scripts + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + path: _scripts + sparse-checkout: .github/scripts + + - name: Clean dev files + run: bash _scripts/.github/scripts/clean-server-dev-files.sh && rm -rf _scripts + + - name: Upload + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: server + path: . + include-hidden-files: true + retention-days: 1 + + fetch-3rdparty: + needs: init runs-on: ubuntu-latest steps: - - run: echo "This is a stub. Run from the feature branch instead." + - name: Checkout 3rdparty + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.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@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # v2.37.1 + 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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ matrix.id }} + path: . + include-hidden-files: true + retention-days: 1 + + fetch-updater: + needs: init + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + repository: nextcloud/updater + ref: ${{ needs.init.outputs.ref }} + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: updater + path: | + index.php + updater.phar + retention-days: 1 + + fetch-example-files: + needs: init + runs-on: ubuntu-latest + steps: + - 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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + 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-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 + + steps: + - name: Checkout config and scripts + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + path: _config + + - name: Download all artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + + - name: Set up PHP + uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # v2.37.1 + with: + php-version: ${{ needs.init.outputs.php_version }} + + - name: Assemble release + run: | + # 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|release) continue ;; esac + mv "$app_dir" "/tmp/build/apps/$id" + done + + bash _config/.github/scripts/assemble.sh /tmp/build /tmp/nextcloud + rm -rf /tmp/build + + - 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: Update version.php + run: | + export NC=/tmp/nextcloud + bash _config/.github/scripts/update-version-php.sh \ + /tmp/nextcloud "${{ needs.init.outputs.channel }}" "${{ needs.init.outputs.branch }}" + + - name: Sign release + env: + SIGN_KEY: ${{ secrets.SIGN_PRIVATE_KEY }} + SIGN_CERT: ${{ secrets.SIGN_CERTIFICATE }} + run: | + if [ -z "$SIGN_KEY" ]; then + echo "::warning::SIGN_PRIVATE_KEY not set, skipping code signing" + exit 0 + fi + echo "$SIGN_KEY" > /tmp/signing-key.pem + CERT_ARG="" + if [ -n "$SIGN_CERT" ]; then + echo "$SIGN_CERT" > /tmp/signing-cert.pem + CERT_ARG="/tmp/signing-cert.pem" + fi + 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 + if: needs.init.outputs.major >= 30 + run: | + mkdir -p "$GITHUB_WORKSPACE/releases" + bash _config/.github/scripts/generate-metadata.sh \ + /tmp/nextcloud "${{ needs.init.outputs.version }}" "$GITHUB_WORKSPACE/releases" + + - name: Package and checksum + run: | + 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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + 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@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: release + path: /tmp/new-release + + - 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 "$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 existing archive found (GitHub release or download server)" + 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) + + { + SOURCE="${{ steps.download.outputs.source }}" + echo "## Build Comparison: ${{ needs.init.outputs.tag }}" + echo "" + echo "Reference archive from: **${SOURCE}**" + echo "" + echo "| | Files |" + echo "|---|---|" + 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) |" + 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) + 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 -E "$KNOWN_CONTENT" + 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 -E "$KNOWN_CONTENT" || echo "(none)" + else + echo "(none)" + fi diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml new file mode 100644 index 00000000000..b21f0e3bc31 --- /dev/null +++ b/.github/workflows/release-tag.yml @@ -0,0 +1,179 @@ +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT + +name: Tag all repositories +run-name: "Tag ${{ inputs.tag }}" + +on: + 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: + 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.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="master.json" + 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 + tag-only.json at $BRANCH HEAD → $TAG" + + - name: Tag all repositories + env: + 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 + FAILED=0 + + { + echo "## Tag results: $TAG" + echo "" + echo "| Repository | Branch | Status | Commit |" + echo "|---|---|---|---|" + } >> "$GITHUB_STEP_SUMMARY" + + while read -r repo; do + [ -z "$repo" ] && continue + TOTAL=$((TOTAL + 1)) + + # 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" + 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 ... " + + if [ -z "$SHA" ]; then + echo "SKIP (no branch found)" + echo "| \`$repo\` | - | no branch 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 + 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 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)) + else + echo "FAILED" + echo "| \`$repo\` | $REPO_BRANCH | **failed** | - |" >> "$GITHUB_STEP_SUMMARY" + FAILED=$((FAILED + 1)) + fi + done <<< "$REPOS" + + { + 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/.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 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 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/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" + } +] 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" + } +] diff --git a/stable32.json b/stable32.json new file mode 100644 index 00000000000..69d9b5aeeee --- /dev/null +++ b/stable32.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/stable33.json b/stable33.json new file mode 100644 index 00000000000..69d9b5aeeee --- /dev/null +++ b/stable33.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/stable34.json b/stable34.json new file mode 100644 index 00000000000..af5e4054dbf --- /dev/null +++ b/stable34.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/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" +]