diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000000..ba57cfffec --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,88 @@ +name: Build + +on: + schedule: + - cron: "50 7 * * 1" + workflow_dispatch: + workflow_call: + +permissions: {} + +cache-mode: none + +concurrency: + group: build-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + version: + name: Read the version + permissions: + contents: read + runs-on: ubuntu-26.04 # https://github.com/actions/runner-images + timeout-minutes: 10 + outputs: + coolify: ${{ steps.version.outputs.coolify }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + with: + persist-credentials: false + + - id: version + env: + BRANCH: ${{ github.base_ref || github.ref_name }} + run: | + coolify="$(scripts/version.sh --build)" + echo "coolify=$coolify" >> "$GITHUB_OUTPUT" + + build: + name: Build ${{ matrix.platform }} + needs: version + permissions: + contents: read + runs-on: ${{ matrix.runner }} # https://github.com/actions/runner-images + timeout-minutes: 60 + strategy: + matrix: + platform: [linux/amd64, linux/arm64] + include: + - platform: linux/amd64 + arch: amd64 + runner: ubuntu-26.04 + - platform: linux/arm64 + arch: arm64 + runner: ubuntu-26.04-arm + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + with: + persist-credentials: false + + - name: Set the build arguments + id: args + run: echo "epoch=$(git log -1 --format=%ct)" >> "$GITHUB_OUTPUT" + + - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # https://github.com/docker/setup-buildx-action/releases/tag/v4.4.1 + with: + cache-binary: false + + - uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # https://github.com/docker/build-push-action/releases/tag/v7.4.0 + with: + context: . + file: docker/production/Dockerfile + platforms: ${{ matrix.platform }} + tags: coollabsio/coolify:test + outputs: type=oci,dest=${{ runner.temp }}/image,tar=false,rewrite-timestamp=true + build-args: | + COOLIFY_VERSION=${{ needs.version.outputs.coolify }} + SOURCE_DATE_EPOCH=${{ steps.args.outputs.epoch }} + + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # https://github.com/actions/upload-artifact/releases/tag/v7.0.2 + with: + name: image-${{ matrix.arch }} + path: ${{ runner.temp }}/image + if-no-files-found: error + retention-days: 1 diff --git a/.github/workflows/coolify-main-build.yml b/.github/workflows/coolify-main-build.yml deleted file mode 100644 index 2ab48a4825..0000000000 --- a/.github/workflows/coolify-main-build.yml +++ /dev/null @@ -1,122 +0,0 @@ -name: Build Coolify Main - -on: - push: - branches: ["main"] - -permissions: - contents: read - packages: write - -concurrency: - group: coolify-main-build - cancel-in-progress: false - -env: - GITHUB_REGISTRY: ghcr.io - DOCKER_REGISTRY: docker.io - IMAGE_NAME: "coollabsio/coolify" - -jobs: - build-push: - outputs: - short_sha: ${{ steps.version.outputs.short_sha }} - strategy: - matrix: - include: - - arch: amd64 - platform: linux/amd64 - runner: ubuntu-24.04 - - arch: aarch64 - platform: linux/aarch64 - runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} - steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - - name: Resolve internal version - id: version - run: | - BASE_VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php) - echo "version=${BASE_VERSION}-dev.${GITHUB_SHA::9}" >> "$GITHUB_OUTPUT" - echo "short_sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" - - - name: Login to ${{ env.GITHUB_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ${{ env.DOCKER_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and Push Image (${{ matrix.arch }}) - uses: docker/build-push-action@v6 - with: - context: . - file: docker/production/Dockerfile - platforms: ${{ matrix.platform }} - push: true - build-args: | - COOLIFY_VERSION=${{ steps.version.outputs.version }} - labels: | - org.opencontainers.image.revision=${{ github.sha }} - tags: | - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:main-${{ matrix.arch }} - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:main-${{ matrix.arch }} - - merge-manifest: - runs-on: ubuntu-24.04 - needs: build-push - steps: - - uses: docker/setup-buildx-action@v3 - - - name: Login to ${{ env.GITHUB_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ${{ env.DOCKER_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }} - env: - REGISTRY: ${{ env.GITHUB_REGISTRY }} - SHA: ${{ needs.build-push.outputs.short_sha }} - run: | - IMAGE="${REGISTRY}/${IMAGE_NAME}" - docker buildx imagetools create \ - "${IMAGE}:main-amd64" \ - "${IMAGE}:main-aarch64" \ - --tag "${IMAGE}:sha-${SHA}" \ - --tag "${IMAGE}:main" - - - name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }} - env: - REGISTRY: ${{ env.DOCKER_REGISTRY }} - SHA: ${{ needs.build-push.outputs.short_sha }} - run: | - IMAGE="${REGISTRY}/${IMAGE_NAME}" - docker buildx imagetools create \ - "${IMAGE}:main-amd64" \ - "${IMAGE}:main-aarch64" \ - --tag "${IMAGE}:sha-${SHA}" \ - --tag "${IMAGE}:main" - - - uses: sarisia/actions-status-discord@v1 - if: always() - with: - webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }} diff --git a/.github/workflows/coolify-next-build.yml b/.github/workflows/coolify-next-build.yml deleted file mode 100644 index be85ce51a7..0000000000 --- a/.github/workflows/coolify-next-build.yml +++ /dev/null @@ -1,154 +0,0 @@ -name: Build Coolify Next - -on: - push: - branches: [next] - paths-ignore: - - .github/workflows/coolify-helper.yml - - .github/workflows/coolify-helper-next.yml - - .github/workflows/pr-quality.yaml - - docker/coolify-helper/Dockerfile - - docker/testing-host/Dockerfile - - templates/** - - CHANGELOG.md - -permissions: - contents: read - packages: write - -concurrency: - group: coolify-next-build - cancel-in-progress: false - -env: - GITHUB_REGISTRY: ghcr.io - DOCKER_REGISTRY: docker.io - IMAGE_NAME: coollabsio/coolify - -jobs: - prepare: - runs-on: ubuntu-24.04 - outputs: - rc_version: ${{ steps.version.outputs.rc_version }} - short_sha: ${{ steps.version.outputs.short_sha }} - version: ${{ steps.version.outputs.version }} - steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - - name: Resolve next version - id: version - run: | - RC_VERSION=$(jq -r '.coolify.nightly.version' other/nightly/versions.json) - if [[ ! "${RC_VERSION}" =~ ^[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then - echo "Invalid next RC version: ${RC_VERSION}" - exit 1 - fi - - SHORT_SHA="${GITHUB_SHA::7}" - VERSION="${RC_VERSION}.${SHORT_SHA}" - echo "rc_version=${RC_VERSION}" >> "$GITHUB_OUTPUT" - echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT" - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - - build: - needs: prepare - strategy: - matrix: - include: - - arch: amd64 - platform: linux/amd64 - runner: ubuntu-24.04 - - arch: aarch64 - platform: linux/aarch64 - runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} - steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - - uses: docker/setup-buildx-action@v3 - - - name: Login to ${{ env.GITHUB_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ${{ env.DOCKER_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push next image (${{ matrix.arch }}) - uses: docker/build-push-action@v6 - with: - context: . - file: docker/production/Dockerfile - platforms: ${{ matrix.platform }} - push: true - build-args: | - COOLIFY_VERSION=${{ needs.prepare.outputs.version }} - tags: | - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:next-build-${{ needs.prepare.outputs.short_sha }}-${{ matrix.arch }} - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:next-build-${{ needs.prepare.outputs.short_sha }}-${{ matrix.arch }} - - publish: - needs: [prepare, build] - runs-on: ubuntu-24.04 - steps: - - uses: docker/setup-buildx-action@v3 - - - name: Login to ${{ env.GITHUB_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ${{ env.DOCKER_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Publish next manifest on ${{ env.GITHUB_REGISTRY }} - env: - REGISTRY: ${{ env.GITHUB_REGISTRY }} - SHA: ${{ needs.prepare.outputs.short_sha }} - VERSION: ${{ needs.prepare.outputs.version }} - run: | - IMAGE="${REGISTRY}/${IMAGE_NAME}" - SOURCE="next-build-${SHA}" - docker buildx imagetools create \ - "${IMAGE}:${SOURCE}-amd64" \ - "${IMAGE}:${SOURCE}-aarch64" \ - --tag "${IMAGE}:sha-${SHA}" \ - --tag "${IMAGE}:${VERSION}" \ - --tag "${IMAGE}:next" - - - name: Publish next manifest on ${{ env.DOCKER_REGISTRY }} - env: - REGISTRY: ${{ env.DOCKER_REGISTRY }} - SHA: ${{ needs.prepare.outputs.short_sha }} - VERSION: ${{ needs.prepare.outputs.version }} - run: | - IMAGE="${REGISTRY}/${IMAGE_NAME}" - SOURCE="next-build-${SHA}" - docker buildx imagetools create \ - "${IMAGE}:${SOURCE}-amd64" \ - "${IMAGE}:${SOURCE}-aarch64" \ - --tag "${IMAGE}:sha-${SHA}" \ - --tag "${IMAGE}:${VERSION}" \ - --tag "${IMAGE}:next" - - - uses: sarisia/actions-status-discord@v1 - if: always() - with: - webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }} diff --git a/.github/workflows/coolify-rc-release.yml b/.github/workflows/coolify-rc-release.yml deleted file mode 100644 index c5f3cb1e0a..0000000000 --- a/.github/workflows/coolify-rc-release.yml +++ /dev/null @@ -1,304 +0,0 @@ -name: Release Coolify RC -run-name: ${{ inputs.tag }} - -on: - workflow_dispatch: - inputs: - tag: - description: Existing draft prerelease tag (for example, v4.4-rc.1) - required: true - type: string - -permissions: {} - -concurrency: - group: coolify-rc-release - cancel-in-progress: false - -env: - GITHUB_REGISTRY: ghcr.io - DOCKER_REGISTRY: docker.io - IMAGE_NAME: coollabsio/coolify - -jobs: - validate: - runs-on: ubuntu-24.04 - permissions: - contents: write - outputs: - release_id: ${{ steps.draft.outputs.release_id }} - version: ${{ steps.version.outputs.version }} - steps: - - name: Reject releases outside next - if: ${{ github.ref != 'refs/heads/next' }} - run: | - echo "RC releases must run from the next branch, not ${{ github.ref }}." - exit 1 - - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - persist-credentials: false - - - name: Validate version - id: version - env: - TAG_NAME: ${{ inputs.tag }} - run: | - if [[ ! "${TAG_NAME}" =~ ^v[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then - echo "Unsupported RC tag: ${TAG_NAME}" - exit 1 - fi - - VERSION="${TAG_NAME#v}" - CONFIG_VERSION=$(jq -r '.coolify.nightly.version' other/nightly/versions.json) - if [[ "${CONFIG_VERSION}" != "${VERSION}" ]]; then - echo "RC tag ${VERSION} does not match nightly version ${CONFIG_VERSION}." - exit 1 - fi - - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - - - name: Validate and pin draft prerelease - id: draft - uses: actions/github-script@v8 - env: - TAG_NAME: ${{ inputs.tag }} - with: - script: | - const releases = await github.paginate(github.rest.repos.listReleases, { - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 100, - }); - const release = releases.find((candidate) => candidate.tag_name === process.env.TAG_NAME); - - if (!release) { - core.setFailed(`Create a draft prerelease for ${process.env.TAG_NAME} before running this workflow.`); - return; - } - if (!release.draft) { - core.setFailed(`Release ${process.env.TAG_NAME} must still be a draft.`); - return; - } - if (!release.prerelease) { - core.setFailed(`RC release ${process.env.TAG_NAME} must be marked as a prerelease.`); - return; - } - if (!release.body?.trim()) { - core.setFailed(`Draft prerelease ${process.env.TAG_NAME} must contain reviewed release notes.`); - return; - } - - try { - await github.rest.git.getRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `tags/${process.env.TAG_NAME}`, - }); - core.setFailed(`Git tag ${process.env.TAG_NAME} already exists.`); - return; - } catch (error) { - if (error.status !== 404) throw error; - } - - await github.rest.repos.updateRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: release.id, - tag_name: process.env.TAG_NAME, - target_commitish: context.sha, - prerelease: true, - }); - core.setOutput('release_id', release.id); - - build: - needs: validate - permissions: - contents: read - packages: write - strategy: - matrix: - include: - - arch: amd64 - platform: linux/amd64 - runner: ubuntu-24.04 - - arch: aarch64 - platform: linux/aarch64 - runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} - steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - - uses: docker/setup-buildx-action@v3 - - - name: Login to ${{ env.GITHUB_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ${{ env.DOCKER_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push RC image (${{ matrix.arch }}) - uses: docker/build-push-action@v6 - with: - context: . - file: docker/production/Dockerfile - platforms: ${{ matrix.platform }} - push: true - build-args: | - COOLIFY_VERSION=${{ needs.validate.outputs.version }} - tags: | - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:rc-release-${{ needs.validate.outputs.version }}-${{ github.sha }}-${{ matrix.arch }} - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:rc-release-${{ needs.validate.outputs.version }}-${{ github.sha }}-${{ matrix.arch }} - - revalidate: - needs: [validate, build] - runs-on: ubuntu-24.04 - permissions: - contents: write - steps: - - name: Revalidate draft prerelease - uses: actions/github-script@v8 - env: - RELEASE_ID: ${{ needs.validate.outputs.release_id }} - TAG_NAME: ${{ inputs.tag }} - with: - script: | - const releaseId = Number(process.env.RELEASE_ID); - const { data: release } = await github.rest.repos.getRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: releaseId, - }); - - if (release.tag_name !== process.env.TAG_NAME || !release.draft || !release.prerelease) { - core.setFailed(`Draft prerelease ${process.env.TAG_NAME} changed while the images were building.`); - return; - } - if (!release.body?.trim()) { - core.setFailed(`Draft prerelease ${process.env.TAG_NAME} no longer contains release notes.`); - return; - } - if (release.target_commitish !== context.sha) { - core.setFailed(`Draft prerelease ${process.env.TAG_NAME} no longer targets ${context.sha}.`); - return; - } - - try { - await github.rest.git.getRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `tags/${process.env.TAG_NAME}`, - }); - core.setFailed(`Git tag ${process.env.TAG_NAME} was created while the images were building.`); - } catch (error) { - if (error.status !== 404) throw error; - } - - publish: - needs: [validate, build, revalidate] - runs-on: ubuntu-24.04 - permissions: - contents: write - packages: write - steps: - - uses: docker/setup-buildx-action@v3 - - - name: Login to ${{ env.GITHUB_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ${{ env.DOCKER_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Publish RC and next on ${{ env.GITHUB_REGISTRY }} - env: - REGISTRY: ${{ env.GITHUB_REGISTRY }} - VERSION: ${{ needs.validate.outputs.version }} - run: | - IMAGE="${REGISTRY}/${IMAGE_NAME}" - SOURCE="rc-release-${VERSION}-${GITHUB_SHA}" - docker buildx imagetools create \ - "${IMAGE}:${SOURCE}-amd64" \ - "${IMAGE}:${SOURCE}-aarch64" \ - --tag "${IMAGE}:${VERSION}" \ - --tag "${IMAGE}:next" - - - name: Publish RC and next on ${{ env.DOCKER_REGISTRY }} - env: - REGISTRY: ${{ env.DOCKER_REGISTRY }} - VERSION: ${{ needs.validate.outputs.version }} - run: | - IMAGE="${REGISTRY}/${IMAGE_NAME}" - SOURCE="rc-release-${VERSION}-${GITHUB_SHA}" - docker buildx imagetools create \ - "${IMAGE}:${SOURCE}-amd64" \ - "${IMAGE}:${SOURCE}-aarch64" \ - --tag "${IMAGE}:${VERSION}" \ - --tag "${IMAGE}:next" - - - name: Publish reviewed draft prerelease - uses: actions/github-script@v8 - env: - RELEASE_ID: ${{ needs.validate.outputs.release_id }} - TAG_NAME: ${{ inputs.tag }} - with: - script: | - const releaseId = Number(process.env.RELEASE_ID); - const { data: release } = await github.rest.repos.getRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: releaseId, - }); - - if (release.tag_name !== process.env.TAG_NAME || !release.draft || !release.prerelease) { - core.setFailed(`Draft prerelease ${process.env.TAG_NAME} changed while the images were building.`); - return; - } - if (!release.body?.trim()) { - core.setFailed(`Draft prerelease ${process.env.TAG_NAME} no longer contains release notes.`); - return; - } - if (release.target_commitish !== context.sha) { - core.setFailed(`Draft prerelease ${process.env.TAG_NAME} no longer targets ${context.sha}.`); - return; - } - - try { - await github.rest.git.getRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `tags/${process.env.TAG_NAME}`, - }); - core.setFailed(`Git tag ${process.env.TAG_NAME} was created while the images were building.`); - return; - } catch (error) { - if (error.status !== 404) throw error; - } - - await github.rest.repos.updateRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: Number(process.env.RELEASE_ID), - tag_name: process.env.TAG_NAME, - target_commitish: context.sha, - prerelease: true, - draft: false, - }); diff --git a/.github/workflows/coolify-release.yml b/.github/workflows/coolify-release.yml deleted file mode 100644 index 70bb5d6af5..0000000000 --- a/.github/workflows/coolify-release.yml +++ /dev/null @@ -1,260 +0,0 @@ -name: Release Coolify Stable -run-name: ${{ inputs.tag }} - -on: - workflow_dispatch: - inputs: - tag: - description: Existing draft release tag (for example, v4.3.1) - required: true - type: string - -permissions: {} - -concurrency: - group: coolify-fix-release - cancel-in-progress: false - -env: - GITHUB_REGISTRY: ghcr.io - DOCKER_REGISTRY: docker.io - IMAGE_NAME: coollabsio/coolify - -jobs: - validate: - runs-on: ubuntu-24.04 - permissions: - contents: write - outputs: - release_id: ${{ steps.draft.outputs.release_id }} - version: ${{ steps.version.outputs.version }} - steps: - - name: Reject releases outside the production branch - # tmp: allow the 4.4.7 security release from fix/v4.4.7 - if: ${{ github.ref_name != 'main' && !startsWith(github.ref_name, 'fix/v') }} - run: | - echo "Stable releases must run from main, not ${{ github.ref_name }}." - exit 1 - - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - persist-credentials: false - - - name: Validate version - id: version - env: - TAG_NAME: ${{ inputs.tag }} - run: | - if [[ ! "${TAG_NAME}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Unsupported fix release tag: ${TAG_NAME}" - exit 1 - fi - - VERSION="${TAG_NAME#v}" - CONFIG_VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php) - - if [[ "${CONFIG_VERSION}" != "${VERSION}" ]]; then - echo "Release tag ${VERSION} does not match config version ${CONFIG_VERSION}." - exit 1 - fi - - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - - - name: Validate and pin draft release - id: draft - uses: actions/github-script@v8 - env: - TAG_NAME: ${{ inputs.tag }} - with: - script: | - const releases = await github.paginate(github.rest.repos.listReleases, { - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 100, - }); - const release = releases.find((candidate) => candidate.tag_name === process.env.TAG_NAME); - - if (!release) { - core.setFailed(`Create a draft release for ${process.env.TAG_NAME} before running this workflow.`); - return; - } - if (!release.draft) { - core.setFailed(`Release ${process.env.TAG_NAME} must still be a draft.`); - return; - } - if (release.prerelease) { - core.setFailed(`Fix release ${process.env.TAG_NAME} cannot be marked as a prerelease.`); - return; - } - if (!release.body?.trim()) { - core.setFailed(`Draft release ${process.env.TAG_NAME} must contain reviewed release notes.`); - return; - } - - try { - await github.rest.git.getRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `tags/${process.env.TAG_NAME}`, - }); - core.setFailed(`Git tag ${process.env.TAG_NAME} already exists.`); - return; - } catch (error) { - if (error.status !== 404) throw error; - } - - await github.rest.repos.updateRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: release.id, - tag_name: process.env.TAG_NAME, - target_commitish: context.sha, - }); - core.setOutput('release_id', release.id); - - build: - needs: validate - permissions: - contents: read - packages: write - strategy: - matrix: - include: - - arch: amd64 - platform: linux/amd64 - runner: ubuntu-24.04 - - arch: aarch64 - platform: linux/aarch64 - runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} - steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - - uses: docker/setup-buildx-action@v3 - - - name: Login to ${{ env.GITHUB_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ${{ env.DOCKER_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push release image (${{ matrix.arch }}) - uses: docker/build-push-action@v6 - with: - context: . - file: docker/production/Dockerfile - platforms: ${{ matrix.platform }} - push: true - build-args: | - COOLIFY_VERSION=${{ needs.validate.outputs.version }} - tags: | - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:release-${{ needs.validate.outputs.version }}-${{ github.sha }}-${{ matrix.arch }} - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:release-${{ needs.validate.outputs.version }}-${{ github.sha }}-${{ matrix.arch }} - - publish: - needs: [validate, build] - runs-on: ubuntu-24.04 - permissions: - contents: write - packages: write - steps: - - uses: docker/setup-buildx-action@v3 - - - name: Login to ${{ env.GITHUB_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ${{ env.DOCKER_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Publish version and latest on ${{ env.GITHUB_REGISTRY }} - env: - REGISTRY: ${{ env.GITHUB_REGISTRY }} - VERSION: ${{ needs.validate.outputs.version }} - run: | - IMAGE="${REGISTRY}/${IMAGE_NAME}" - SOURCE="release-${VERSION}-${GITHUB_SHA}" - docker buildx imagetools create \ - "${IMAGE}:${SOURCE}-amd64" \ - "${IMAGE}:${SOURCE}-aarch64" \ - --tag "${IMAGE}:${VERSION}" \ - --tag "${IMAGE}:latest" - - - name: Publish version and latest on ${{ env.DOCKER_REGISTRY }} - env: - REGISTRY: ${{ env.DOCKER_REGISTRY }} - VERSION: ${{ needs.validate.outputs.version }} - run: | - IMAGE="${REGISTRY}/${IMAGE_NAME}" - SOURCE="release-${VERSION}-${GITHUB_SHA}" - docker buildx imagetools create \ - "${IMAGE}:${SOURCE}-amd64" \ - "${IMAGE}:${SOURCE}-aarch64" \ - --tag "${IMAGE}:${VERSION}" \ - --tag "${IMAGE}:latest" - - - name: Publish reviewed draft release - uses: actions/github-script@v8 - env: - RELEASE_ID: ${{ needs.validate.outputs.release_id }} - TAG_NAME: ${{ inputs.tag }} - with: - script: | - const releaseId = Number(process.env.RELEASE_ID); - const { data: release } = await github.rest.repos.getRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: releaseId, - }); - - if (release.tag_name !== process.env.TAG_NAME || !release.draft || release.prerelease) { - core.setFailed(`Draft release ${process.env.TAG_NAME} changed while the images were building.`); - return; - } - if (!release.body?.trim()) { - core.setFailed(`Draft release ${process.env.TAG_NAME} no longer contains release notes.`); - return; - } - if (release.target_commitish !== context.sha) { - core.setFailed(`Draft release ${process.env.TAG_NAME} no longer targets ${context.sha}.`); - return; - } - - try { - await github.rest.git.getRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `tags/${process.env.TAG_NAME}`, - }); - core.setFailed(`Git tag ${process.env.TAG_NAME} was created while the images were building.`); - return; - } catch (error) { - if (error.status !== 404) throw error; - } - - await github.rest.repos.updateRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: Number(process.env.RELEASE_ID), - tag_name: process.env.TAG_NAME, - target_commitish: context.sha, - draft: false, - }); diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000000..38098158c7 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,199 @@ +name: Release + +on: + push: + branches: + - main + - next + - v[0-9]+.x + - v[0-9]+.[0-9]+.x + - release/v[0-9]+.[0-9]+ + paths: + - release.json + workflow_dispatch: + +permissions: {} + +cache-mode: none + +concurrency: + group: release + cancel-in-progress: false + queue: max + +defaults: + run: + shell: bash + +jobs: + version: + name: Select the releases + if: ${{ github.repository == 'coollabsio/coolify' }} + permissions: + contents: read + runs-on: ubuntu-26.04 # https://github.com/actions/runner-images + timeout-minutes: 10 + outputs: + releases: ${{ steps.version.outputs.releases }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - id: version + env: + BRANCH: ${{ github.ref_name }} + run: | + releases="$(scripts/version.sh --release)" + echo "releases=$releases" | tee -a "$GITHUB_OUTPUT" + + build: + name: Build + needs: version + if: ${{ needs.version.outputs.releases != '[]' }} + permissions: + contents: read + uses: $/.github/workflows/build.yaml + + publish: + name: Publish ${{ matrix.release.tag }} + needs: [version, build] + permissions: + contents: write # create the GitHub release and its tag + packages: write # push the built images to ghcr.io + environment: + name: release + deployment: false + runs-on: ubuntu-26.04 # https://github.com/actions/runner-images + timeout-minutes: 30 + strategy: + matrix: + release: ${{ fromJSON(needs.version.outputs.releases) }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + with: + persist-credentials: false + + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # https://github.com/actions/download-artifact/releases/tag/v8.0.2 + with: + pattern: image-* + path: ${{ runner.temp }}/images + + - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # https://github.com/docker/login-action/releases/tag/v4.7.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # https://github.com/docker/login-action/releases/tag/v4.7.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Push the built images + env: + TAG: ${{ matrix.release.tag }} + run: | + sources=() + for index in "$RUNNER_TEMP"/images/*/index.json; do + sources+=("oci-layout://${index%/index.json}@$(jq -r '.manifests[0].digest' "$index")") + done + for repository in ghcr.io/coollabsio/coolify docker.io/coollabsio/coolify; do + docker buildx imagetools create --tag "$repository:${TAG#v}" "${sources[@]}" + done + + - name: Create the GitHub release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ matrix.release.tag }} + IMAGE_VERSION: ${{ matrix.release.image }} + run: | + args=(--title "$TAG" --target "$GITHUB_SHA" --notes-file "$RUNNER_TEMP/notes.md" --latest=false) + heading="## [v$IMAGE_VERSION]" + [[ "$IMAGE_VERSION" != *-* ]] || { args+=(--prerelease); heading="## [Unreleased]"; } + awk -v heading="$heading" 'index($0, heading) == 1 { inside = 1; next } /^## / { inside = 0 } inside { sub(/^### /, "## "); print }' CHANGELOG.md > "$RUNNER_TEMP/notes.md" + gh release create "$TAG" "${args[@]}" + + tags: + name: Move the tags + needs: publish + permissions: + contents: write # mark the GitHub release that latest points at as the latest release + packages: write # move the tags on ghcr.io + environment: + name: release + deployment: false + runs-on: ubuntu-26.04 # https://github.com/actions/runner-images + timeout-minutes: 10 + outputs: + tags: ${{ steps.tags.outputs.tags }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # https://github.com/docker/login-action/releases/tag/v4.7.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # https://github.com/docker/login-action/releases/tag/v4.7.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Move the tags the releases take over + id: tags + env: + GH_TOKEN: ${{ github.token }} + run: | + tags="$(scripts/version.sh --tags)" + echo "tags=$tags" >> "$GITHUB_OUTPUT" + jq -r 'to_entries[] | "\(.key) \(.value)"' <<< "$tags" | while read -r tag release; do + for repository in ghcr.io/coollabsio/coolify docker.io/coollabsio/coolify; do + docker buildx imagetools create --tag "$repository:$tag" "$repository:${release#v}" + done + done + latest="$(jq -r '.latest // empty' <<< "$tags")" + [ -z "$latest" ] || gh release edit "$latest" --latest + + cdn: + name: Sync the CDN + needs: tags + if: ${{ needs.tags.outputs.tags != '{}' }} + permissions: + contents: read + environment: + name: release + deployment: false + runs-on: ubuntu-26.04 # https://github.com/actions/runner-images + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Sync the channels of the moved tags + env: + GH_TOKEN: ${{ secrets.CDN_TOKEN }} + TAGS: ${{ needs.tags.outputs.tags }} + run: | + gh auth setup-git + git config --global user.name "github-actions[bot]" + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + jq -r '{stable: .latest, preview: .preview} | to_entries[] | select(.value) | "\(.key) \(.value)"' <<< "$TAGS" | while read -r channel release; do + git switch --quiet --detach "$release" + scripts/sync-cdn.sh "--$channel" "${release#v}" + done + + merge: + name: Merge forward + needs: publish + permissions: + contents: write # push the merges + pull-requests: write # open a pull request when a merge conflicts + uses: $/.github/workflows/merge-forward.yaml diff --git a/.github/workflows/tmp-coolify-hotfix-release.yml b/.github/workflows/tmp-coolify-hotfix-release.yml deleted file mode 100644 index 2ef14b3597..0000000000 --- a/.github/workflows/tmp-coolify-hotfix-release.yml +++ /dev/null @@ -1,159 +0,0 @@ -# Temporary: releases the 4.0 to 4.3 security fixes from the v4.0.x to v4.3.x branches without moving latest. -# Delete this workflow once 4.5 ships. -name: TMP Release Coolify Hotfix -run-name: tmp ${{ inputs.branch }} - -on: - workflow_dispatch: - inputs: - branch: - description: Release line branch - required: true - type: choice - options: - - v4.3.x - - v4.2.x - - v4.1.x - - v4.0.x - -permissions: {} - -concurrency: - group: tmp-coolify-hotfix-release - cancel-in-progress: false - -env: - GITHUB_REGISTRY: ghcr.io - DOCKER_REGISTRY: docker.io - IMAGE_NAME: coollabsio/coolify - -jobs: - validate: - runs-on: ubuntu-24.04 - permissions: - contents: read - outputs: - sha: ${{ steps.version.outputs.sha }} - version: ${{ steps.version.outputs.version }} - steps: - - uses: actions/checkout@v5 - with: - ref: ${{ inputs.branch }} - persist-credentials: false - - - name: Resolve version - id: version - env: - BRANCH: ${{ inputs.branch }} - GH_TOKEN: ${{ github.token }} - run: | - VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php) - if [[ "${VERSION}" != "${BRANCH:1:3}."* || ! "${VERSION}" =~ ^4\.[0-3]\.[0-9]+$ ]]; then - echo "Version ${VERSION} does not belong to ${BRANCH}." - exit 1 - fi - if gh release view "v${VERSION}" --repo "${GITHUB_REPOSITORY}" > /dev/null 2>&1; then - echo "Release v${VERSION} already exists." - exit 1 - fi - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - - build: - needs: validate - permissions: - contents: read - packages: write - strategy: - matrix: - include: - - arch: amd64 - platform: linux/amd64 - runner: ubuntu-24.04 - - arch: aarch64 - platform: linux/aarch64 - runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} - steps: - - uses: actions/checkout@v5 - with: - ref: ${{ needs.validate.outputs.sha }} - persist-credentials: false - - - name: Login to ${{ env.GITHUB_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ${{ env.DOCKER_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push image (${{ matrix.arch }}) - uses: docker/build-push-action@v6 - with: - context: . - file: docker/production/Dockerfile - platforms: ${{ matrix.platform }} - push: true - build-args: | - COOLIFY_VERSION=${{ needs.validate.outputs.version }} - labels: | - org.opencontainers.image.revision=${{ needs.validate.outputs.sha }} - tags: | - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:release-${{ needs.validate.outputs.version }}-${{ needs.validate.outputs.sha }}-${{ matrix.arch }} - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:release-${{ needs.validate.outputs.version }}-${{ needs.validate.outputs.sha }}-${{ matrix.arch }} - - publish: - needs: [validate, build] - runs-on: ubuntu-24.04 - permissions: - contents: write - packages: write - steps: - - uses: docker/setup-buildx-action@v3 - - - name: Login to ${{ env.GITHUB_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ${{ env.DOCKER_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Publish the version tag - env: - VERSION: ${{ needs.validate.outputs.version }} - SHA: ${{ needs.validate.outputs.sha }} - run: | - for REGISTRY in "${GITHUB_REGISTRY}" "${DOCKER_REGISTRY}"; do - IMAGE="${REGISTRY}/${IMAGE_NAME}" - docker buildx imagetools create \ - "${IMAGE}:release-${VERSION}-${SHA}-amd64" \ - "${IMAGE}:release-${VERSION}-${SHA}-aarch64" \ - --tag "${IMAGE}:${VERSION}" - done - - - name: Publish the GitHub release - env: - GH_TOKEN: ${{ github.token }} - VERSION: ${{ needs.validate.outputs.version }} - SHA: ${{ needs.validate.outputs.sha }} - run: | - gh release create "v${VERSION}" \ - --repo "${GITHUB_REPOSITORY}" \ - --target "${SHA}" \ - --title "v${VERSION}" \ - --latest=false \ - --notes "Security fix release for Coolify ${VERSION%.*}. Apply it with: curl -fsSL https://cdn.coollabs.io/coolify/patch.sh | sudo bash" diff --git a/bootstrap/getVersion.php b/bootstrap/getVersion.php deleted file mode 100644 index 8fa27a3328..0000000000 --- a/bootstrap/getVersion.php +++ /dev/null @@ -1,10 +0,0 @@ -and(is_dir(base_path('docker/coolify-realtime')))->toBeFalse() ->and(file_exists(base_path('.github/workflows/coolify-realtime.yml')))->toBeFalse() ->and(file_exists(base_path('.github/workflows/coolify-realtime-next.yml')))->toBeFalse() - ->and(file_get_contents(base_path('.github/workflows/coolify-next-build.yml')))->not->toContain('coolify-realtime') ->and($productionInstallScript)->not->toContain('LATEST_REALTIME_VERSION') ->not->toContain('| Realtime') ->and($nightlyInstallScript)->not->toContain('LATEST_REALTIME_VERSION') diff --git a/tests/Unit/ProductionImageWorkflowTest.php b/tests/Unit/ProductionImageWorkflowTest.php index de42d2f5c0..97b8700457 100644 --- a/tests/Unit/ProductionImageWorkflowTest.php +++ b/tests/Unit/ProductionImageWorkflowTest.php @@ -9,70 +9,6 @@ it('reads the Coolify and helper versions by key from the versions.json of the C expect(trim($output))->toBe("4.6.0-beta.1 {$helper}"); }); -it('publishes main builds under rolling and commit sha tags with a traceable internal version', function () { - $workflow = file_get_contents(dirname(__DIR__, 2).'/.github/workflows/coolify-main-build.yml'); - $dockerfile = file_get_contents(dirname(__DIR__, 2).'/docker/production/Dockerfile'); - $constants = file_get_contents(dirname(__DIR__, 2).'/config/constants.php'); - - expect($workflow) - ->toContain('name: Build Coolify Main') - ->toContain('branches: ["main"]') - ->not->toContain('v4.x') - ->toContain('group: coolify-main-build') - ->toContain('cancel-in-progress: false') - ->toContain('IMAGE_NAME }}:main-${{ matrix.arch }}') - ->toContain('--tag "${IMAGE}:main"') - ->toContain('short_sha=${GITHUB_SHA::7}') - ->toContain('SHA: ${{ needs.build-push.outputs.short_sha }}') - ->toContain('--tag "${IMAGE}:sha-${SHA}"') - ->not->toContain('sha-${{ steps.version.outputs.short_sha }}-${{ matrix.arch }}') - ->toContain('org.opencontainers.image.revision=${{ github.sha }}') - ->toContain('php bootstrap/getVersion.php') - ->toContain('version=${BASE_VERSION}-dev.${GITHUB_SHA::9}') - ->toContain('COOLIFY_VERSION=${{ steps.version.outputs.version }}') - ->toContain('sarisia/actions-status-discord@v1') - ->toContain('webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}') - ->not->toContain('IMAGE_NAME }}:latest') - ->and($dockerfile) - ->toContain('ARG COOLIFY_VERSION') - ->toContain('ENV COOLIFY_VERSION=${COOLIFY_VERSION}') - ->and($constants) - ->toContain("'version' => env('COOLIFY_VERSION') ?: \$release['stable']['coolify']"); -}); - -it('orders a maintenance development build before its stable release', function () { - expect(version_compare('4.3.2-dev.d64cbda3e', '4.3.2', '<'))->toBeTrue() - ->and(version_compare('4.3.2', '4.3.2-dev.d64cbda3e', '>'))->toBeTrue(); -}); - -it('orders rolling and exact release candidates before the stable release', function () { - expect(version_compare('4.4-rc.1.d64cbda', '4.4-rc.1', '<'))->toBeTrue() - ->and(version_compare('4.4-rc.1', '4.4-rc.2', '<'))->toBeTrue() - ->and(version_compare('4.4-rc.2', '4.4.0', '<'))->toBeTrue(); -}); - -it('requires a reviewed draft release before building a stable version', function () { - $workflow = file_get_contents(dirname(__DIR__, 2).'/.github/workflows/coolify-release.yml'); - - expect($workflow) - ->toContain('name: Release Coolify Stable') - ->toContain('run-name: ${{ inputs.tag }}') - ->toContain('workflow_dispatch:') - ->toContain('tag:') - ->toContain("github.ref_name != 'main'") - ->toContain('github.paginate(github.rest.repos.listReleases') - ->toContain('release.draft') - ->toContain('release.prerelease') - ->toContain('release.body?.trim()') - ->toContain('bootstrap/getVersion.php') - ->toContain('target_commitish: context.sha') - ->toContain('tag_name: process.env.TAG_NAME') - ->toContain('actions/github-script@v8') - ->not->toContain('actions/github-script@v7') - ->not->toContain('environment: production-release') - ->not->toContain('generate-notes'); -}); - it('runs support image workflows from main', function (string $workflowFile) { $workflow = file_get_contents(dirname(__DIR__, 2)."/.github/workflows/{$workflowFile}"); @@ -116,85 +52,6 @@ it('publishes the testing host only to Docker Hub', function () { ->toContain('dockerfile: ./docker/testing-host/Dockerfile'); }); -it('publishes traceable rolling builds from next without creating an exact rc tag', function () { - $workflow = file_get_contents(dirname(__DIR__, 2).'/.github/workflows/coolify-next-build.yml'); - - expect($workflow) - ->toContain('name: Build Coolify Next') - ->toContain('branches: [next]') - ->toContain('group: coolify-next-build') - ->toContain("jq -r '.coolify.nightly.version' other/nightly/versions.json") - ->toContain('VERSION="${RC_VERSION}.${SHORT_SHA}"') - ->toContain('COOLIFY_VERSION=${{ needs.prepare.outputs.version }}') - ->toContain('--tag "${IMAGE}:sha-${SHA}"') - ->toContain('--tag "${IMAGE}:${VERSION}"') - ->toContain('--tag "${IMAGE}:next"') - ->toContain('sarisia/actions-status-discord@v1') - ->toContain('webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}') - ->not->toContain('--tag "${IMAGE}:${RC_VERSION}"') - ->not->toContain('--tag "${IMAGE}:latest"'); -}); - -it('requires a reviewed draft prerelease before publishing an exact rc', function () { - $workflow = file_get_contents(dirname(__DIR__, 2).'/.github/workflows/coolify-rc-release.yml'); - - expect($workflow) - ->toContain('name: Release Coolify RC') - ->toContain('run-name: ${{ inputs.tag }}') - ->toContain("github.ref != 'refs/heads/next'") - ->toContain('group: coolify-rc-release') - ->toContain('^v[0-9]+\\.[0-9]+-rc\\.[0-9]+$') - ->toContain("jq -r '.coolify.nightly.version' other/nightly/versions.json") - ->toContain('release.draft') - ->toContain('!release.prerelease') - ->toContain('release.body?.trim()') - ->toContain('revalidate:') - ->toMatch('/revalidate:.*?permissions:\s+contents: write/s') - ->toContain('needs: [validate, build, revalidate]') - ->toContain('COOLIFY_VERSION=${{ needs.validate.outputs.version }}') - ->toContain('--tag "${IMAGE}:${VERSION}"') - ->toContain('--tag "${IMAGE}:next"') - ->toContain('prerelease: true') - ->toContain('actions/github-script@v8') - ->not->toContain('--tag "${IMAGE}:latest"') - ->not->toContain('environment:'); -}); - -it('rebuilds stable images and publishes the reviewed draft after both architectures succeed', function () { - $workflow = file_get_contents(dirname(__DIR__, 2).'/.github/workflows/coolify-release.yml'); - - expect($workflow) - ->toContain('docker/build-push-action@v6') - ->toContain('COOLIFY_VERSION=${{ needs.validate.outputs.version }}') - ->toContain('release-${{ needs.validate.outputs.version }}-${{ github.sha }}-${{ matrix.arch }}') - ->toContain('--tag "${IMAGE}:${VERSION}"') - ->toContain('--tag "${IMAGE}:latest"') - ->toContain('github.rest.repos.getRelease') - ->toContain('release.target_commitish !== context.sha') - ->toContain('release_id: Number(process.env.RELEASE_ID)') - ->toContain('draft: false') - ->toContain('permissions: {}') - ->not->toContain('sarisia/actions-status-discord@v1') - ->not->toContain('SOURCE_TAG="sha-${RELEASE_SHA}"'); -}); - -it('documents the production, rc, and hotfix release flows', function () { - $releaseGuide = file_get_contents(dirname(__DIR__, 2).'/RELEASE.md'); - - expect($releaseGuide) - ->toContain('| `main` | Latest production source |') - ->toContain('| `next` | Feature integration and RC releases |') - ->toContain('| `hotfix/X.Y.Z` | Production fixes based on `main` |') - ->toContain('feature/* → next → RC') - ->toContain('next → main → stable release') - ->toContain('main → hotfix/X.Y.Z → main → next') - ->toContain('reviewed draft GitHub Release') - ->toContain('workflows never edit or commit versions') - ->toContain('Update the CDN only after the release is approved') - ->not->toContain('`edge`') - ->not->toContain('promotes the existing SHA image'); -}); - it('documents pull request targets for fixes and features', function () { $contributingGuide = file_get_contents(dirname(__DIR__, 2).'/CONTRIBUTING.md');