diff --git a/.docker/app/Dockerfile b/.docker/app/Dockerfile index 3a94750..b4a4a06 100644 --- a/.docker/app/Dockerfile +++ b/.docker/app/Dockerfile @@ -25,6 +25,7 @@ RUN set -eux; \ FROM ${NEXTCLOUD_SOURCE} AS runtime RUN apt-get update \ + && apt-get upgrade -y \ && apt-get install -y --no-install-recommends \ locales \ poppler-utils \ diff --git a/.env.example b/.env.example index fc4375d..b84d06a 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -NEXTCLOUD_VERSION=34-fpm +NEXTCLOUD_VERSION=35-fpm VIRTUAL_HOST= LETSENCRYPT_HOST= diff --git a/.github/actions/build-and-scan/action.yml b/.github/actions/build-and-scan/action.yml index 02d9955..9128b2b 100644 --- a/.github/actions/build-and-scan/action.yml +++ b/.github/actions/build-and-scan/action.yml @@ -1,13 +1,18 @@ -name: Build, scan, and validate runtime images -description: Build and scan both runtime images, then run app image acceptance tests +name: Build, scan, and validate image +description: Build either a Nextcloud FPM runtime or its independent web image inputs: - nextcloud_version: - description: Nextcloud version passed to the app image build + component: + description: Either app or web required: true + nextcloud_version: + description: Official Nextcloud release tag for app builds (e.g. 35.0.2-fpm) + required: false + default: '' runs: using: composite steps: - name: Setup Bats + if: inputs.component == 'app' uses: bats-core/bats-action@77d6fb60505b4d0d1d73e48bd035b55074bbfb43 # 4.0.0 with: support-install: false @@ -24,6 +29,7 @@ runs: uses: docker/setup-buildx-action@v3 - name: Build app image (linux/amd64) + if: inputs.component == 'app' uses: docker/build-push-action@v6 with: context: .docker/app @@ -32,10 +38,11 @@ runs: build-args: | NEXTCLOUD_BASE_IMAGE=nextcloud:${{ inputs.nextcloud_version }} tags: scan/app:amd64 - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=app-${{ inputs.nextcloud_version }}-amd64 + cache-to: type=gha,mode=max,scope=app-${{ inputs.nextcloud_version }}-amd64 - name: Build app image (linux/arm64) + if: inputs.component == 'app' uses: docker/build-push-action@v6 with: context: .docker/app @@ -44,28 +51,30 @@ runs: build-args: | NEXTCLOUD_BASE_IMAGE=nextcloud:${{ inputs.nextcloud_version }} tags: scan/app:arm64 - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=app-${{ inputs.nextcloud_version }}-arm64 + cache-to: type=gha,mode=max,scope=app-${{ inputs.nextcloud_version }}-arm64 - name: Build web image (linux/amd64) + if: inputs.component == 'web' uses: docker/build-push-action@v6 with: context: .docker/web platforms: linux/amd64 load: true tags: scan/web:amd64 - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=web-amd64 + cache-to: type=gha,mode=max,scope=web-amd64 - name: Build web image (linux/arm64) + if: inputs.component == 'web' uses: docker/build-push-action@v6 with: context: .docker/web platforms: linux/arm64 load: true tags: scan/web:arm64 - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=web-arm64 + cache-to: type=gha,mode=max,scope=web-arm64 - name: Install Trivy uses: aquasecurity/setup-trivy@81e514348e19b6112ce2a7e3ecbafe19c1e1f567 # v0.3.1 @@ -74,22 +83,31 @@ runs: version: v0.75.0 cache: true - - name: Scan runtime images + - name: Scan app image + if: inputs.component == 'app' shell: bash run: | bash scripts/scan-images.sh \ 'app@linux/amd64=scan/app:amd64' \ - 'app@linux/arm64=scan/app:arm64' \ + 'app@linux/arm64=scan/app:arm64' + + - name: Scan web image + if: inputs.component == 'web' + shell: bash + run: | + bash scripts/scan-images.sh \ 'web@linux/amd64=scan/web:amd64' \ 'web@linux/arm64=scan/web:arm64' - name: Runtime acceptance app image (linux/amd64) + if: inputs.component == 'app' shell: bash env: APP_IMAGE: scan/app:amd64 run: bats tests/app-image.bats - name: Runtime acceptance app image (linux/arm64) + if: inputs.component == 'app' shell: bash env: APP_IMAGE: scan/app:arm64 diff --git a/.github/nextcloud-supported.json b/.github/nextcloud-supported.json new file mode 100644 index 0000000..7c73f88 --- /dev/null +++ b/.github/nextcloud-supported.json @@ -0,0 +1,21 @@ +{ + "schema_version": 1, + "source": "https://github.com/nextcloud/server/wiki/Maintenance-and-Release-Schedule", + "versions": [ + { + "major": 33, + "release": "33.0.10", + "eol": "2027-02" + }, + { + "major": 34, + "release": "34.0.5", + "eol": "2027-06" + }, + { + "major": 35, + "release": "35.0.2", + "eol": "2027-09" + } + ] +} diff --git a/.github/workflows/discover-nextcloud-versions.yml b/.github/workflows/discover-nextcloud-versions.yml new file mode 100644 index 0000000..ca6ff2d --- /dev/null +++ b/.github/workflows/discover-nextcloud-versions.yml @@ -0,0 +1,74 @@ +name: Discover maintained Nextcloud versions + +on: + schedule: + - cron: '21 5,17 * * *' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + actions: write + +concurrency: + group: nextcloud-wiki-discovery + cancel-in-progress: false + +jobs: + discover: + runs-on: ubuntu-latest + steps: + - name: Checkout reviewed branch + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Setup Bats + uses: bats-core/bats-action@77d6fb60505b4d0d1d73e48bd035b55074bbfb43 # 4.0.0 + with: + support-install: false + assert-install: false + detik-install: false + file-install: false + + - name: Test wiki parser and publication policy + run: make test-ci-scripts + + - name: ShellCheck version discovery + run: shellcheck scripts/nextcloud-versions.sh + + - name: Discover and verify maintained versions + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: bash scripts/nextcloud-versions.sh discover + + - name: Propose reviewed manifest update + id: pull_request + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + add-paths: .github/nextcloud-supported.json + branch: automation/nextcloud-supported-versions + delete-branch: true + signoff: true + sign-commits: true + commit-message: 'chore(ci): synchronize maintained Nextcloud versions' + title: 'chore(ci): synchronize maintained Nextcloud versions' + body: | + Sync the reviewed Nextcloud version manifest with the upstream + Maintenance and Release Schedule wiki. + + The discovery job requires official stable release artifacts and + amd64/arm64 FPM images. Review version additions/removals, patch + upgrades and EOL transitions before merging. Do not auto-merge. + + The source is the upstream server wiki. This PR does not publish + any Docker images. + labels: dependencies + + # GITHUB_TOKEN pushes do not automatically trigger push/PR workflows. + # workflow_dispatch does trigger; publishing is forbidden off main. + - name: Validate generated PR branch + if: contains(fromJSON('["created","updated"]'), steps.pull_request.outputs.pull-request-operation) + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh workflow run docker-image.yml --repo "$GITHUB_REPOSITORY" --ref automation/nextcloud-supported-versions diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 70f5c4d..584b937 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,4 +1,4 @@ -name: Build and Push Docker Image +name: Build and publish maintained Nextcloud images on: push: @@ -7,153 +7,198 @@ on: pull_request: branches: - main + workflow_dispatch: + schedule: + - cron: '13 4 * * 2' permissions: contents: read +concurrency: + group: nextcloud-stable-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: true + env: - REGISTRY: ghcr.io + APP_IMAGE: ghcr.io/librecodecoop/nextcloud-docker-app + WEB_IMAGE: ghcr.io/librecodecoop/nextcloud-docker-web jobs: - verify: - if: github.event_name == 'pull_request' + script-tests: + name: Test version and publication scripts runs-on: ubuntu-latest - - permissions: - contents: read - steps: - name: Checkout repository uses: actions/checkout@v7 - - - name: Read Nextcloud version - id: nextcloud_version - run: | - version=$(grep -m1 '^NEXTCLOUD_VERSION=' .env.example | cut -d= -f2-) - if [ -z "$version" ]; then - echo "NEXTCLOUD_VERSION is missing from .env.example" >&2 - exit 1 - fi - echo "value=$version" >> "$GITHUB_OUTPUT" - - - name: Build and scan runtime images - uses: ./.github/actions/build-and-scan with: - nextcloud_version: ${{ steps.nextcloud_version.outputs.value }} + persist-credentials: false - - name: Save SARIF reports - uses: actions/upload-artifact@v6 + - name: Setup Bats + uses: bats-core/bats-action@77d6fb60505b4d0d1d73e48bd035b55074bbfb43 # 4.0.0 with: - name: trivy-sarif-reports - path: trivy-results/*.sarif - if-no-files-found: warn + support-install: false + assert-install: false + detik-install: false + file-install: false + + - name: Run offline regression tests + run: make test-ci-scripts + + - name: Test Trivy wrapper + run: bash tests/test-scan-images.sh - publish: - if: github.event_name == 'push' + - name: ShellCheck image publication scripts + run: | + shellcheck scripts/nextcloud-versions.sh scripts/publish-images.sh \ + scripts/publish-manifest.sh scripts/push-architecture.sh \ + scripts/publish-master-manifest.sh scripts/push-master-architecture.sh + + versions: + needs: script-tests + name: Load approved Nextcloud releases runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.approved.outputs.matrix }} + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Validate reviewed manifest and emit matrix + id: approved + run: bash scripts/nextcloud-versions.sh matrix --github-output "$GITHUB_OUTPUT" + + app: + name: app ${{ matrix.tag }} + needs: [script-tests, versions] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.versions.outputs.matrix) }} permissions: contents: read packages: write security-events: write - steps: - name: Checkout repository uses: actions/checkout@v7 + with: + persist-credentials: false - - name: Read Nextcloud version - id: nextcloud_version - run: | - version=$(grep -m1 '^NEXTCLOUD_VERSION=' .env.example | cut -d= -f2-) - if [ -z "$version" ]; then - echo "NEXTCLOUD_VERSION is missing from .env.example" >&2 - exit 1 - fi - echo "value=$version" >> "$GITHUB_OUTPUT" - - - name: Normalize repository name - id: repo_name - run: echo "value=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT" - - - name: Build and scan runtime images + - name: Build, scan, and test Nextcloud app uses: ./.github/actions/build-and-scan with: - nextcloud_version: ${{ steps.nextcloud_version.outputs.value }} + component: app + nextcloud_version: ${{ matrix.release }}-fpm - - name: Upload app amd64 SARIF to GitHub code scanning - if: hashFiles('trivy-results/app-linux-amd64.sarif') != '' + - name: Upload amd64 security findings + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' uses: github/codeql-action/upload-sarif@v3 with: sarif_file: trivy-results/app-linux-amd64.sarif - category: trivy-app-linux-amd64 + category: trivy-app-linux-amd64-${{ matrix.tag }} - - name: Upload app arm64 SARIF to GitHub code scanning - if: hashFiles('trivy-results/app-linux-arm64.sarif') != '' + - name: Upload arm64 security findings + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' uses: github/codeql-action/upload-sarif@v3 with: sarif_file: trivy-results/app-linux-arm64.sarif - category: trivy-app-linux-arm64 + category: trivy-app-linux-arm64-${{ matrix.tag }} + + - name: Save vulnerability reports + uses: actions/upload-artifact@v6 + with: + name: trivy-app-${{ matrix.tag }} + path: trivy-results/*.sarif + if-no-files-found: error + + - name: Log in to GHCR + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + uses: docker/login-action@v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Stage validated architectures + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + run: bash scripts/publish-images.sh stage-app "$APP_IMAGE" "$GITHUB_SHA" "${{ matrix.tag }}" + + web: + name: web runtime + needs: script-tests + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + persist-credentials: false - - name: Upload web amd64 SARIF to GitHub code scanning - if: hashFiles('trivy-results/web-linux-amd64.sarif') != '' + - name: Build and scan web image once + uses: ./.github/actions/build-and-scan + with: + component: web + + - name: Upload amd64 security findings + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' uses: github/codeql-action/upload-sarif@v3 with: sarif_file: trivy-results/web-linux-amd64.sarif category: trivy-web-linux-amd64 - - name: Upload web arm64 SARIF to GitHub code scanning - if: hashFiles('trivy-results/web-linux-arm64.sarif') != '' + - name: Upload arm64 security findings + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' uses: github/codeql-action/upload-sarif@v3 with: sarif_file: trivy-results/web-linux-arm64.sarif category: trivy-web-linux-arm64 - - name: Save SARIF reports + - name: Save vulnerability reports uses: actions/upload-artifact@v6 with: - name: trivy-sarif-reports + name: trivy-web path: trivy-results/*.sarif - if-no-files-found: warn + if-no-files-found: error - - name: Log in to Container Registry + - name: Log in to GHCR + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' uses: docker/login-action@v4.6.0 with: - registry: ${{ env.REGISTRY }} + registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Push scanned architecture images - env: - APP_IMAGE: ${{ env.REGISTRY }}/${{ steps.repo_name.outputs.value }}-app - WEB_IMAGE: ${{ env.REGISTRY }}/${{ steps.repo_name.outputs.value }}-web - IMAGE_VERSION: sha-${{ github.sha }} - run: | - set -euo pipefail - - docker tag scan/app:amd64 "${APP_IMAGE}:${IMAGE_VERSION}-amd64" - docker tag scan/app:arm64 "${APP_IMAGE}:${IMAGE_VERSION}-arm64" - docker tag scan/web:amd64 "${WEB_IMAGE}:${IMAGE_VERSION}-amd64" - docker tag scan/web:arm64 "${WEB_IMAGE}:${IMAGE_VERSION}-arm64" - - docker push "${APP_IMAGE}:${IMAGE_VERSION}-amd64" - docker push "${APP_IMAGE}:${IMAGE_VERSION}-arm64" - docker push "${WEB_IMAGE}:${IMAGE_VERSION}-amd64" - docker push "${WEB_IMAGE}:${IMAGE_VERSION}-arm64" - - - name: Publish multi-platform image tags - env: - APP_IMAGE: ${{ env.REGISTRY }}/${{ steps.repo_name.outputs.value }}-app - WEB_IMAGE: ${{ env.REGISTRY }}/${{ steps.repo_name.outputs.value }}-web - IMAGE_VERSION: sha-${{ github.sha }} - run: | - set -euo pipefail + - name: Stage validated web architectures + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + run: bash scripts/publish-images.sh stage-web "$WEB_IMAGE" "$GITHUB_SHA" - docker buildx imagetools create \ - --tag "${APP_IMAGE}:latest" \ - "${APP_IMAGE}:${IMAGE_VERSION}-amd64" \ - "${APP_IMAGE}:${IMAGE_VERSION}-arm64" + promote: + name: Publish versioned manifests and moving aliases + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + needs: [script-tests, versions, app, web] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - docker buildx imagetools create \ - --tag "${WEB_IMAGE}:latest" \ - "${WEB_IMAGE}:${IMAGE_VERSION}-amd64" \ - "${WEB_IMAGE}:${IMAGE_VERSION}-arm64" + - name: Publish approved images and aliases + run: bash scripts/publish-images.sh promote "$APP_IMAGE" "$WEB_IMAGE" "$GITHUB_SHA" .github/nextcloud-supported.json diff --git a/.github/workflows/nextcloud-development.yml b/.github/workflows/nextcloud-development.yml index e74ef0d..fbe90aa 100644 --- a/.github/workflows/nextcloud-development.yml +++ b/.github/workflows/nextcloud-development.yml @@ -176,6 +176,11 @@ jobs: packages: write steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Set up Docker Buildx uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 diff --git a/Makefile b/Makefile index 95454a2..8f73fa7 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COMPOSE ?= docker compose GARAGES3_COMPOSE_FILE ?= docker-compose-garages3.yml APP_TEST_IMAGE ?= nextcloud-app:acceptance -.PHONY: up-garages3 down-garages3 bootstrap-garages3 garage-status-garages3 start-garages3 wait-nextcloud-garages3 setup-garages3 test-scan-images test-development-image-scripts test-ncdd test-app-image test-current-app-image scan-images +.PHONY: up-garages3 down-garages3 bootstrap-garages3 garage-status-garages3 start-garages3 wait-nextcloud-garages3 setup-garages3 test-scan-images test-ci-scripts test-development-image-scripts test-ncdd test-app-image test-current-app-image scan-images up-garages3: $(COMPOSE) -f $(GARAGES3_COMPOSE_FILE) up -d garage @@ -30,6 +30,9 @@ setup-garages3: test-scan-images: bash tests/test-scan-images.sh +test-ci-scripts: + bats tests/nextcloud-versions.bats tests/publish-images.bats + test-development-image-scripts: bats tests/nextcloud-development-image.bats diff --git a/README.md b/README.md index aeebc5e..d853d3d 100644 --- a/README.md +++ b/README.md @@ -1,243 +1,54 @@ -Languages avaliable: [pt-BR](docs/README_ptBR.md) +# Nextcloud with Docker -# Nextcloud with SSL and Docker +[Português](docs/README_ptBR.md) -- [Nextcloud with SSL and Docker](#nextcloud-with-ssl-and-docker) - - [Setup of docker](#setup-of-docker) - - [Setup of proxy](#setup-of-proxy) - - [Setup of database](#setup-of-database) - - [Before first run](#before-first-run) - - [After setup](#after-setup) - - [Custom setup](#custom-setup) - - [Customize docker-compose content](#customize-docker-compose-content) - - [PHP](#php) - - [Run Nextcloud](#run-nextcloud) - - [Use a specific version of Nextcloud](#use-a-specific-version-of-nextcloud) - - [Logs](#logs) - - [Nextcloud Talk](#talk) +A configurable Docker environment for organizations and teams running their own Nextcloud. Maintained by [LibreCode](https://librecode.coop/), this repository combines the Nextcloud application, web server, caching, and database integration in a deployment you can adapt to your infrastructure. -## NCDD command interface +## Why use this project? -For development automation, use the repository's `bin/ncdd` wrapper instead of reproducing Docker Compose details in CI, agents, or local instructions. It operates the existing `docker-compose.yml` and `docker-compose-postgres.yml`; it does not define a second stack. +- **Control upgrades.** Choose a specific Nextcloud major and plan upgrades instead of unexpectedly moving to a new major. +- **Keep deployment choices open.** Use Docker Compose with Nginx, Redis, PostgreSQL, and optional Garage S3 storage. +- **Use tested image channels.** Published application images follow supported Nextcloud releases; image builds go through vulnerability scanning and runtime checks before publication. +- **Separate production from development.** Versioned stable images and the `master-fpm` development channel serve different needs. -```bash -bin/ncdd up -bin/ncdd doctor -bin/ncdd shell -bin/ncdd down -``` - -See [docs/ncdd.md](docs/ncdd.md) for the command contract and testing policy. - -## Container image contract - -Image names, moving tags, immutable references, runtime variants, and required traceability metadata are defined in [docs/images.md](docs/images.md). - -## Setup of docker - -You need to have, on your server, the installed docker. The installation can be done with an official script, following the following steps: -- Download the docker -```bash -curl -fsSL https://get.docker.com -o get-docker.sh -``` -- run the script -```bash -sh get-docker.sh -``` -- Give permissions to execute the Docker command to your user -```bash -sudo usermod -aG docker $USER -``` -- Remove the installation script -```bash -rm get-docker.sh -``` - -## Setup of proxy - -Follow the instructions of this repository: +This project is intended for teams that manage their own infrastructure or deliver Nextcloud as a service. It provides deployment tooling, not a hosted Nextcloud subscription. -https://github.com/LibreCodeCoop/nginx-proxy +## Get started -## Setup of database - -Follow the instructions of this repository: - -https://github.com/LibreCodeCoop/postgres-docker - -## Before first run - -Copy the `.env.example` to `.env` and set the values. +You need Docker Engine with Docker Compose, a domain for public access, and a reverse proxy. This example uses the [LibreCode Nginx proxy](https://github.com/LibreCodeCoop/nginx-proxy); the PostgreSQL Compose extension is included in this repository. ```bash cp .env.example .env -``` - -| Environment | service | Description | -|-------------|---------|-------| -| [`VIRTUAL_HOST`](https://github.com/nginx-proxy/nginx-proxy#usage) | `web` | Your domain | -| [`LETSENCRYPT_HOST`](https://github.com/nginx-proxy/docker-letsencrypt-nginx-proxy-companion/blob/master/docs/Basic-usage.md#step-3---proxyed-containers) | `web` | Your domain | -| [`LETSENCRYPT_EMAIL`](https://github.com/nginx-proxy/docker-letsencrypt-nginx-proxy-companion/blob/master/docs/Let's-Encrypt-and-ACME.md#contact-address) | `web` | Your sysadmin email | -| `NEXTCLOUD_TRUSTED_DOMAINS` | `app` | domains separated by comma. The domain web is mandatory, add your domain together with whe domain web. The domain `web` is the domain of Nginx service. | -| `POSTGRES_DB` | `db` | PostgreSQL database name (default: nextcloud) | -| `POSTGRES_USER` | `db` | PostgreSQL database user (default: nextcloud) | -| `POSTGRES_PASSWORD` | `db` | PostgreSQL database user password | -| `POSTGRES_HOST` | `app` | PostgreSQL server host (default: postgres) | -| `NEXTCLOUD_ADMIN_USER` | `app` | Nextcloud administrator username | -| `NEXTCLOUD_ADMIN_PASSWORD` | `app` | Nextcloud administrator password | -| `NEXTCLOUD_ADMIN_EMAIL` | `app` | Nextcloud administrator email | -| `SMTP_HOST` | `app` | SMTP server for email sending | -| `SMTP_SECURE` | `app` | SMTP security type (ssl, tls, or empty) | -| `SMTP_PORT` | `app` | SMTP server port | -| `SMTP_AUTHTYPE` | `app` | SMTP authentication type (LOGIN, PLAIN, NTLM) | -| `SMTP_NAME` | `app` | Username for SMTP authentication | -| `SMTP_PASSWORD` | `app` | Password for SMTP authentication | -| `MAIL_FROM_ADDRESS` | `app` | Sender email address | -| `MAIL_DOMAIN` | `app` | Sender email domain | -| `TZ` | `app` | Timezone (e.g., America/Sao_Paulo) | -| `TRASHBIN_RETENTION_OBLIGATION` | `app` | Number of days files should remain in the recycle bin before being cleaned. | - -> **PS**: Let's Encrypt only work in servers when the `VIRTUAL_HOST` and `LETSENCRYPT_HOST` have a valid public domain registered in a DNS server. Don't try to use in localhost, don't work! - -Create a network - -```bash +# Edit .env: domain, credentials, administrator account, and your Nextcloud version. docker network create reverse-proxy docker network create postgres +docker compose -f docker-compose.yml -f docker-compose-postgres.yml up -d --build ``` -## After setup - -After finish the setup, access this url: https://yourdomain.tld/settings/admin/overview. - -If is necessary run any occ command, run like this: - -```bash -docker compose exec -u www-data app ./occ db:add-missing-indices -docker compose exec -u www-data app ./occ db:convert-filecache-bigint -``` - -## Custom setup - -### Customize docker-compose content - -You can do this using environments and creating a file called `docker-compose.override.yml` to add new services. - -### Redis - -The main compose files now include a `redis` service by default. This keeps the stack self-contained for Nextcloud installations that already use Redis in `config.php` and avoids depending on a host-specific external network. - -### Garage S3 primary storage - -Use `docker-compose-garages3.yml` when you want Nextcloud to store files in a Garage S3 bucket instead of the local `data/` directory. - -The stack expects these values in `.env`: - -- `GARAGES3_BUCKET` -- `GARAGES3_KEY` -- `GARAGES3_KEY_ID` -- `GARAGES3_SECRET` -- `GARAGES3_HOSTNAME`, defaulting to `host.docker.internal` -- `GARAGES3_PORT`, defaulting to `3900` -- `GARAGES3_REGION`, defaulting to `garage` - -Create the bucket and access key in Garage before starting Nextcloud with this compose file. `GARAGES3_KEY_ID` must contain the Garage access key ID used by Nextcloud. - -The Garage service uses `garage/garage.toml`. Update `rpc_secret` before using it in a real environment. - -Use `make up-garages3` to start Garage, and `make bootstrap-garages3` to create the Garage bucket and access key. -The bootstrap updates `.env` in place with the generated Garage credentials. -Use `make setup-garages3` to run the full setup and wait for Nextcloud to report as installed. -The stack now uses PostgreSQL 16. If you already created the database volume with an older PostgreSQL major version, recreate or migrate that volume once before starting the updated compose file. - -For a clean local installation, use `make reset-garages3`. -Use `make setup-garages3` if you want to keep the existing local state and only rerun the setup steps. - -Basic flow: - -1. Copy `.env.example` to `.env` if needed. -2. Update `garage/garage.toml` and replace the placeholder `rpc_secret`. -3. Run `make setup-garages3`. -4. Open the Nextcloud URL and finish the initial admin setup if it is still pending. - -### PHP - -- Create your `.ini` file at `volumes/php/` folder. Example: `volumes/php/xdebug.ini` -- Alter the file `docker-compose.override.yml` adding your volume -```yaml -services: - app: - volumes: - - ./volumes/php/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini -``` - - -### PHP-FPM - -- For PHP-FPM modifications, include the following volume to the app service in `docker-compose.override.yml` file: -```yaml -services: - app: - volumes: - - ./volumes/php/pm.conf:/usr/local/etc/php-fpm.d/www2.conf -``` -- Create a file `./volumes/php/pm.conf` with the following content (see references for tunning according your setup): -```yaml -[www] -pm.max_children = 10 -pm.start_servers = 2 -pm.min_spare_servers = 1 -pm.max_spare_servers = 3 -``` - -- References: -- https://docs.nextcloud.com/server/21/admin_manual/installation/server_tuning.html#tune-php-fpm -- https://spot13.com/pmcalculator/ +The external `reverse-proxy` network must have a configured proxy attached. Set up the proxy and DNS before expecting HTTPS to work. For production, review the PostgreSQL version in the Compose example, your backup strategy, and volume migrations. -## Run Nextcloud +For environment variables, existing deployments, storage options, and troubleshooting, see the [deployment guide](docs/deployment.md). -```bash -# The postgres service is executed separated to be possible reuse this service to other applications that use PostgreSQL -docker compose up -f docker-compose-postgres.yml -d -docker compose up -d -docker compose -d -``` -## Use a specific version of Nextcloud +## Nextcloud versions and image updates -Change the value of NEXTCLOUD_VERSION at `.env` file and put the tag name that you want to use. Check the availables tags here: https://hub.docker.com/_/nextcloud/tags +Set `NEXTCLOUD_VERSION` in `.env` to select the **official Nextcloud base image** for this repository's local Docker Compose build. This is separate from publishing the LibreCode images to GHCR. -The GHCR build workflow reads `NEXTCLOUD_VERSION` from `.env.example` and publishes the app and web images as static `:latest` tags so tools like Watchtower can track them reliably. +Prebuilt LibreCode application images use versioned tags, such as: -Build the images, down the containers and get up again: - -```bash -docker compose build --pull -docker compose up -d +```text +ghcr.io/librecodecoop/nextcloud-docker-app:35-fpm ``` -## Vulnerability scanning +For production, **pin a Nextcloud major or an OCI digest** and plan major upgrades deliberately. The moving `:latest` and `:stable-fpm` aliases can switch to a newer major; they are not safe defaults for unattended production upgrades. -Published `app` and `web` images are scanned for vulnerabilities. Contributors -can run the scan locally with: - -```bash -make scan-images -``` - -## Logs - -If you want to see the logs, run: - -```bash -docker compose logs -f --tail=100 -``` -You will see this message in the logs and other many upgrade messages: - -```log -app_1 | 2020-04-28T19:49:38.568623133Z Initializing nextcloud 18.0.4.2 ... -app_1 | 2020-04-28T19:49:38.577733913Z Upgrading nextcloud from 18.0.3.0 ... -``` +See [image names, tags, and upgrade policy](docs/images.md) for the complete image options. -## Talk +## Documentation -For setting up Nextcloud Talk with all services, see [here](https://github.com/LibreCodeCoop/nextcloud-docker-talk). +| Guide | Purpose | +| --- | --- | +| [Deployment and customization](docs/deployment.md) | Installation, configuration, storage, PHP tuning, and logs | +| [Container images](docs/images.md) | Available image tags, compatibility, and safe upgrades | +| [Nextcloud Talk](https://github.com/LibreCodeCoop/nextcloud-docker-talk) | Additional services for Talk | +| [Development commands](docs/ncdd.md) | The `bin/ncdd` interface for local development | +| [Image maintenance](docs/maintenance.md) | Version discovery, workflows, CI checks, and release publication | diff --git a/docs/README_ptBR.md b/docs/README_ptBR.md index 1e013f8..a3e3fce 100644 --- a/docs/README_ptBR.md +++ b/docs/README_ptBR.md @@ -1,166 +1,54 @@ -# Nextcloud com SSL e Docker -- [Nextcloud com SSL e Docker](#nextcloud-com-ssl-e-docker) - - [Configuração do Docker](#configuração-do-docker) - - [Configuração de proxy](#configuração-de-proxy) - - [Antes da primeira execução](#antes-da-primeira-execução) - - [Após a configuração](#após-a-configuração) - - [Configuração personalizada](#configuração-personalizada) - - [Personalize o conteúdo do docker-compose](#personalize-o-conteúdo-do-docker-compose) - - [PHP](#php) - - [Execute o Nextcloud](#execute-o-nextcloud) - - [Use uma versão específica do Nextcloud](#use-uma-versão-específica-do-nextcloud) - - [Logs](#logs) - - [Nextcloud Talk](#talk) - -## Configuração do Docker - -Você precisa ter, em seu servidor, o Docker instalado. A instalação pode ser feita com um script oficial, seguindo os seguintes passos: -- Baixe o Docker -```bash -curl -fsSL https://get.docker.com -o get-docker.sh -``` -- Execute o script -```bash -sh get-docker.sh -``` -- Conceda permissões para executar o comando Docker para seu usuário -```bash -sudo usermod -aG docker $USER -``` -- Remova o script de instalação -```bash -rm get-docker.sh -``` - -## Configuração de proxy +# Nextcloud com Docker -Siga as instruções deste repositório: +[English](../README.md) -https://github.com/LibreCodeCoop/nginx-proxy +Um ambiente Docker configurável para organizações e equipes que hospedam seu próprio Nextcloud. Mantido pela [LibreCode](https://librecode.coop/), este repositório reúne a aplicação, o servidor web, o cache e a integração com banco de dados em uma implantação que pode ser adaptada à sua infraestrutura. -## Antes da primeira execução +## Por que utilizar este projeto? -Copie o `.env.example` para `.env` e defina os valores. +- **Controle das atualizações.** Escolha uma versão principal do Nextcloud e planeje as migrações sem mudanças inesperadas de versão. +- **Flexibilidade na infraestrutura.** Utilize Docker Compose com Nginx, Redis, PostgreSQL e armazenamento opcional no Garage S3. +- **Imagens com verificações automatizadas.** As imagens publicadas acompanham versões mantidas do Nextcloud e passam por varredura de vulnerabilidades e testes de execução antes da publicação. +- **Separação entre produção e desenvolvimento.** Imagens estáveis versionadas e o canal `master-fpm` atendem a necessidades diferentes. -```bash -cp .env.example .env -``` +O projeto é voltado a equipes que administram sua própria infraestrutura ou oferecem Nextcloud como serviço. Ele fornece ferramentas de implantação, não uma assinatura de hospedagem. -| Ambiente | serviço | Descrição | -|-------------|---------|-------| -| [`VIRTUAL_HOST`](https://github.com/nginx-proxy/nginx-proxy#usage) | `web` | Seu domínio | -| [`LETSENCRYPT_HOST`](https://github.com/nginx-proxy/docker-letsencrypt-nginx-proxy-companion/blob/master/docs/Basic-usage.md#step-3---proxyed-containers) | `web` | Seu domínio | -| [`LETSENCRYPT_EMAIL`](https://github.com/nginx-proxy/docker-letsencrypt-nginx-proxy-companion/blob/master/docs/Let's-Encrypt-and-ACME.md#contact-address) | `web` | Seu email de administrador de sistema | -| `NEXTCLOUD_TRUSTED_DOMAINS` | `app` | domínios separados por vírgula. O domínio `web` é obrigatório, adicione seu domínio junto com o domínio `web`. O domínio `web` é o domínio do serviço Nginx. | +## Comece a utilizar -> **PS**: A Let's Encrypt só funciona em servidores quando os `VIRTUAL_HOST` e `LETSENCRYPT_HOST` têm um domínio público válido registrado em um servidor DNS. Não tente usá-lo em localhost, não funciona! - -Crie uma rede +Você precisa do Docker Engine com Docker Compose, de um domínio para acesso público e de um proxy reverso. O exemplo utiliza o [proxy Nginx da LibreCode](https://github.com/LibreCodeCoop/nginx-proxy); a extensão Compose do PostgreSQL está incluída neste repositório. ```bash +cp .env.example .env +# Edite .env: domínio, credenciais, administrador e versão do Nextcloud. docker network create reverse-proxy +docker network create postgres +docker compose -f docker-compose.yml -f docker-compose-postgres.yml up -d --build ``` -## Após a configuração - -Após concluir a configuração, acesse esta URL: https://seudomínio.tld/settings/admin/overview. - -Se for necessário executar algum comando occ, execute da seguinte forma: - -```bash -docker compose exec -u www-data app ./occ db:add-missing-indices -docker compose exec -u www-data app ./occ db:convert-filecache-bigint -``` - - ## Configuração personalizada - -### Personalize o conteúdo do docker-compose - -Você pode fazer isso usando variáveis de ambiente e criando um arquivo chamado `docker-compose.override.yml` para adicionar novos serviços. - -### Redis - -Os arquivos principais de compose agora incluem o serviço `redis` por padrão. Isso deixa o stack autocontido para instalações do Nextcloud que já usam Redis no `config.php` e evita depender de uma rede externa específica do host. - -### Storage primário Garage S3 - -Use `docker-compose-garages3.yml` quando quiser que o Nextcloud grave os arquivos em um bucket Garage S3 em vez do diretório local `data/`. - -O stack espera estes valores em `.env`: - -- `GARAGES3_BUCKET` -- `GARAGES3_KEY` -- `GARAGES3_KEY_ID` -- `GARAGES3_SECRET` -- `GARAGES3_HOSTNAME`, com padrão `host.docker.internal` -- `GARAGES3_PORT`, com padrão `3900` -- `GARAGES3_REGION`, com padrão `garage` - -Crie o bucket e a chave de acesso no Garage antes de iniciar o Nextcloud com este compose. `GARAGES3_KEY_ID` deve conter o access key ID do Garage usado pelo Nextcloud. - -O serviço Garage usa `garage/garage.toml`. Atualize `rpc_secret` antes de usar em ambiente real. - -Use `make up-garages3` para subir o Garage e `make bootstrap-garages3` para criar o bucket e a chave de acesso no Garage. -O bootstrap atualiza `.env` com as credenciais geradas do Garage. -Use `make setup-garages3` para executar a configuração completa e aguardar o Nextcloud reportar como instalado. -O stack agora usa PostgreSQL 16. Se você já criou o volume do banco com uma versão major mais antiga, recrie ou migre esse volume uma vez antes de subir o compose atualizado. - -Para uma instalação local limpa, use `make reset-garages3`. -Use `make setup-garages3` se quiser manter o estado local existente e apenas repetir as etapas de setup. - -Fluxo básico: - -1. Copie `.env.example` para `.env`, se necessário. -2. Atualize `garage/garage.toml` e substitua o `rpc_secret` placeholder. -3. Execute `make setup-garages3`. -4. Abra a URL do Nextcloud e finalize a configuração inicial do admin, se ainda estiver pendente. - -### PHP - -- Crie seu arquivo `.ini` na pasta `volumes/php/`. Exemplo: `volumes/php/xdebug.ini` -- Altere o arquivo `docker-compose.override.yml` adicionando seu volume -```yaml -services: - app: - volumes: - - ./volumes/php/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini -``` - -## Execute o Nextcloud +O proxy reverso deve estar configurado e conectado à rede externa `reverse-proxy`. Configure também o DNS antes de esperar que o HTTPS funcione. Em produção, revise a versão do PostgreSQL usada no exemplo, os backups e as migrações dos volumes. -```bash -# O serviço postgres é executado separadamente para que seja possível reutilizar este serviço para outras aplicações que usam PostgreSQL -docker compose up -f docker-compose-postgres.yml -d -docker compose up -d -docker compose -d -``` -## Use uma versão específica do Nextcloud +Para variáveis de ambiente, instalações existentes, armazenamento e solução de problemas, consulte o [guia de implantação](deployment_ptBR.md). -Altere o valor de `NEXTCLOUD_VERSION` no arquivo `.env` e coloque o nome do rótulo que deseja usar. Verifique as tags disponíveis aqui: https://hub.docker.com/_/nextcloud/tags +## Versões e atualizações do Nextcloud -O workflow de build no GHCR lê `NEXTCLOUD_VERSION` de `.env.example` e publica as imagens de app e web com tag fixa `:latest`, para que ferramentas como o Watchtower acompanhem atualizações sem trocar o nome da imagem. +Defina `NEXTCLOUD_VERSION` no arquivo `.env` para selecionar a **imagem-base oficial do Nextcloud** utilizada no build local do Docker Compose. Isso é diferente da publicação das imagens da LibreCode no GHCR. -Construa as imagens, derrube os contêineres e inicie-os novamente: +As imagens de aplicação da LibreCode disponibilizam tags por versão, por exemplo: -```bash -docker compose build --pull -docker compose up -d +```text +ghcr.io/librecodecoop/nextcloud-docker-app:35-fpm ``` -## Logs +Em produção, **fixe uma versão principal ou o digest OCI** e planeje cada atualização de versão principal. As tags móveis `:latest` e `:stable-fpm` podem passar para uma versão principal mais recente; não são uma boa escolha para atualizações automáticas em produção. -Se você quiser ver os logs, execute: - -```bash -docker compose logs -f --tail=100 -``` -Você verá esta mensagem nos logs e muitas outras mensagens de atualização: - -```log -app_1 | 2020-04-28T19:49:38.568623133Z Inicializando o Nextcloud 18.0.4.2 ... -app_1 | 2020-04-28T19:49:38.577733913Z Atualizando o Nextcloud a partir da versão 18.0.3.0 ... -``` +Consulte [imagens, tags e política de atualização](images.md) para conhecer as opções. -## Talk +## Documentação -Para configurar o Nextcloud Talk e demais serviços necessários, veja [aqui](https://github.com/LibreCodeCoop/nextcloud-docker-talk). +| Guia | Conteúdo | +| --- | --- | +| [Implantação e personalização](deployment_ptBR.md) | Instalação, configuração, armazenamento, PHP e logs | +| [Imagens de contêiner](images.md) | Tags, compatibilidade e atualizações seguras | +| [Nextcloud Talk](https://github.com/LibreCodeCoop/nextcloud-docker-talk) | Serviços adicionais para o Talk | +| [Comandos de desenvolvimento](ncdd.md) | Interface `bin/ncdd` para desenvolvimento local | +| [Manutenção das imagens](maintenance.md) | Descoberta de versões, workflows, CI e publicação | diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..489d582 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,117 @@ +# Deployment and customization + +This guide covers the example Docker Compose stack shipped with this repository. The Compose configuration **builds application and web images locally**. It does not pull the prebuilt LibreCode GHCR images by default. + +Return to the [project overview](../README.md) or see the [published image tags](images.md). + +## Prerequisites + +Install Docker Engine and the Docker Compose plugin. See the [Docker installation documentation](https://docs.docker.com/engine/install/). + +The standard deployment expects a public domain, a reverse proxy that handles HTTPS (for example, [LibreCode nginx-proxy](https://github.com/LibreCodeCoop/nginx-proxy)), and two external Docker networks: + +```bash +docker network create reverse-proxy +docker network create postgres +``` + +Create these networks **once** if they do not exist. Configure the proxy service, DNS, and any Let's Encrypt integration separately. A publicly resolvable domain is required to obtain a public TLS certificate; Let's Encrypt will not issue a certificate for localhost. + +## Configure the environment + +```bash +cp .env.example .env +``` + +Edit `.env` before running the services. At a minimum, provide administrator credentials, the database password, and your public domain. + +| Variable | Purpose | +| --- | --- | +| `NEXTCLOUD_VERSION` | Nextcloud version/tag used for the **official Docker base image** during a local build, e.g. `35-fpm` | +| `VIRTUAL_HOST`, `LETSENCRYPT_HOST` | Public domain passed to the external proxy | +| `LETSENCRYPT_EMAIL` | Contact email for certificate management | +| `NEXTCLOUD_TRUSTED_DOMAINS` | Trusted hostnames, including the public domain and the internal `web` host where applicable | +| `NEXTCLOUD_ADMIN_USER`, `NEXTCLOUD_ADMIN_PASSWORD` | Initial administrator credentials | +| `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD` | Database name, username, and password | +| `POSTGRES_HOST` | Database host; defaults to `postgres` in the standard Compose extension | +| `TZ` | Container timezone | +| `SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE` | Outgoing mail transport | +| `SMTP_NAME`, `SMTP_PASSWORD`, `SMTP_AUTHTYPE` | Optional mail authentication | +| `MAIL_FROM_ADDRESS`, `MAIL_DOMAIN` | Sender address settings | + +See `docker-compose.yml` and `.env.example` for the values consumed by the stack. + +## Start Nextcloud + +The standard setup uses the base Compose file plus the PostgreSQL extension: + +```bash +docker compose -f docker-compose.yml -f docker-compose-postgres.yml up -d --build +``` + +The main Compose file provides Nextcloud FPM, an Nginx web container, Redis, and cron. The additional Compose file supplies PostgreSQL and connects the app and cron services to its network. + +**Database compatibility:** `docker-compose-postgres.yml` currently uses `postgres:11-alpine`, which is no longer a supported PostgreSQL major. This is a legacy example, not a production-ready database policy. Before a production deployment, choose a supported PostgreSQL version compatible with your Nextcloud release and plan any existing volume migration. Do not simply swap PostgreSQL majors against an existing data directory. The Garage S3 example uses a separate PostgreSQL 16 service; its volume is not interchangeable without migration. + +After setup, open `https://your-domain.example/settings/admin/overview` to check the administrator overview. + +For maintenance commands, run `occ` as the web-server user: + +```bash +docker compose -f docker-compose.yml -f docker-compose-postgres.yml \ + exec -u www-data app php occ status +``` + +## Choosing and upgrading Nextcloud + +Change `NEXTCLOUD_VERSION` in `.env` to select the official upstream image used by local Compose builds. It **does not control** which GHCR versions are published by LibreCode. + +```bash +docker compose -f docker-compose.yml -f docker-compose-postgres.yml build --pull +docker compose -f docker-compose.yml -f docker-compose-postgres.yml up -d +``` + +Back up the database, application configuration, and persistent files before an upgrade. Check Nextcloud's upgrade requirements and compatibility first: downgrades and skipping unsupported upgrade paths can leave stored data unusable. Use a controlled major-version upgrade process instead of unattended `:latest` changes. + +For users deploying the prebuilt GHCR images, see the [image selection and upgrade policy](images.md). + +## Optional Garage S3 primary storage + +Use the separate `docker-compose-garages3.yml` example if Nextcloud should store primary files in Garage instead of the standard local data directory. This layout uses its own PostgreSQL 16 service. + +Set up the required Garage values in `.env`: + +- `GARAGES3_BUCKET`, `GARAGES3_KEY_ID`, `GARAGES3_SECRET` +- `GARAGES3_HOSTNAME` (default `host.docker.internal`), `GARAGES3_PORT` (default `3900`) +- `GARAGES3_REGION` (default `garage`), plus TLS/path-style settings as needed + +Replace the placeholder `rpc_secret` in `garage/garage.toml` before deployment. Create the bucket and access key for Nextcloud, or use the project's bootstrap commands: + +```bash +make up-garages3 +make bootstrap-garages3 +make setup-garages3 +``` + +The bootstrap may update `.env` with generated credentials. Review its effects before using it on an existing deployment. Do not switch primary storage on a live installation without a migration plan. + +## Advanced customization + +Add services or override service options in `docker-compose.override.yml`. For example, to load a PHP configuration file: + +```yaml +services: + app: + volumes: + - ./volumes/php/custom.ini:/usr/local/etc/php/conf.d/custom.ini:ro +``` + +PHP-FPM pool settings can also be mounted into `/usr/local/etc/php-fpm.d/`. Keep changes compatible with the underlying Nextcloud PHP version and workload. + +## Logs and troubleshooting + +```bash +docker compose -f docker-compose.yml -f docker-compose-postgres.yml logs -f --tail=100 +``` + +To inspect available development commands, see [NCDD](ncdd.md). To configure calling and conferencing services, see [Nextcloud Talk](https://github.com/LibreCodeCoop/nextcloud-docker-talk). diff --git a/docs/deployment_ptBR.md b/docs/deployment_ptBR.md new file mode 100644 index 0000000..bf50174 --- /dev/null +++ b/docs/deployment_ptBR.md @@ -0,0 +1,117 @@ +# Implantação e personalização + +Este guia explica o ambiente Docker Compose fornecido pelo repositório. O Compose **constrói as imagens da aplicação e do servidor web localmente**. Por padrão, ele não utiliza as imagens pré-compiladas da LibreCode no GHCR. + +Volte para a [apresentação do projeto](README_ptBR.md) ou consulte as [tags das imagens publicadas](images.md). + +## Pré-requisitos + +Instale o Docker Engine e o plugin Docker Compose. Consulte a [documentação oficial](https://docs.docker.com/engine/install/). + +A instalação padrão pressupõe um domínio público, um proxy reverso responsável pelo HTTPS (por exemplo, o [nginx-proxy da LibreCode](https://github.com/LibreCodeCoop/nginx-proxy)) e duas redes externas do Docker: + +```bash +docker network create reverse-proxy +docker network create postgres +``` + +Crie essas redes **uma única vez**, caso ainda não existam. Configure separadamente o proxy, o DNS e a integração com Let's Encrypt. Para obter um certificado TLS público, você precisa de um domínio resolvível publicamente; o Let's Encrypt não emite certificados para localhost. + +## Configuração do ambiente + +```bash +cp .env.example .env +``` + +Edite o `.env` antes de iniciar os serviços. Informe, no mínimo, as credenciais do administrador, a senha do banco e o domínio público. + +| Variável | Finalidade | +| --- | --- | +| `NEXTCLOUD_VERSION` | Versão/tag da **imagem-base oficial do Nextcloud** utilizada no build local, por exemplo `35-fpm` | +| `VIRTUAL_HOST`, `LETSENCRYPT_HOST` | Domínio público utilizado pelo proxy | +| `LETSENCRYPT_EMAIL` | E-mail de contato para os certificados | +| `NEXTCLOUD_TRUSTED_DOMAINS` | Domínios confiáveis, incluindo o domínio público e o host interno `web`, quando aplicável | +| `NEXTCLOUD_ADMIN_USER`, `NEXTCLOUD_ADMIN_PASSWORD` | Credenciais do administrador inicial | +| `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD` | Nome, usuário e senha do banco de dados | +| `POSTGRES_HOST` | Host do banco; o padrão na extensão Compose é `postgres` | +| `TZ` | Fuso horário dos contêineres | +| `SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE` | Configurações do servidor de envio de e-mail | +| `SMTP_NAME`, `SMTP_PASSWORD`, `SMTP_AUTHTYPE` | Autenticação de e-mail, quando necessária | +| `MAIL_FROM_ADDRESS`, `MAIL_DOMAIN` | Endereço do remetente | + +Consulte `docker-compose.yml` e `.env.example` para verificar as variáveis usadas pelo ambiente. + +## Iniciar o Nextcloud + +A configuração padrão combina o Compose principal com a extensão do PostgreSQL: + +```bash +docker compose -f docker-compose.yml -f docker-compose-postgres.yml up -d --build +``` + +O Compose principal contém Nextcloud FPM, Nginx, Redis e cron. A extensão adiciona o PostgreSQL e conecta os serviços da aplicação e do cron à rede do banco. + +**Compatibilidade do banco:** o arquivo `docker-compose-postgres.yml` ainda utiliza `postgres:11-alpine`, versão que não recebe mais manutenção. Esse é um exemplo legado, não uma recomendação para produção. Para produção, escolha uma versão do PostgreSQL ainda suportada e compatível com o Nextcloud e planeje a migração dos volumes existentes. Não altere simplesmente a versão principal do PostgreSQL mantendo um diretório de dados antigo. O exemplo Garage S3 usa PostgreSQL 16 de forma independente. + +Após a configuração, abra `https://seu-dominio.example/settings/admin/overview` para consultar a visão administrativa. + +Para executar comandos de manutenção, utilize o usuário do servidor web: + +```bash +docker compose -f docker-compose.yml -f docker-compose-postgres.yml \ + exec -u www-data app php occ status +``` + +## Escolher e atualizar a versão do Nextcloud + +Altere `NEXTCLOUD_VERSION` no arquivo `.env` para escolher a imagem oficial utilizada nos builds locais. Essa variável **não define** quais versões são publicadas pela LibreCode no GHCR. + +```bash +docker compose -f docker-compose.yml -f docker-compose-postgres.yml build --pull +docker compose -f docker-compose.yml -f docker-compose-postgres.yml up -d +``` + +Faça backup do banco de dados, da configuração e dos arquivos persistidos antes de atualizar. Confira as exigências de compatibilidade do Nextcloud: downgrade ou saltos não suportados entre versões podem impedir o uso dos dados. Prefira atualizações de versões principais planejadas. + +Para quem utiliza as imagens do GHCR, consulte a [política de tags e atualizações](images.md). + +## Armazenamento primário opcional com Garage S3 + +O arquivo `docker-compose-garages3.yml` oferece um exemplo separado em que o Nextcloud utiliza o Garage para armazenar os arquivos, em vez do diretório de dados local. Esse ambiente possui seu próprio PostgreSQL 16. + +Configure no `.env`: + +- `GARAGES3_BUCKET`, `GARAGES3_KEY_ID`, `GARAGES3_SECRET` +- `GARAGES3_HOSTNAME` (padrão `host.docker.internal`) e `GARAGES3_PORT` (padrão `3900`) +- `GARAGES3_REGION` (padrão `garage`) e as opções de TLS e acesso por caminho, quando necessárias + +Substitua o valor de exemplo de `rpc_secret` em `garage/garage.toml`. Crie o bucket e a chave de acesso do Nextcloud ou utilize os comandos de preparação do projeto: + +```bash +make up-garages3 +make bootstrap-garages3 +make setup-garages3 +``` + +O bootstrap pode atualizar o `.env` com as credenciais geradas. Revise os efeitos antes de executá-lo em uma instalação existente. Não mude o armazenamento primário de um ambiente em produção sem um plano de migração. + +## Personalização avançada + +Utilize `docker-compose.override.yml` para acrescentar serviços ou personalizar os existentes. Por exemplo, para carregar um arquivo de configuração do PHP: + +```yaml +services: + app: + volumes: + - ./volumes/php/custom.ini:/usr/local/etc/php/conf.d/custom.ini:ro +``` + +As configurações do PHP-FPM também podem ser montadas em `/usr/local/etc/php-fpm.d/`. Verifique a compatibilidade com a versão do PHP utilizada pelo Nextcloud. + +## Logs e solução de problemas + +```bash +docker compose -f docker-compose.yml -f docker-compose-postgres.yml logs -f --tail=100 +``` + +Para comandos de desenvolvimento, consulte o [NCDD](ncdd.md). Para serviços de chamada e conferência, veja o [Nextcloud Talk](https://github.com/LibreCodeCoop/nextcloud-docker-talk). diff --git a/docs/images.md b/docs/images.md index 7a47144..77d12f6 100644 --- a/docs/images.md +++ b/docs/images.md @@ -1,213 +1,63 @@ -# Container image naming and tagging +# Container images and upgrade policy -This document is the source of truth for image names, tags, runtime variants, and traceability for images published by this repository. +The LibreCode Nextcloud images let you choose a maintained Nextcloud release for your environment. This guide describes the **public image names, tags, and update behavior**. For details of how these images are built, tested, and published, see the [maintainer guide](maintenance.md). -The convention intentionally stays close to the official Nextcloud image model. Channel or server version, runtime variant, and immutable image identity are separate concerns. +## Images -## Image names +| Image | Purpose | +| --- | --- | +| `ghcr.io/librecodecoop/nextcloud-docker-app` | Nextcloud application runtime with PHP-FPM | +| `ghcr.io/librecodecoop/nextcloud-docker-web` | Nginx web runtime; independent of the Nextcloud major | -Published image names describe the component contained in the image: +The [repository's Docker Compose example](deployment.md) builds its images locally from the official `nextcloud:${NEXTCLOUD_VERSION}` base. It does not automatically pull these GHCR images. -- `ghcr.io/librecodecoop/nextcloud-docker-app` — Nextcloud application runtime. -- `ghcr.io/librecodecoop/nextcloud-docker-web` — web/proxy runtime published by this repository. +## Application tags -Version, development channel, and runtime variant belong in tags, not in the image name. - -Do not create a new image name merely to represent a Nextcloud major or development channel. - -## App image tags - -The app image follows the tag structure used by the official Nextcloud images. - -| Reference | Meaning | Behavior | +| Tag | Meaning | Update behavior | | --- | --- | --- | -| `:-fpm` | Stable release channel within one Nextcloud major, for example `35-fpm` | Moving | -| `:-fpm` | A specific Nextcloud server release, for example `35.0.1-fpm` | Release-specific tag; may be rebuilt | -| `:stable-fpm` | Current stable Nextcloud channel, when this alias is intentionally published | Moving | -| `:master-fpm` | Development channel following Nextcloud Server `master` | Moving | -| `@sha256:` | Exact OCI image content | Immutable | - -The runtime variant is the suffix after the channel or version. The currently defined app runtime variant is `fpm`. - -A future runtime variant, such as `apache`, must preserve the same channel/version semantics instead of introducing a different naming system. - -The development channel must follow upstream `master`; it must not be identified by a future Nextcloud major. - -## Web image tags - -The web image is not tied to a Nextcloud server version. - -Use: - -- `:main` for the moving image built from this repository's main development line; -- `@sha256:` when an exact immutable image is required. +| `:-fpm`, e.g. `:35-fpm` | One maintained Nextcloud major | Receives updates within that major | +| `:-fpm`, e.g. `:35.0.2-fpm` | A particular Nextcloud server release | May be rebuilt with updated base packages | +| `:stable-fpm` | The newest approved, supported major | Can advance to a different major | +| `:latest` | Compatibility alias of `:stable-fpm` | Can advance to a different major | +| `:master-fpm` | Development builds following upstream Nextcloud Server `master` | Changes independently of stable releases | +| `@sha256:` | Exact OCI image content | Immutable reference | -Do not add a Nextcloud major to the web image tag unless the web image itself gains a real version-dependent contract. - -## Moving tags and immutable references - -Moving tags are convenient channel selectors. Their target can change when a new image is published. - -Examples: - -- `35-fpm`; -- `stable-fpm`; -- `master-fpm`; -- `main` for the web image. - -Use a moving tag when following a maintained channel is intentional. - -The canonical immutable identity of a published image is its OCI digest: - -``` -ghcr.io/librecodecoop/nextcloud-docker-app@sha256: -``` +All currently defined application tags use the `fpm` runtime variant. The `master-fpm` channel is intended for testing and development, not production data. -A tag containing a repository commit, such as `sha-`, may be useful for traceability but is not inherently immutable if a rebuild can resolve different upstream packages or base-image content. +## Web tags -A full Nextcloud release tag such as `35.0.1-fpm` identifies the server release clearly, but the OCI digest is still the exact image identity. +The web image is not coupled to a Nextcloud release: -## Tags that are not part of the new contract +| Tag | Meaning | +| --- | --- | +| `:main` | Moving web image from the main development line | +| `:latest` | Compatibility alias of `:main` | +| `@sha256:` | Exact web image content | -Do not introduce these as the repository convention: +## Production upgrade guidance -- bare `:latest`; -- `:dev`; -- `:dev-`; -- `:stable--fpm`; -- `:nc-`; -- bare `:`; -- a development tag tied only to the next Nextcloud major. +**Prefer a major-specific tag** such as `:35-fpm` when you want security and patch updates within the same Nextcloud major. For fully reproducible rollouts, resolve and pin an OCI digest. -Historical tags may continue to exist temporarily while old implementations are replaced, but their existence does not make them part of this contract. +Do **not** use the moving `:latest` or `:stable-fpm` application aliases with unattended production upgrades: a new Nextcloud major can require application, database, and data migrations. Back up your installation, confirm compatibility, and follow Nextcloud's supported upgrade path before changing majors. -The historical development tag `:35` does not require a compatibility alias: the consumer inventory in #52 found no active consumer that depends on it. +When a major reaches end of life, it stops receiving rebuilt, maintained images after its removal is approved. Previously published GHCR tags are not automatically deleted, but an existing tag does **not** mean its Nextcloud major is still supported. -## Traceability metadata +See the [official Nextcloud Server maintenance schedule](https://github.com/nextcloud/server/wiki/Maintenance-and-Release-Schedule) for current support dates. -Every image published under this convention must expose enough metadata to identify both this repository build and the relevant upstream runtime. +## Image references -Use standard OCI labels where the standard already defines the meaning: +Examples of image identifiers: -- `org.opencontainers.image.source` — source repository URL; -- `org.opencontainers.image.revision` — source repository revision used for the build; -- `org.opencontainers.image.created` — build timestamp; -- `org.opencontainers.image.version` — human-readable image/server version or channel where applicable. - -The published metadata must also identify: - -- Nextcloud version or upstream revision; -- PHP version for the app runtime; -- runtime variant, such as `fpm`; -- resolved upstream/base image when applicable. - -Project-specific labels may be used for metadata that has no suitable OCI standard label, but they must not replace an existing OCI label. - -Traceability metadata does not replace an OCI digest when an immutable reference is required. - -## Examples - -Stable moving app channel: - -``` +```text ghcr.io/librecodecoop/nextcloud-docker-app:35-fpm -``` - -Specific stable Nextcloud release: - -``` -ghcr.io/librecodecoop/nextcloud-docker-app:35.0.1-fpm -``` - -Development channel following Nextcloud Server `master`: - -``` +ghcr.io/librecodecoop/nextcloud-docker-app:35.0.2-fpm ghcr.io/librecodecoop/nextcloud-docker-app:master-fpm -``` - -Moving web image: - -``` ghcr.io/librecodecoop/nextcloud-docker-web:main +ghcr.io/librecodecoop/nextcloud-docker-app@sha256: ``` -Immutable stable app image after resolving the digest of the selected stable build: - -``` -ghcr.io/librecodecoop/nextcloud-docker-app@sha256: -``` - -Immutable development app image after resolving the digest published for the `master-fpm` build: - -``` -ghcr.io/librecodecoop/nextcloud-docker-app@sha256: -``` - -Immutable web image: - -``` -ghcr.io/librecodecoop/nextcloud-docker-web@sha256: -``` - -## App image build sources - -The app image uses a single `.docker/app/Dockerfile` for both release and development builds. - -The Dockerfile models its two source modes as Docker build stages instead of runtime shell branching: - -- `NEXTCLOUD_SOURCE=release` selects the stage that keeps the Nextcloud payload provided by the selected official Nextcloud base image; -- `NEXTCLOUD_SOURCE=daily` selects the stage that replaces that payload with a verified upstream daily archive. - -The base runtime and the Nextcloud server payload are separate inputs. A development build can therefore follow Nextcloud Server `master` without introducing a Dockerfile or image identity tied to a future major. - -For daily builds, `NEXTCLOUD_DAILY_URL` is required. The Dockerfile downloads the matching `.sha512` file, verifies the archive, and prepares `/usr/src/nextcloud` in the layout expected by the official Nextcloud entrypoint. - -The repository Compose environment keeps `NEXTCLOUD_VERSION` as its user-facing version setting and maps it to the generic official base image. There is no second Compose stack for development images. - -The app image remains LibreSign-ready while being generic across Nextcloud versions. It keeps repository-level runtime requirements such as Poppler, UTF-8 locale support, and the PHP bz2 extension, but does not reinstall extensions already supplied by the official Nextcloud base image such as Imagick. Dependabot owns Docker and GitHub Actions updates in this repository. Renovate is deliberately restricted to custom regex-managed values that Dependabot cannot see, currently the explicit Trivy binary version used by CI. The two bots must not manage the same dependency. - -## Development publication - -The rolling Nextcloud Server `master` image is published as: - -``` -ghcr.io/librecodecoop/nextcloud-docker-app:master-fpm -``` - -Pull requests build, scan, and run runtime acceptance for both amd64 and arm64 without publishing. Pushes to `main`, the daily scheduled run, and manual workflow dispatches may publish only after both architectures pass those gates. - -The workflow resolves the official `nextcloud:stable-fpm` base to an OCI digest and records that digest in image metadata. It also records the SHA-512 of the exact upstream `latest-master.tar.bz2` artifact used for the build. This provides exact upstream artifact traceability without inventing a Nextcloud Git commit that the daily archive does not expose. - -Architecture-specific staging tags are implementation details used to assemble the multi-platform manifest. The public development-channel contract is `:master-fpm`. - -## Runtime acceptance - -The app image has a runtime acceptance test based on the same behavioral checks used by the official Nextcloud container projects. - -The test operates on an already-built local image. It does not rebuild the image and does not use the repository deployment Compose files: - -```bash -make test-app-image APP_IMAGE=scan/app:amd64 -``` - -For a local build of the current app image followed by the same acceptance test: - -```bash -make test-current-app-image -``` - -The Bats acceptance test creates an isolated Docker network and PostgreSQL container, starts the image with Nextcloud autoinstall variables, waits for the installation, runs `occ status` and `occ check`, and sends a FastCGI request through the FPM runtime. Test-created resources are removed on success and failure. - -CI must run this test against the exact locally loaded app images produced by the build step. Runtime acceptance is a publication gate alongside vulnerability scanning; a separate deployment-stack test is not required for this contract. - -## Implementation boundary - -This document defines the target contract and the generic app-image foundation now implements the release/daily source split. +A release tag identifies the upstream Nextcloud version, but only an OCI digest identifies exact image content. Repository revision tags used during publishing are not necessarily immutable. -Remaining work in #47 must continue incrementally. In particular: +Published OCI metadata identifies the source, revision, build time, version or channel, and relevant runtime information where available. This helps with traceability but does not replace digest pinning. -- keep the `:master-fpm` publication workflow aligned with the generic app-image foundation; -- extend traceability metadata when upstream exposes stronger revision identifiers; -- keep LibreSign-specific behavior out of the generic runtime; -- preserve scan and runtime-acceptance gates before publication; -- preserve the existing Compose environment until a replacement is explicitly validated. +For maintainers: [version discovery, security gates, and publication workflows](maintenance.md). diff --git a/docs/maintenance.md b/docs/maintenance.md new file mode 100644 index 0000000..550cad6 --- /dev/null +++ b/docs/maintenance.md @@ -0,0 +1,96 @@ +# Image maintenance and release automation + +This document is for contributors maintaining this repository's Nextcloud images and GitHub Actions workflows. For user-facing image tags and production upgrade guidance, see [Container images](images.md). For the example Compose installation, see [Deployment](deployment.md). + +## Release channels and build sources + +The application uses one `.docker/app/Dockerfile` for stable releases and development builds. Its source modes are separate Docker build stages: + +- `NEXTCLOUD_SOURCE=release` keeps the Nextcloud payload from the selected official base image. +- `NEXTCLOUD_SOURCE=daily` replaces it with a verified Nextcloud Server daily archive. + +The base PHP runtime and Nextcloud server payload are separate inputs. The development channel therefore follows upstream `master` without being tied to the next numbered major. + +For daily builds, `NEXTCLOUD_DAILY_URL` identifies the upstream archive. The Dockerfile fetches its SHA-512 checksum and prepares the payload for the official Nextcloud entrypoint. The workflow also records the upstream base image digest and archive checksum for traceability. + +The generic image retains required runtime dependencies such as Poppler, UTF-8 locale support, and the PHP bz2 extension. Avoid reinstalling extensions already provided by the official Nextcloud base. Keep LibreSign-specific behavior outside this generic runtime. + +The repository Compose example retains `NEXTCLOUD_VERSION` as its user-facing setting for the **locally built official base image**. It does not control the GHCR publication matrix. + +## Reviewed stable-version inventory + +The file `.github/nextcloud-supported.json` is the authoritative **reviewed** inventory for stable-image CI. Its entries contain a major, the current stable patch release, and an EOL month, along with the upstream source and schema version. + +The [official Nextcloud Server maintenance wiki](https://github.com/nextcloud/server/wiki/Maintenance-and-Release-Schedule) is the primary source for determining supported majors. The watcher is not driven by the `nextcloud/docker` repository's image-retention list or by third-party EOL services. + +The `Discover maintained Nextcloud versions` workflow runs twice a day and on manual dispatch. It uses `scripts/nextcloud-versions.sh` and `scripts/parse-nextcloud-wiki.awk` to: + +1. Extract the **Currently supported/maintained versions** table and cross-check the **EOL Versions** table. +2. Reject missing headings, incompatible table structures, duplicate majors, expired EOL months, future release dates, and invalid release numbers. +3. Verify each candidate against an official stable `nextcloud-releases/server` release and the exact `nextcloud:-fpm` Docker Hub tag on AMD64 and ARM64. +4. Write the candidate JSON only when validation succeeds. + +This job does **not** directly publish or alter production images. + +## Reviewing a proposed version change + +If the candidate JSON differs from the reviewed file, the watcher proposes a pull request on `automation/nextcloud-supported-versions`. + +Review the upstream changes, the proposed additions and removals, the supported PHP/Nextcloud combinations, and the release/EOL transition **before merging**. The bot does not auto-merge. + +If a wiki edit cannot be parsed or the upstream sources disagree, the discovery job fails with a GitHub Actions error and job summary. **No JSON change or PR is created on failure** and the existing reviewed inventory is left intact. Enable workflow-failure notifications; a failed schedule should be visible. + +The repository or organization must allow GitHub Actions to create pull requests. Because pushes made with `GITHUB_TOKEN` do not trigger ordinary PR workflows, the watcher explicitly dispatches the stable-image workflow on its PR branch. Workflow dispatches outside `main` validate without publishing. + +**Signing policy:** the current watcher uses DCO sign-off and GitHub Actions bot commit signing. The Git Signing MCP is used for contributor commits, but is **not yet integrated with the watcher**. Review that limitation before enabling automated commits under a strict signing policy. + +## Stable builds and promotion + +After review and merge, `.github/workflows/docker-image.yml` loads the committed JSON to create the matrix. It builds each approved `nextcloud:-fpm` on AMD64 and ARM64 and builds the web image once. + +Trivy vulnerability checks and Bats runtime acceptance are publication gates. Successful jobs publish architecture-specific staging tags. Only when all required matrix jobs pass does `scripts/publish-images.sh` promote the multiarch manifests. + +Publication produces: + +- `:-fpm` for every approved supported major; +- `:-fpm` for the tested patch release; +- `:stable-fpm` and compatibility `:latest` for the **highest approved major**, without a hardcoded major number; +- `:main` and compatibility `:latest` for the independent web image. + +Expired EOL months in the approved JSON stop the release workflow instead of silently rebuilding an unsupported version. Existing EOL tags are **not deleted automatically**; they stop being rebuilt after the reviewed inventory changes. + +Stable publication runs on pushes to `main`, a weekly rebuild schedule, or manual dispatch on `main`. Pull requests and non-`main` dispatches validate without publishing. + +## Development image `master-fpm` + +The separate `.github/workflows/nextcloud-development.yml` workflow follows the latest upstream Nextcloud Server master archive. It builds and scans AMD64 and ARM64 images, runs runtime acceptance, and promotes `:master-fpm` when its release gates pass. + +This development channel is separate from the stable JSON and must not be labeled as a numbered, stable Nextcloud major. Its architecture-specific staging tags are internal publication details. + +## Testing and diagnostics + +The repository uses Bash, AWK, `jq`, and Bats rather than a separate Python implementation. + +```bash +make test-ci-scripts +make test-development-image-scripts +bash tests/test-scan-images.sh +make test-app-image APP_IMAGE=scan/app:amd64 +make test-current-app-image +``` + +The wiki parser regression tests include real historical snapshots from 2023 to 2026, a malformed revision, overlapping EOL entries, and a release listed before publication. Publication tests simulate Docker calls so they do not push to the registry. + +The runtime acceptance test uses an isolated Docker network and PostgreSQL container, verifies Nextcloud installation and `occ` status, and checks the PHP-FPM interface. Test-created resources are cleaned up. CI must run these tests against the images actually built by the workflow. + +For YAML syntax, Shell scripts, and publication changes, use `actionlint`, `ShellCheck`, and the Bats suite before requesting review. Do not weaken vulnerability gates to pass CI. + +## Other maintenance decisions + +Image metadata should use standard OCI labels wherever possible, including the source, revision, creation time, and version or channel. Record the resolved upstream revision or base image where the source permits. OCI digests are the immutable deployment reference; a commit-based staging tag can be rebuilt and is not a substitute. + +Keep tag naming consistent with the official Nextcloud model. Do not introduce `:dev`, `:dev-`, bare `:`, or `:nc-` as new public conventions. The legacy development tag `:35` is not a required compatibility alias; see the consumer analysis in #52. + +Dependabot manages Docker and GitHub Actions dependencies. Renovate is limited to regex-managed values Dependabot cannot discover (currently the explicitly pinned Trivy binary version), so the two bots do not manage the same dependency. + +The remaining implementation constraints tracked under #47 are to improve metadata when upstream identifiers become available, keep the stable and master publishing contracts aligned, preserve runtime and security gates, and maintain deployment compatibility until replacement approaches are validated. diff --git a/scripts/nextcloud-versions.sh b/scripts/nextcloud-versions.sh new file mode 100644 index 0000000..49e8933 --- /dev/null +++ b/scripts/nextcloud-versions.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# Reviewable Nextcloud stable-version inventory. Requires curl, awk and jq. +set -euo pipefail + +source_url=https://github.com/nextcloud/server/wiki/Maintenance-and-Release-Schedule +wiki_url=https://raw.githubusercontent.com/wiki/nextcloud/server/Maintenance-and-Release-Schedule.md +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +manifest="$repo_root/.github/nextcloud-supported.json" +command_name=${1:?Usage: nextcloud-versions.sh discover|matrix [options]} +shift +wiki_file='' +github_output='' +skip_upstream=false +today=$(date -u +%F) + +error() { + local message=$1 + printf '::error title=Nextcloud version discovery failed::%s\n' "$message" >&2 + if [[ -n ${GITHUB_STEP_SUMMARY:-} ]]; then + printf '**Nextcloud version discovery FAILED:** %s. Existing manifest was preserved.\n' "$message" >> "$GITHUB_STEP_SUMMARY" + fi + exit 1 +} +trap 'error "Unexpected failure at line $LINENO (exit $?)"' ERR + +while (( $# )); do + case "$1" in + --file|--wiki-file|--github-output|--today) + (( $# >= 2 )) || error "Missing argument for $1" + case "$1" in + --file) manifest=$2 ;; + --wiki-file) wiki_file=$2 ;; + --github-output) github_output=$2 ;; + --today) today=$2 ;; + esac + shift 2 + ;; + --skip-upstream-check) skip_upstream=true; shift ;; + *) error "Unknown option: $1" ;; + esac +done + +[[ $today =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || error "Invalid date: $today" + +# Validate the committed JSON before it can reach the build matrix or tag promotion. +validate_manifest() { + local path=$1 + jq -e --arg source "$source_url" --arg month "${today:0:7}" ' + . as $root | + ($root | type == "object") and + (($root | keys) == ["schema_version","source","versions"]) and + ($root.schema_version == 1) and ($root.source == $source) and + ($root.versions | type == "array" and length >= 1 and length <= 5) and + ($root.versions | all(.[]; + type == "object" and + (keys == ["eol","major","release"]) and + (.major | type == "number" and . >= 20 and . <= 99 and . == floor) and + (.release | type == "string" and test("^[0-9]{2,3}[.][0-9]+[.][0-9]+$")) and + ((.release | split(".")[0] | tonumber) == .major) and + (.eol | type == "string" and test("^20[0-9]{2}-(0[1-9]|1[0-2])$") and . >= $month) + )) and + ([$root.versions[].major] as $majors | + ($majors == ($majors | unique | sort)) and + all(range(1; ($majors | length)); . as $i | $majors[$i] == ($majors[$i-1] + 1))) + ' "$path" > /dev/null || error "Invalid or EOL approved manifest: $path" +} + +request() { + curl --fail --silent --show-error --location --retry 2 --max-time 30 \ + -H 'User-Agent: librecode-nextcloud-version-discovery' "$@" +} + +verify_release() { + local release=$1 github_tag docker_tag headers=() + if [[ -n ${GITHUB_TOKEN:-} ]]; then + headers=(-H "Authorization: Bearer $GITHUB_TOKEN") + fi + github_tag=$(request "${headers[@]}" \ + "https://api.github.com/repos/nextcloud-releases/server/releases/tags/v${release}") + jq -e --arg release "v$release" ' + .tag_name == $release and .prerelease == false and .draft == false + ' <<< "$github_tag" > /dev/null || error "No official stable GitHub release v$release" + + docker_tag=$(request "https://hub.docker.com/v2/repositories/library/nextcloud/tags/${release}-fpm") + jq -e --arg tag "${release}-fpm" ' + .name == $tag and + (.images | any(.[]; .os == "linux" and .architecture == "amd64")) and + (.images | any(.[]; .os == "linux" and .architecture == "arm64")) + ' <<< "$docker_tag" > /dev/null || error "Official image nextcloud:${release}-fpm lacks amd64 or arm64" +} + +case "$command_name" in + matrix) + validate_manifest "$manifest" + matrix=$(jq -c '{include: [.versions[] | {major, tag: ((.major | tostring) + "-fpm"), release}]}' "$manifest") + if [[ -n $github_output ]]; then + printf 'matrix=%s\n' "$matrix" >> "$github_output" + else + printf '%s\n' "$matrix" + fi + printf 'Approved Nextcloud versions: %s\n' "$(jq -r '[.versions[].major | tostring] | join(", ")' "$manifest")" >&2 + ;; + discover) + tmpdir=$(mktemp -d) + trap 'rm -rf "$tmpdir"' EXIT + if [[ -n $wiki_file ]]; then + cp -- "$wiki_file" "$tmpdir/wiki.md" + else + request "$wiki_url" > "$tmpdir/wiki.md" + fi + + awk -f "$repo_root/scripts/parse-nextcloud-wiki.awk" "$tmpdir/wiki.md" > "$tmpdir/releases.tsv" + while IFS=$'\t' read -r major release eol released; do + [[ $eol =~ ^20[0-9]{2}-(0[1-9]|1[0-2])$ && $eol > "${today:0:7}" || $eol == "${today:0:7}" ]] \ + || error "Nextcloud $major is EOL or has invalid EOL month: $eol" + [[ $released =~ ^20[0-9]{2}-[0-9]{2}-[0-9]{2}$ && $released < $today || $released == "$today" ]] \ + || error "Nextcloud $major release date is invalid or in the future: $released" + [[ $(date -u -d "$released" +%F 2>/dev/null) == "$released" ]] || error "Invalid release date: $released" + if [[ $skip_upstream == false ]]; then verify_release "$release"; fi + done < "$tmpdir/releases.tsv" + + jq -Rn --arg source "$source_url" ' + {schema_version: 1, source: $source, + versions: ([inputs | split("\t") | + {major: (.[0] | tonumber), release: .[1], eol: .[2]}] | sort_by(.major))} + ' < "$tmpdir/releases.tsv" > "$tmpdir/manifest.json" + + validate_manifest "$tmpdir/manifest.json" + if [[ ! -f $manifest ]] || ! cmp -s "$tmpdir/manifest.json" "$manifest"; then + cp -- "$tmpdir/manifest.json" "$manifest" + status=updated + else + status=unchanged + fi + printf 'Nextcloud discovery: %s; manifest %s\n' \ + "$(jq -r '[.versions[].major | tostring] | join(", ")' "$manifest")" "$status" + if [[ -n ${GITHUB_STEP_SUMMARY:-} ]]; then + printf 'Nextcloud discovery: **%s**, manifest **%s**. Source: %s\n' \ + "$(jq -r '[.versions[].major | tostring] | join(", ")' "$manifest")" "$status" "$source_url" >> "$GITHUB_STEP_SUMMARY" + fi + ;; + *) error "Expected discover or matrix, received $command_name" ;; +esac diff --git a/scripts/parse-nextcloud-wiki.awk b/scripts/parse-nextcloud-wiki.awk new file mode 100644 index 0000000..4f95f7f --- /dev/null +++ b/scripts/parse-nextcloud-wiki.awk @@ -0,0 +1,109 @@ +# Extract the supported-version table and reject conflicting EOL entries. +# Output: majorreleaseeol-monthrelease-date +function trim(value) { + gsub(/\r/, "", value) + gsub(/\*\*/, "", value) + gsub(sprintf("%c", 96), "", value) + sub(/^[[:space:]]+/, "", value) + sub(/[[:space:]]+$/, "", value) + return value +} +function columns(line, out, count, field_index, raw) { + line = trim(line) + sub(/^\|/, "", line) + sub(/\|$/, "", line) + count = split(line, raw, "|") + for (field_index = 1; field_index <= count; field_index++) { + out[field_index] = trim(raw[field_index]) + } + return count +} +function failure(message) { + print "Nextcloud wiki parser: " message > "/dev/stderr" + invalid = 1 + exit 1 +} +BEGIN { section = ""; state = 0; supported = 0; eol_rows = 0 } +{ + heading = trim($0) + if (heading ~ /^#+[[:space:]]+/) { + sub(/^#+[[:space:]]+/, "", heading) + heading = tolower(heading) + if (heading ~ /^currently (supported|maintained) versions?$/) { + if (supported_section++) failure("duplicate supported section") + section = "supported" + state = 0 + } else if (heading == "eol versions") { + section = "eol" + state = 0 + eol_section++ + } else { + section = "" + } + next + } + if (section == "") next + if ($0 !~ /^[[:space:]]*\|/) { + if (state == 2) state = 3 + next + } + if (state == 3) next + + count = columns($0, parts) + if (state == 0) { + if (section == "supported") { + delete field_index + for (i = 1; i <= count; i++) field_index[tolower(parts[i])] = i + if (!("version code" in field_index) || !("release date" in field_index) || + !("end of life" in field_index) || !("current version" in field_index)) { + failure("supported table header changed") + } + } else if (tolower(parts[1]) != "version code") { + failure("EOL table header changed") + } + header_columns = count + state = 1 + next + } + if (state == 1) { + if (count != header_columns) failure("separator column count changed") + for (i = 1; i <= count; i++) { + if (parts[i] !~ /^:?-{3,}:?$/) failure("invalid Markdown table separator") + } + state = 2 + next + } + if (section == "supported" && count != header_columns) { + failure("supported table row column count changed") + } + if (section == "supported") { + major = parts[field_index["version code"]] + current = parts[field_index["current version"]] + split(current, release_fields, /[[:space:]]+/) + release = release_fields[1] + if (major !~ /^[0-9]+$/ || release !~ /^[0-9]+\.[0-9]+\.[0-9]+$/) { + failure("invalid current release or major " major) + } + if (int(major) != int(release) || int(major) < 20 || int(major) > 99) { + failure("major does not match current release " release) + } + if (seen[major]++) failure("duplicate supported major " major) + supported++ + versions[supported] = major "\t" release "\t" parts[field_index["end of life"]] "\t" parts[field_index["release date"]] + supported_majors[major] = 1 + } else if (section == "eol" && parts[1] ~ /^[0-9]+$/) { + eol_majors[parts[1]] = 1 + eol_rows++ + } +} +END { + if (invalid) exit 1 + if (supported_section != 1 || supported < 1 || supported > 5 || + eol_section != 1 || eol_rows < 1) { + failure("supported/EOL sections or their version tables are missing") + } + for (major in supported_majors) { + if (major in eol_majors) failure("version " major " is both supported and EOL") + } + for (i = 1; i <= supported; i++) print versions[i] +} diff --git a/scripts/publish-images.sh b/scripts/publish-images.sh new file mode 100644 index 0000000..000a757 --- /dev/null +++ b/scripts/publish-images.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Publish only scanned images; moving tags wait for every CI matrix job to pass. +set -euo pipefail +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +fail() { + echo "::error title=Container publication failed::$1" >&2 + exit 2 +} +validate_image() { + [[ $1 =~ ^[a-z0-9.-]+(:[0-9]+)?(/[a-z0-9._-]+)+$ ]] || fail "Invalid image name: $1" +} +validate_revision() { + [[ $1 =~ ^[0-9a-f]{40}$ ]] || fail "Expected a full 40-digit Git SHA" +} +stage() { + local image=$1 revision=$2 variant=$3 local_component tag arch + validate_image "$image" + validate_revision "$revision" + if [[ $variant == web ]]; then + local_component=web + else + [[ $variant =~ ^[2-9][0-9]-fpm$ ]] || fail "Invalid Nextcloud channel: $variant" + local_component=app + fi + for arch in amd64 arm64; do + tag="sha-${revision}-${variant}-${arch}" + bash "$script_dir/push-architecture.sh" "$image" "scan/${local_component}:${arch}" "$tag" + done +} +publish() { + local image=$1 tag=$2 prefix=$3 + bash "$script_dir/publish-manifest.sh" "$image" "$tag" \ + "${image}:${prefix}-amd64" "${image}:${prefix}-arm64" +} +promote() { + local app_image=$1 web_image=$2 revision=$3 manifest_file=$4 + local major release prefix latest_prefix + validate_image "$app_image" + validate_image "$web_image" + validate_revision "$revision" + + # Independently enforce the reviewed schema and EOL policy before any pushes. + bash "$script_dir/nextcloud-versions.sh" matrix --file "$manifest_file" > /dev/null + while IFS=$'\t' read -r major release; do + prefix="sha-${revision}-${major}-fpm" + publish "$app_image" "${major}-fpm" "$prefix" + publish "$app_image" "${release}-fpm" "$prefix" + latest_prefix=$prefix + done < <(jq -r '.versions[] | [.major, .release] | @tsv' "$manifest_file") + + [[ -n ${latest_prefix:-} ]] || fail "No approved stable Nextcloud major" + publish "$app_image" stable-fpm "$latest_prefix" + publish "$app_image" latest "$latest_prefix" + + publish "$web_image" main "sha-${revision}-web" + publish "$web_image" latest "sha-${revision}-web" +} + +case ${1:-} in + stage-app) + (( $# == 4 )) || fail 'Usage: stage-app ' + stage "$2" "$3" "$4" + ;; + stage-web) + (( $# == 3 )) || fail 'Usage: stage-web ' + stage "$2" "$3" web + ;; + promote) + (( $# == 5 )) || fail 'Usage: promote ' + promote "$2" "$3" "$4" "$5" + ;; + *) fail 'Expected stage-app, stage-web or promote' ;; +esac diff --git a/scripts/publish-manifest.sh b/scripts/publish-manifest.sh new file mode 100644 index 0000000..a9532ed --- /dev/null +++ b/scripts/publish-manifest.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# Common multi-platform manifest publisher for master and stable channels. +set -euo pipefail + +image=${1:?Usage: publish-manifest.sh } +tag=${2:?Missing target tag} +amd64=${3:?Missing amd64 staging image} +arm64=${4:?Missing arm64 staging image} +(( $# == 4 )) || { echo 'Expected exactly two architecture sources' >&2; exit 2; } + +docker buildx imagetools create --tag "${image}:${tag}" "$amd64" "$arm64" +docker buildx imagetools inspect "${image}:${tag}" diff --git a/scripts/publish-master-manifest.sh b/scripts/publish-master-manifest.sh index 47f3464..1c90a4f 100644 --- a/scripts/publish-master-manifest.sh +++ b/scripts/publish-master-manifest.sh @@ -1,13 +1,9 @@ #!/usr/bin/env bash - set -euo pipefail app_image=${1:?Usage: publish-master-manifest.sh } -revision=${2:?Usage: publish-master-manifest.sh } +revision=${2:?Missing revision} -docker buildx imagetools create \ - --tag "${app_image}:master-fpm" \ +bash "$(dirname "$0")/publish-manifest.sh" "$app_image" master-fpm \ "${app_image}:master-fpm-${revision}-amd64" \ "${app_image}:master-fpm-${revision}-arm64" - -docker buildx imagetools inspect "${app_image}:master-fpm" diff --git a/scripts/push-architecture.sh b/scripts/push-architecture.sh new file mode 100644 index 0000000..9cf5cf7 --- /dev/null +++ b/scripts/push-architecture.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Reuse the same tested push sequence for release and development channels. +set -euo pipefail + +image=${1:?Usage: push-architecture.sh } +source_image=${2:?Missing source image} +staging_tag=${3:?Missing staging tag} +(( $# == 3 )) || exit 2 + +docker tag "$source_image" "${image}:${staging_tag}" +docker push "${image}:${staging_tag}" diff --git a/scripts/push-master-architecture.sh b/scripts/push-master-architecture.sh index 6268c2c..fe573cc 100644 --- a/scripts/push-master-architecture.sh +++ b/scripts/push-master-architecture.sh @@ -1,20 +1,15 @@ #!/usr/bin/env bash - set -euo pipefail app_image=${1:?Usage: push-master-architecture.sh } -source_tag=${2:?Usage: push-master-architecture.sh } -revision=${3:?Usage: push-master-architecture.sh } -arch=${4:?Usage: push-master-architecture.sh } +source_tag=${2:?Missing source image} +revision=${3:?Missing revision} +arch=${4:?Missing architecture} -case "${arch}" in +case "$arch" in amd64|arm64) ;; - *) - echo "Unsupported architecture: ${arch}" >&2 - exit 2 - ;; + *) echo "Unsupported architecture: $arch" >&2; exit 2 ;; esac -staging_tag="master-fpm-${revision}-${arch}" -docker tag "${source_tag}" "${app_image}:${staging_tag}" -docker push "${app_image}:${staging_tag}" +bash "$(dirname "$0")/push-architecture.sh" \ + "$app_image" "$source_tag" "master-fpm-${revision}-${arch}" diff --git a/tests/fixtures/2023-maintained.md b/tests/fixtures/2023-maintained.md new file mode 100644 index 0000000..ad1b929 --- /dev/null +++ b/tests/fixtures/2023-maintained.md @@ -0,0 +1,28 @@ +## Currently maintained versions + +| Version code | Version name | Release date | End of life | Current version | Next version +|:------------:|:------------:|:------------------|-----------------:|-------------------------:|-----------------------: +| **27** | Hub 5 | 2023-06-13 | 2024-06 | **27.0.0** (2023-06-13) | 27.0.1 (2023-07-20) +| **26** | Hub 4 | 2023-03-21 | 2024-03 | **26.0.3** (2023-06-22) | 26.0.4 (2023-07-20) +| **25** | Hub 3 | 2022-10-19 | 2023-10 | **25.0.8** (2023-06-22) | 25.0.9 (2023-07-20) + +Older version have reached their **End of Life** and do not receive any updates anymore. + +For more details about the planning check out the [documentation page](https://docs.nextcloud.com/server/stable/admin_manual/release_schedule.html). + +## EOL Versions + + +| Version code | Version name | Release date | End of life | Current version | Next version +|:------------:|:------------:|:------------------|-----------------:|-------------------------:|-----------------------: +| **24** | Hub 3 | 2022-05-03 | 2023-04 | 24.0.12 (2023-04-20) | **End of Life** +| **23** | Hub 2 | 2021-11-30 | 2022-12 | 23.0.12 (2022-12-08) | **End of Life** +| **22** | Hub | 2021-07-06 | 2022-07 | 22.2.10 (2022-07-18) | **End of Life** +| **21** | Hub | 2021-02-22 | 2022-02 | 21.0.9 (2022-02-15) | **End of Life** +| **20** | Hub | 2020-10-03 | 2021-11 | 20.0.14 (2021-11-11) | **End of Life** +| **19** | Hub | 2020-06-03 | 2021-06 | 19.0.13 (2021-07-01) | **End of Life** +| **18** | Hub | 2020-01-16 | 2021-01 | 18.0.14 (2021-01-27) | **End of Life** +| **17** | 17 | 2019-09-30 | 2020-10 | 17.0.10 (2020-10-08) | **End of Life** +| **16** | 16 | 2019-04-25 | 2020-06 | 16.0.11 (2020-06-04) | **End of Life** +| **15** | 15 | 2018-12-10 | 2019-12 | 15.0.14 (2019-12-19) | **End of Life** +| **14** | 14 | 2018-09-10 | 2019-09 | 14.0.14 (2019-08-15) | **End of Life** diff --git a/tests/fixtures/2024-maintained.md b/tests/fixtures/2024-maintained.md new file mode 100644 index 0000000..38df13e --- /dev/null +++ b/tests/fixtures/2024-maintained.md @@ -0,0 +1,30 @@ +## Currently maintained versions + +| Version code | Version name | Release date | End of life | Current version | Next version +|:------------:|:------------:|:------------------|-----------------:|-------------------------:|-------------------------: +| **29** | Hub 8 | 2024-04-24 | 2025-04 | **29.0.3** (2024-06-25) | **29.0.4** (2024-07-18) +| **28** | Hub 7 | 2023-12-12 | 2024-12 | **28.0.7** (2024-06-25) | **28.0.8** (2024-07-18) + + +📣 Hub 6 (27.1.x) is the successor to Hub 5 (27.0.x) + +Older version have reached their **End of Life** and do not receive any updates anymore. + +For more details about the planning check out the [documentation page](https://docs.nextcloud.com/server/stable/admin_manual/release_schedule.html). + +## EOL Versions + +| Version code | Version name | Release date | End of life | Current version | Next version +|:------------:|:------------:|:------------------|-----------------:|-------------------------:|-----------------------: +| **27** | Hub 6 | 2023-06-13 | 2024-06 | 27.1.11 (2024-06-25) | **End of Life** +| **26** | Hub 4 | 2023-03-21 | 2024-03 | 26.0.13 (2024-03-28) | **End of Life** +| **25** | Hub 3 | 2022-10-19 | 2023-10 | 25.0.13 (2023-10-26) | **End of Life** +| **24** | Hub 3 | 2022-05-03 | 2023-04 | 24.0.12 (2023-04-20) | **End of Life** +| **23** | Hub 2 | 2021-11-30 | 2022-12 | 23.0.12 (2022-12-08) | **End of Life** +| **22** | Hub | 2021-07-06 | 2022-07 | 22.2.10 (2022-07-18) | **End of Life** +| **21** | Hub | 2021-02-22 | 2022-02 | 21.0.9 (2022-02-15) | **End of Life** +| **20** | Hub | 2020-10-03 | 2021-11 | 20.0.14 (2021-11-11) | **End of Life** +| **19** | Hub | 2020-06-03 | 2021-06 | 19.0.13 (2021-07-01) | **End of Life** +| **18** | Hub | 2020-01-16 | 2021-01 | 18.0.14 (2021-01-27) | **End of Life** +| **17** | 17 | 2019-09-30 | 2020-10 | 17.0.10 (2020-10-08) | **End of Life** +| **16** | 16 | 2019-04-25 | 2020-06 | 16.0.11 (2020-06-04) | **End of Life** diff --git a/tests/fixtures/2024-malformed.md b/tests/fixtures/2024-malformed.md new file mode 100644 index 0000000..5537640 --- /dev/null +++ b/tests/fixtures/2024-malformed.md @@ -0,0 +1,22 @@ +## Currently supported versions + +Older versions have reached their **End of Life** and do not receive any updates anymore. + +For more details about the planning check out the [documentation page](https://docs.nextcloud.com/server/stable/admin_manual/release_schedule.html). + +## EOL Versions + +📣 Hub 6 (27.1.x) was the successor to Hub 5 (27.0.x) + +| Version code | Version name | Release date | End of life | Current version | Next version +|:------------:|:------------:|:------------------|-----------------:|-------------------------:|-----------------------: +| **27** | Hub 6 | 2023-06-13 | 2024-06 | 27.1.11 (2024-06-25) | **End of Life** +| **26** | Hub 4 | 2023-03-21 | 2024-03 | 26.0.13 (2024-03-28) | **End of Life** +| **25** | Hub 3 | 2022-10-19 | 2023-10 | 25.0.13 (2023-10-26) | **End of Life** +| **24** | Hub 3 | 2022-05-03 | 2023-04 | 24.0.12 (2023-04-20) | **End of Life** +| **23** | Hub 2 | 2021-11-30 | 2022-12 | 23.0.12 (2022-12-08) | **End of Life** +| **22** | Hub | 2021-07-06 | 2022-07 | 22.2.10 (2022-07-18) | **End of Life** +| **21** | Hub | 2021-02-22 | 2022-02 | 21.0.9 (2022-02-15) | **End of Life** +| **20** | Hub | 2020-10-03 | 2021-11 | 20.0.14 (2021-11-11) | **End of Life** +| **19** | Hub | 2020-06-03 | 2021-06 | 19.0.13 (2021-07-01) | **End of Life** +| **18** | Hub | 2020-01-16 | 2021-01 | 18.0.14 (2021-01-27) | **End of Life** diff --git a/tests/fixtures/2025-eol-overlap.md b/tests/fixtures/2025-eol-overlap.md new file mode 100644 index 0000000..f087135 --- /dev/null +++ b/tests/fixtures/2025-eol-overlap.md @@ -0,0 +1,31 @@ +## Currently supported versions + +New major versions are published every four months and are supported for one year through monthly maintenance releases. Maintenance releases are published for all still supported major versions and target all known critical bugs and security problems. + +Major versions that have reached their **End of Life** no longer receive further bug fixes or security updates. + +For more details to help with planning your updates, check the [documentation page](https://docs.nextcloud.com/server/stable/admin_manual/release_schedule.html). + + +| Version code | Version name | Release date | End of life | Current version | Next version +|:------------:|:------------:|:------------------|-----------------:|--------------------------:|--------------------------: +| **31** | Hub 10 | 2025-02-25 | 2026-02 | **31.0.3** (2025-04-10) | **31.0.4** (2025-04-17) +| **30** | Hub 9 | 2024-09-14 | 2025-09 | **30.0.9** (2025-04-10) | **30.0.10** (2025-04-17) +| **29** | Hub 8 | 2024-04-24 | 2025-04 | **29.0.15** (2025-04-10) | **29.0.16** (2025-04-17) + +## EOL Versions + +📣 Hub 6 (27.1.x) was the successor to Hub 5 (27.0.x) + +| Version code | Version name | Release date | End of life | Current version | Next version +|:------------:|:------------:|:------------------|-----------------:|-------------------------:|-----------------------: +| **29** | Hub 8 | 2024-04-24 | 2025-04 | 29.0.15 (2025-04-10) | **End of Life** +| **28** | Hub 7 | 2023-12-12 | 2024-12 | 28.0.14 (2024-12-12) | **End of Life** +| **27** | Hub 6 | 2023-06-13 | 2024-06 | 27.1.11 (2024-06-25) | **End of Life** +| **26** | Hub 4 | 2023-03-21 | 2024-03 | 26.0.13 (2024-03-28) | **End of Life** +| **25** | Hub 3 | 2022-10-19 | 2023-10 | 25.0.13 (2023-10-26) | **End of Life** +| **24** | Hub 3 | 2022-05-03 | 2023-04 | 24.0.12 (2023-04-20) | **End of Life** +| **23** | Hub 2 | 2021-11-30 | 2022-12 | 23.0.12 (2022-12-08) | **End of Life** +| **22** | Hub | 2021-07-06 | 2022-07 | 22.2.10 (2022-07-18) | **End of Life** +| **21** | Hub | 2021-02-22 | 2022-02 | 21.0.9 (2022-02-15) | **End of Life** +| **20** | Hub | 2020-10-03 | 2021-11 | 20.0.14 (2021-11-11) | **End of Life** diff --git a/tests/fixtures/2025-eol-structure.md b/tests/fixtures/2025-eol-structure.md new file mode 100644 index 0000000..e60bbae --- /dev/null +++ b/tests/fixtures/2025-eol-structure.md @@ -0,0 +1,92 @@ +## Currently supported versions + +New major versions are published every four months and are supported for one year through monthly maintenance releases. Maintenance releases are published for all still supported major versions and target all known critical bugs and security problems. + +Major versions that have reached their **End of Life** no longer receive further bug fixes or security updates. + +For more details to help with planning your updates, check the [documentation page](https://docs.nextcloud.com/server/stable/admin_manual/release_schedule.html). + + +| Version code | Version name | Release date | End of life | Current version | Next version +|:------------:|:-------------:|:------------------|-----------------:|-------------------------:|--------------------------: +| **32** | Hub 25 Autumn | 2025-09-27 | 2026-09 | **32.0.1** (2025-10-23) | **32.0.2** (2025-11-20) +| **31** | Hub 10 | 2025-02-25 | 2026-02 | **31.0.10** (2025-10-23) | **31.0.11** (2025-11-20) + +```mermaid + %%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%% + timeline + Version code
(major) + : Version name + : Initial release + : Supported Until + : Latest release + : Latest release date + : Next release + 31 : Hub 10 : 25 Feb 2025 : 24 Feb 2026 : 31.0.10 : 23 October 2025 : 20 November 2025 + 32 : Hub 25 Autumn : 27 Sep 2025 : 26 Sep 2026 : 32.0.1 : 23 October 2025 : 20 November 2025 +``` + + + + +## EOL Versions + +📣 Hub 6 (27.1.x) was the successor to Hub 5 (27.0.x) + +| Version code | Version name | Release date | End of life | Last version +|:------------:|:------------:|:------------------|-----------------:|-------------------------: +| **30** | Hub 9 | 2024-09-11 | 2025-10 | 30.0.17 (2025-10-23) +| **29** | Hub 8 | 2024-04-24 | 2025-04 | 29.0.16 (2025-04-17) +| **28** | Hub 7 | 2023-12-12 | 2024-12 | 28.0.14 (2024-12-12) +| **27** | Hub 6 | 2023-06-13 | 2024-06 | 27.1.11 (2024-06-25) +| **26** | Hub 4 | 2023-03-21 | 2024-03 | 26.0.13 (2024-03-28) +| **25** | Hub 3 | 2022-10-19 | 2023-10 | 25.0.13 (2023-10-26 +| **24** | Hub 3 | 2022-05-03 | 2023-04 | 24.0.12 (2023-04-20) +| **23** | Hub 2 | 2021-11-30 | 2022-12 | 23.0.12 (2022-12-08) +| **22** | Hub | 2021-07-06 | 2022-07 | 22.2.10 (2022-07-18) +| **21** | Hub | 2021-02-22 | 2022-02 | 21.0.9 (2022-02-15) +| **20** | Hub | 2020-10-03 | 2021-11 | 20.0.14 (2021-11-11) +| **19** | Hub | 2020-06-03 | 2021-06 | 19.0.13 (2021-07-01) +| **18** | Hub | 2020-01-16 | 2021-01 | 18.0.14 (2021-01-27) +| **17** | 17 | 2019-09-30 | 2020-10 | 17.0.10 (2020-10-08) +| **16** | 16 | 2019-04-25 | 2020-06 | 16.0.11 (2020-06-04) +| **15** | 15 | 2018-12-10 | 2019-12 | 15.0.14 (2019-12-19) +| **14** | 14 | 2018-09-10 | 2019-09 | 14.0.14 (2019-08-15) +| **13** | 13 | 2018-02-06 | 2019-02 | 13.0.12 (2019-02-28) +| **12** | 12 | 2017-05-22 | 2018-11 | 12.0.13 (2018-11-22) +| **11** | 11 | 2016-12-13 | 2018-03 | 11.0.8 (2018-03-15) +| **10** | 10 | 2016-08-25 | 2017-08 | 10.0.6 (2017-08-07) +| **9** | 9 | 2016-08-03 | 2017-08 | 9.0.58 (2017-04-24) + +--- + +```mermaid +gantt + + title Currently Supported Versions + axisFormat %e %b %Y + todayMarker stroke-width:5px,stroke:#0f0,opacity:0.5,content: "Today" + + section Latest + Nextcloud 32 :crit,active, v32, 2025-09-27, 365d + + section Previous + Nextcloud 31 :active, v30, 2025-02-25, 365d +``` diff --git a/tests/fixtures/2025-supported.md b/tests/fixtures/2025-supported.md new file mode 100644 index 0000000..cf19c8f --- /dev/null +++ b/tests/fixtures/2025-supported.md @@ -0,0 +1,30 @@ +## Currently supported versions + +New major versions are published every four months and are supported for one year through monthly maintenance releases. Maintenance releases are published for all still supported major versions and target all known critical bugs and security problems. + +Major versions that have reached their **End of Life** no longer receive further bug fixes or security updates. + +For more details to help with planning your updates, check the [documentation page](https://docs.nextcloud.com/server/stable/admin_manual/release_schedule.html). + + +| Version code | Version name | Release date | End of life | Current version | Next version +|:------------:|:------------:|:------------------|-----------------:|--------------------------:|--------------------------: +| **31** | Hub 10 | 2025-02-25 | 2026-02 | **31.0.6** (2025-06-12) | **31.0.7** (2025-07-10) +| **30** | Hub 9 | 2024-09-14 | 2025-09 | **30.0.12** (2025-06-12) | **30.0.13** (2025-07-10) + +## EOL Versions + +📣 Hub 6 (27.1.x) was the successor to Hub 5 (27.0.x) + +| Version code | Version name | Release date | End of life | Current version | Next version +|:------------:|:------------:|:------------------|-----------------:|-------------------------:|-----------------------: +| **29** | Hub 8 | 2024-04-24 | 2025-04 | 29.0.16 (2025-04-17) | **End of Life** +| **28** | Hub 7 | 2023-12-12 | 2024-12 | 28.0.14 (2024-12-12) | **End of Life** +| **27** | Hub 6 | 2023-06-13 | 2024-06 | 27.1.11 (2024-06-25) | **End of Life** +| **26** | Hub 4 | 2023-03-21 | 2024-03 | 26.0.13 (2024-03-28) | **End of Life** +| **25** | Hub 3 | 2022-10-19 | 2023-10 | 25.0.13 (2023-10-26) | **End of Life** +| **24** | Hub 3 | 2022-05-03 | 2023-04 | 24.0.12 (2023-04-20) | **End of Life** +| **23** | Hub 2 | 2021-11-30 | 2022-12 | 23.0.12 (2022-12-08) | **End of Life** +| **22** | Hub | 2021-07-06 | 2022-07 | 22.2.10 (2022-07-18) | **End of Life** +| **21** | Hub | 2021-02-22 | 2022-02 | 21.0.9 (2022-02-15) | **End of Life** +| **20** | Hub | 2020-10-03 | 2021-11 | 20.0.14 (2021-11-11) | **End of Life** diff --git a/tests/fixtures/2026-supported.md b/tests/fixtures/2026-supported.md new file mode 100644 index 0000000..7544afb --- /dev/null +++ b/tests/fixtures/2026-supported.md @@ -0,0 +1,31 @@ +## Currently supported versions + +New major versions are published every four months and are supported for one year through monthly maintenance releases. Maintenance releases are published for all still supported major versions and target all known critical bugs and security problems. + +Major versions that have reached their **End of Life** no longer receive further bug fixes or security updates. + +For more details to help with planning your updates, check the [documentation page](https://docs.nextcloud.com/server/stable/admin_manual/release_schedule.html). + + +| Version code | Version name | Release date | End of life | Current version | Next version +|:------------:|:-------------:|:------------------|-----------------:|-------------------------:|--------------------------: +| **35** | Hub 26 Summer | 2026-09-16 | 2027-09 | **35.0.2** (2026-10-15) | **35.0.3** (2026-11-12) +| **34** | Hub 26 Spring | 2026-06-09 | 2027-06 | **34.0.5** (2026-10-15) | **34.0.6** (2026-11-12) +| **33** | Hub 26 Winter | 2026-02-18 | 2027-02 | **33.0.10** (2026-10-15) | **33.0.11** (2026-11-12) + +## EOL Versions + +📣 Hub 6 (27.1.x) was the successor to Hub 5 (27.0.x) + +| Version code | Version name | Release date | End of life | Last version +|:------------:|:-------------:|:------------------|-----------------:|-------------------------: +| **32** | Hub 25 Autumn | 2025-09-27 | 2026-09 | 32.0.15 (2026-09-10) +| **31** | Hub 10 | 2025-02-25 | 2026-02 | 31.0.14 (2026-02-12) +| **30** | Hub 9 | 2024-09-11 | 2025-10 | 30.0.17 (2025-10-23) +| **29** | Hub 8 | 2024-04-24 | 2025-04 | 29.0.16 (2025-04-17) +| **28** | Hub 7 | 2023-12-12 | 2024-12 | 28.0.14 (2024-12-12) +| **27** | Hub 6 | 2023-06-13 | 2024-06 | 27.1.11 (2024-06-25) +| **26** | Hub 4 | 2023-03-21 | 2024-03 | 26.0.13 (2024-03-28) +| **25** | Hub 3 | 2022-10-19 | 2023-10 | 25.0.13 (2023-10-26) +| **24** | Hub 3 | 2022-05-03 | 2023-04 | 24.0.12 (2023-04-20) +| **23** | Hub 2 | 2021-11-30 | 2022-12 | 23.0.12 (2022-12-08) diff --git a/tests/fixtures/2026-unreleased.md b/tests/fixtures/2026-unreleased.md new file mode 100644 index 0000000..3e57fb8 --- /dev/null +++ b/tests/fixtures/2026-unreleased.md @@ -0,0 +1,30 @@ +## Currently supported versions + +New major versions are published every four months and are supported for one year through monthly maintenance releases. Maintenance releases are published for all still supported major versions and target all known critical bugs and security problems. + +Major versions that have reached their **End of Life** no longer receive further bug fixes or security updates. + +For more details to help with planning your updates, check the [documentation page](https://docs.nextcloud.com/server/stable/admin_manual/release_schedule.html). + + +| Version code | Version name | Release date | End of life | Current version | Next version +|:------------:|:-------------:|:------------------|-----------------:|-------------------------:|--------------------------: +| **33** | Hub 26 Winter | 2026-02-18 | 2027-02 | **---** | **33.0.0** (2026-02-18) +| **32** | Hub 25 Autumn | 2025-09-27 | 2026-09 | **32.0.6** (2026-02-12) | **32.0.7** (2026-03-26) + +## EOL Versions + +📣 Hub 6 (27.1.x) was the successor to Hub 5 (27.0.x) + +| Version code | Version name | Release date | End of life | Last version +|:------------:|:------------:|:------------------|-----------------:|-------------------------: +| **31** | Hub 10 | 2025-02-25 | 2026-02 | 31.0.14 (2026-02-12) +| **30** | Hub 9 | 2024-09-11 | 2025-10 | 30.0.17 (2025-10-23) +| **29** | Hub 8 | 2024-04-24 | 2025-04 | 29.0.16 (2025-04-17) +| **28** | Hub 7 | 2023-12-12 | 2024-12 | 28.0.14 (2024-12-12) +| **27** | Hub 6 | 2023-06-13 | 2024-06 | 27.1.11 (2024-06-25) +| **26** | Hub 4 | 2023-03-21 | 2024-03 | 26.0.13 (2024-03-28) +| **25** | Hub 3 | 2022-10-19 | 2023-10 | 25.0.13 (2023-10-26) +| **24** | Hub 3 | 2022-05-03 | 2023-04 | 24.0.12 (2023-04-20) +| **23** | Hub 2 | 2021-11-30 | 2022-12 | 23.0.12 (2022-12-08) +| **22** | Hub | 2021-07-06 | 2022-07 | 22.2.10 (2022-07-18) diff --git a/tests/ncdd.bats b/tests/ncdd.bats index b3801d9..aa899b5 100644 --- a/tests/ncdd.bats +++ b/tests/ncdd.bats @@ -41,9 +41,11 @@ teardown() { } @test "env reads the existing repository version source" { + expected_version=$(sed -n 's/^NEXTCLOUD_VERSION=//p' "$REPO_ROOT/.env.example" | head -n 1) + [ -n "$expected_version" ] run bash "$REPO_ROOT/bin/ncdd" env [ "$status" -eq 0 ] - [[ "$output" == *"NEXTCLOUD_VERSION=34-fpm"* ]] + [[ "$output" == *"NEXTCLOUD_VERSION=$expected_version"* ]] } @test "doctor exposes a machine-readable healthy state" { diff --git a/tests/nextcloud-versions.bats b/tests/nextcloud-versions.bats new file mode 100644 index 0000000..cf3bb7d --- /dev/null +++ b/tests/nextcloud-versions.bats @@ -0,0 +1,134 @@ +#!/usr/bin/env bats +# Offline regression tests for the official wiki and reviewed JSON format. +setup() { + cd "$BATS_TEST_DIRNAME/.." + TEST_ROOT=$(mktemp -d) + export TEST_ROOT + mkdir -p "$TEST_ROOT/bin" + export PATH="$TEST_ROOT/bin:$PATH" + script=scripts/nextcloud-versions.sh + inventory="$TEST_ROOT/versions.json" +} +teardown() { + rm -rf "$TEST_ROOT" +} +discover_fixture() { + bash "$script" discover --wiki-file "tests/fixtures/$1.md" \ + --today "$2" --skip-upstream-check --file "$inventory" +} +@test "historical 2023 maintained section parses three majors" { + run discover_fixture 2023-maintained 2023-07-01 + [ "$status" -eq 0 ] + [ "$(jq -r '[.versions[].major] | join(",")' "$inventory")" = 25,26,27 ] +} +@test "historical 2024 maintained section parses two majors" { + run discover_fixture 2024-maintained 2024-07-01 + [ "$status" -eq 0 ] + [ "$(jq -r '[.versions[].major] | join(",")' "$inventory")" = 28,29 ] +} +@test "historical 2025 supported section parses two majors" { + run discover_fixture 2025-supported 2025-07-01 + [ "$status" -eq 0 ] + [ "$(jq -r '[.versions[].major] | join(",")' "$inventory")" = 30,31 ] +} +@test "current supported section extracts 33, 34 and 35, without 32" { + run discover_fixture 2026-supported 2026-10-20 + [ "$status" -eq 0 ] + [ "$(jq -r '[.versions[].major] | join(",")' "$inventory")" = 33,34,35 ] + [ "$(jq -r '.versions[-1].release' "$inventory")" = 35.0.2 ] +} +@test "historical EOL formatting variations do not change the supported majors" { + run discover_fixture 2025-eol-structure 2025-10-27 + [ "$status" -eq 0 ] + [ "$(jq -r '[.versions[].major] | join(",")' "$inventory")" = 31,32 ] +} +@test "malformed wiki preserves the last approved manifest" { + echo '{"untouched":true}' > "$inventory" + run discover_fixture 2024-malformed 2024-10-24 + [ "$status" -ne 0 ] + [[ "$output" == *'::error title=Nextcloud version discovery failed::'* ]] + [ "$(cat "$inventory")" = '{"untouched":true}' ] +} +@test "upstream EOL overlap and unreleased versions fail closed" { + run discover_fixture 2025-eol-overlap 2025-04-15 + [ "$status" -ne 0 ] + [[ "$output" == *'both supported and EOL'* ]] + run discover_fixture 2026-unreleased 2026-02-12 + [ "$status" -ne 0 ] + [[ "$output" == *'invalid current release'* ]] +} +@test "renamed wiki heading fails instead of guessing" { + sed 's/Currently supported versions/Upcoming releases/' \ + tests/fixtures/2026-supported.md > "$TEST_ROOT/broken.md" + run bash "$script" discover --wiki-file "$TEST_ROOT/broken.md" \ + --skip-upstream-check --today 2026-10-20 --file "$inventory" + [ "$status" -ne 0 ] + [ ! -f "$inventory" ] +} +@test "EOL date and future release date prevent a proposal" { + run discover_fixture 2026-supported 2027-10-01 + [ "$status" -ne 0 ] + [[ "$output" == *EOL* ]] + run discover_fixture 2026-supported 2026-02-01 + [ "$status" -ne 0 ] + [[ "$output" == *EOL* || "$output" == *'in the future'* ]] +} +@test "matrix contains only approved and ordered releases" { + discover_fixture 2026-supported 2026-10-20 + run bash "$script" matrix --file "$inventory" --today 2026-10-20 \ + --github-output "$TEST_ROOT/output" + [ "$status" -eq 0 ] + jq -e '.include | map(.tag) == ["33-fpm","34-fpm","35-fpm"]' \ + <(sed 's/^matrix=//' "$TEST_ROOT/output") + jq -e '.include[-1].release == "35.0.2"' \ + <(sed 's/^matrix=//' "$TEST_ROOT/output") +} +@test "out-of-order and expired JSON is rejected" { + discover_fixture 2026-supported 2026-10-20 + jq '.versions |= reverse' "$inventory" > "$TEST_ROOT/reversed.json" + run bash "$script" matrix --file "$TEST_ROOT/reversed.json" --today 2026-10-20 + [ "$status" -ne 0 ] + jq '.versions[0].eol = "2026-09"' "$inventory" > "$TEST_ROOT/expired.json" + run bash "$script" matrix --file "$TEST_ROOT/expired.json" --today 2026-10-20 + [ "$status" -ne 0 ] +} +@test "upstream confirmation verifies release and both FPM architectures" { + cat > "$TEST_ROOT/bin/curl" <<'MOCK' +#!/usr/bin/env bash +url="${*: -1}" +case "$url" in + *api.github.com*) + tag="${url##*/}" + printf '{"tag_name":"%s","prerelease":false,"draft":false}\n' "$tag" + ;; + *hub.docker.com*) + tag="${url##*/}" + printf '{"name":"%s","images":[{"os":"linux","architecture":"amd64"},{"os":"linux","architecture":"arm64"}]}\n' "$tag" + ;; + *) exit 1 ;; +esac +MOCK + chmod +x "$TEST_ROOT/bin/curl" + run bash "$script" discover --wiki-file tests/fixtures/2026-supported.md \ + --today 2026-10-20 --file "$inventory" + [ "$status" -eq 0 ] +} +@test "missing upstream arm64 architecture rejects proposed manifest" { + cat > "$TEST_ROOT/bin/curl" <<'MOCK' +#!/usr/bin/env bash +url="${*: -1}" +case "$url" in + *api.github.com*) printf '{"tag_name":"%s","prerelease":false,"draft":false}\n' "${url##*/}" ;; + *hub.docker.com*) + printf '{"name":"%s","images":[{"os":"linux","architecture":"amd64"}]}\n' "${url##*/}" + ;; + *) exit 1 ;; +esac +MOCK + chmod +x "$TEST_ROOT/bin/curl" + run bash "$script" discover --wiki-file tests/fixtures/2026-supported.md \ + --today 2026-10-20 --file "$inventory" + [ "$status" -ne 0 ] + [[ "$output" == *'lacks amd64 or arm64'* ]] + [ ! -f "$inventory" ] +} diff --git a/tests/publish-images.bats b/tests/publish-images.bats new file mode 100644 index 0000000..f0ce99e --- /dev/null +++ b/tests/publish-images.bats @@ -0,0 +1,78 @@ +#!/usr/bin/env bats +# Docker is mocked; these tests never push real images to a registry. +setup() { + cd "$BATS_TEST_DIRNAME/.." + TEST_ROOT=$(mktemp -d) + export TEST_ROOT + mkdir -p "$TEST_ROOT/bin" + export PATH="$TEST_ROOT/bin:$PATH" + export DOCKER_COMMAND_LOG="$TEST_ROOT/docker.log" + touch "$DOCKER_COMMAND_LOG" + script=scripts/publish-images.sh + APP_IMAGE=ghcr.io/librecodecoop/nextcloud-docker-app + WEB_IMAGE=ghcr.io/librecodecoop/nextcloud-docker-web + SHA=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + INVENTORY="$TEST_ROOT/approved.json" + jq '.versions |= map(.eol = "2099-12")' \ + .github/nextcloud-supported.json > "$INVENTORY" + cat > "$TEST_ROOT/bin/docker" <<'MOCK' +#!/usr/bin/env bash +set -euo pipefail +printf '%s\n' "$*" >> "$DOCKER_COMMAND_LOG" +MOCK + chmod +x "$TEST_ROOT/bin/docker" +} +teardown() { + rm -rf "$TEST_ROOT" +} +@test "stage-app only pushes its requested architecture images" { + run bash "$script" stage-app "$APP_IMAGE" "$SHA" 34-fpm + [ "$status" -eq 0 ] + [ "$(wc -l < "$DOCKER_COMMAND_LOG")" -eq 4 ] + grep -Fx "tag scan/app:amd64 ${APP_IMAGE}:sha-${SHA}-34-fpm-amd64" "$DOCKER_COMMAND_LOG" + grep -Fx "push ${APP_IMAGE}:sha-${SHA}-34-fpm-arm64" "$DOCKER_COMMAND_LOG" +} +@test "web staging never depends on a Nextcloud major" { + run bash "$script" stage-web "$WEB_IMAGE" "$SHA" + [ "$status" -eq 0 ] + grep -Fx "tag scan/web:amd64 ${WEB_IMAGE}:sha-${SHA}-web-amd64" "$DOCKER_COMMAND_LOG" + ! grep -q '35-fpm' "$DOCKER_COMMAND_LOG" +} +@test "invalid channel, image or SHA never invokes Docker" { + run bash "$script" stage-app "$APP_IMAGE" "$SHA" '35;exit' + [ "$status" -ne 0 ] + run bash "$script" stage-app "$APP_IMAGE" invalid 35-fpm + [ "$status" -ne 0 ] + run bash "$script" stage-app 'bad;rm' "$SHA" 35-fpm + [ "$status" -ne 0 ] + [ ! -s "$DOCKER_COMMAND_LOG" ] +} +@test "publish all approved versions and newest aliases after validation" { + run bash "$script" promote "$APP_IMAGE" "$WEB_IMAGE" "$SHA" "$INVENTORY" + [ "$status" -eq 0 ] + [ "$(grep -c '^buildx imagetools create ' "$DOCKER_COMMAND_LOG")" -eq 10 ] + grep -Fx "buildx imagetools create --tag ${APP_IMAGE}:33-fpm ${APP_IMAGE}:sha-${SHA}-33-fpm-amd64 ${APP_IMAGE}:sha-${SHA}-33-fpm-arm64" "$DOCKER_COMMAND_LOG" + grep -Fx "buildx imagetools create --tag ${APP_IMAGE}:35.0.2-fpm ${APP_IMAGE}:sha-${SHA}-35-fpm-amd64 ${APP_IMAGE}:sha-${SHA}-35-fpm-arm64" "$DOCKER_COMMAND_LOG" + grep -Fx "buildx imagetools create --tag ${APP_IMAGE}:latest ${APP_IMAGE}:sha-${SHA}-35-fpm-amd64 ${APP_IMAGE}:sha-${SHA}-35-fpm-arm64" "$DOCKER_COMMAND_LOG" + grep -Fx "buildx imagetools create --tag ${WEB_IMAGE}:main ${WEB_IMAGE}:sha-${SHA}-web-amd64 ${WEB_IMAGE}:sha-${SHA}-web-arm64" "$DOCKER_COMMAND_LOG" + grep -Fx "buildx imagetools create --tag ${WEB_IMAGE}:latest ${WEB_IMAGE}:sha-${SHA}-web-amd64 ${WEB_IMAGE}:sha-${SHA}-web-arm64" "$DOCKER_COMMAND_LOG" +} +@test "latest follows Nextcloud 36 when approved, without code changes" { + jq '.versions = (.versions[1:] + [{"major":36,"release":"36.0.0","eol":"2099-12"}])' \ + "$INVENTORY" > "$TEST_ROOT/next.json" + run bash "$script" promote "$APP_IMAGE" "$WEB_IMAGE" "$SHA" "$TEST_ROOT/next.json" + [ "$status" -eq 0 ] + grep -Fx "buildx imagetools create --tag ${APP_IMAGE}:latest ${APP_IMAGE}:sha-${SHA}-36-fpm-amd64 ${APP_IMAGE}:sha-${SHA}-36-fpm-arm64" "$DOCKER_COMMAND_LOG" + ! grep -q "sha-${SHA}-33-fpm" "$DOCKER_COMMAND_LOG" +} +@test "expired inventory stops before any Docker operation" { + jq '.versions[0].eol = "2026-09"' "$INVENTORY" > "$TEST_ROOT/expired.json" + run bash "$script" promote "$APP_IMAGE" "$WEB_IMAGE" "$SHA" "$TEST_ROOT/expired.json" + [ "$status" -ne 0 ] + [ ! -s "$DOCKER_COMMAND_LOG" ] +} +@test "upstream master still uses same tested manifest publisher" { + run bash scripts/publish-master-manifest.sh "$APP_IMAGE" "$SHA" + [ "$status" -eq 0 ] + grep -Fx "buildx imagetools create --tag ${APP_IMAGE}:master-fpm ${APP_IMAGE}:master-fpm-${SHA}-amd64 ${APP_IMAGE}:master-fpm-${SHA}-arm64" "$DOCKER_COMMAND_LOG" +}