From ff8c72a8b9a3c30fa6a580198005d7c6a346a2d2 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 29 Oct 2026 23:12:52 +0100 Subject: [PATCH 1/6] ci: bump GitHub Actions to the latest releases - actions/stale to v11 and actions/github-script to v9, both ran on Node.js 20, which GitHub Actions removed --- .github/workflows/chore-manage-pr-branch.yaml | 2 +- .github/workflows/chore-manage-stale-issues-and-prs.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/chore-manage-pr-branch.yaml b/.github/workflows/chore-manage-pr-branch.yaml index 65c81ab717..f9c0392934 100644 --- a/.github/workflows/chore-manage-pr-branch.yaml +++ b/.github/workflows/chore-manage-pr-branch.yaml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Retarget PR base branch based on category - uses: actions/github-script@v7 + uses: actions/github-script@v9.0.0 with: script: | const run = context.payload.workflow_run; diff --git a/.github/workflows/chore-manage-stale-issues-and-prs.yml b/.github/workflows/chore-manage-stale-issues-and-prs.yml index d610055497..86c22cfece 100644 --- a/.github/workflows/chore-manage-stale-issues-and-prs.yml +++ b/.github/workflows/chore-manage-stale-issues-and-prs.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Manage stale issues and PRs - uses: actions/stale@v9 + uses: actions/stale@v11.0.0 id: stale with: stale-issue-message: 'This issue will be automatically closed in a few days if no response is received. Please provide an update with the requested information.' From 4a6a2d81ffc3393092bd38f3d5f2cfa5fbe0c8cb Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 29 Oct 2026 23:13:01 +0100 Subject: [PATCH 2/6] ci: remove the workflow that deletes the untagged images on ghcr.io - a release pushes its platform images and attestations untagged behind its tagged index, one run would delete them and break every published version --- .github/workflows/cleanup-ghcr-untagged.yml | 22 --------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/workflows/cleanup-ghcr-untagged.yml diff --git a/.github/workflows/cleanup-ghcr-untagged.yml b/.github/workflows/cleanup-ghcr-untagged.yml deleted file mode 100644 index b66767cda8..0000000000 --- a/.github/workflows/cleanup-ghcr-untagged.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Cleanup Untagged GHCR Images - -on: - workflow_dispatch: - -permissions: - packages: write - -jobs: - cleanup-all-packages: - runs-on: ubuntu-latest - strategy: - matrix: - package: ['coolify', 'coolify-helper'] - steps: - - name: Delete untagged ${{ matrix.package }} images - uses: actions/delete-package-versions@v5 - with: - package-name: ${{ matrix.package }} - package-type: 'container' - min-versions-to-keep: 0 - delete-only-untagged-versions: 'true' From 61ce240425543593813dd879855135e38b23f810 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 29 Oct 2026 23:13:51 +0100 Subject: [PATCH 3/6] ci: lock the actions in actions.lock --- .github/workflows/actions.lock | 90 +++++++++++++++++++ .github/workflows/build.yaml | 10 +-- ...lock-closed-issues-discussions-and-prs.yml | 2 +- .github/workflows/clean-manifests.yaml | 4 +- .github/workflows/main.yaml | 6 +- .github/workflows/merge-forward.yaml | 2 +- .github/workflows/nightly.yaml | 10 +-- .github/workflows/pr-quality.yaml | 2 +- .github/workflows/pull-request.yaml | 4 +- .github/workflows/release.yaml | 18 ++-- 10 files changed, 119 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/actions.lock diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock new file mode 100644 index 0000000000..1ce7be0db8 --- /dev/null +++ b/.github/workflows/actions.lock @@ -0,0 +1,90 @@ +# This file is machine-generated by `gh actions-lock`. +# Do not edit by hand; run `gh actions-lock` to update. +# Docs: https://gh.io/actions-lockfile +version: 'v0.0.3' +workflows: + '.github/workflows/build.yaml': + - 'actions/checkout@v7.0.1' + - 'actions/upload-artifact@v7.0.2' + - 'docker/build-push-action@v7.4.0' + - 'docker/setup-buildx-action@v4.4.1' + '.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml': + - 'dessant/lock-threads@v6.0.2' + '.github/workflows/chore-manage-pr-branch.yaml': + - 'actions/github-script@v9.0.0' + '.github/workflows/chore-manage-stale-issues-and-prs.yml': + - 'actions/stale@v11.0.0' + '.github/workflows/chore-pr-comments.yml': [] + '.github/workflows/clean-manifests.yaml': + - 'actions/checkout@v7.0.1' + - 'actions/upload-artifact@v7.0.2' + '.github/workflows/main.yaml': + - 'actions/checkout@v7.0.1' + - 'actions/download-artifact@v8.0.2' + - 'docker/login-action@v4.7.0' + '.github/workflows/merge-forward.yaml': + - 'actions/checkout@v7.0.1' + '.github/workflows/nightly.yaml': + - 'actions/checkout@v7.0.1' + - 'actions/download-artifact@v8.0.2' + - 'docker/login-action@v4.7.0' + '.github/workflows/pr-quality.yaml': + - 'peakoss/anti-slop@v0.3.0' + '.github/workflows/pull-request.yaml': + - 'actions/download-artifact@v8.0.2' + - 'docker/login-action@v4.7.0' + '.github/workflows/release.yaml': + - 'actions/checkout@v7.0.1' + - 'actions/download-artifact@v8.0.2' + - 'docker/login-action@v4.7.0' +dependencies: + 'actions/checkout@v7.0.1': + ref: 'v7.0.1' + commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1' + owner_id: 44036562 + repo_id: 197814629 + 'actions/download-artifact@v8.0.2': + ref: 'v8.0.2' + commit: 'sha1-9000827ccba6bdab643e8b6fd33ac0654aef8333' + owner_id: 44036562 + repo_id: 192626254 + 'actions/github-script@v9.0.0': + ref: 'v9.0.0' + commit: 'sha1-3a2844b7e9c422d3c10d287c895573f7108da1b3' + owner_id: 44036562 + repo_id: 205262760 + 'actions/stale@v11.0.0': + ref: 'v11.0.0' + commit: 'sha1-4391f3da665fdf50b6810c1a66712fb9ba21aa93' + owner_id: 44036562 + repo_id: 200437066 + 'actions/upload-artifact@v7.0.2': + ref: 'v7.0.2' + commit: 'sha1-cf430e030ddbb5b0abf93d22962f4752f3646cd9' + owner_id: 44036562 + repo_id: 192625955 + 'dessant/lock-threads@v6.0.2': + ref: 'v6.0.2' + commit: 'sha1-89ae32b08ed1a541efecbab17912962a5e38981c' + owner_id: 7513068 + repo_id: 113918130 + 'docker/build-push-action@v7.4.0': + ref: 'v7.4.0' + commit: 'sha1-c3c9e263c25d99ce0380d002d59b67737d91b0dc' + owner_id: 5429470 + repo_id: 241092383 + 'docker/login-action@v4.7.0': + ref: 'v4.7.0' + commit: 'sha1-81c6f93d4bc991e510b342a00b419b3bb4efc5c6' + owner_id: 5429470 + repo_id: 287743349 + 'docker/setup-buildx-action@v4.4.1': + ref: 'v4.4.1' + commit: 'sha1-f87e5991a6d7451dcb8d9637bfbc97413f497069' + owner_id: 5429470 + repo_id: 288485773 + 'peakoss/anti-slop@v0.3.0': + ref: 'v0.3.0' + commit: 'sha1-57858eead489d08b255fab2af45a506c2ca6eab2' + owner_id: 227272980 + repo_id: 1153153658 diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 71efd82429..5e79fe2c4e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -38,7 +38,7 @@ jobs: coolify: ${{ inputs.coolify || steps.plan.outputs.coolify }} steps: - name: Check out the repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + uses: actions/checkout@v7.0.1 with: ref: ${{ inputs.ref }} persist-credentials: false @@ -71,7 +71,7 @@ jobs: runner: ubuntu-26.04-arm steps: - name: Check out the repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + uses: actions/checkout@v7.0.1 with: ref: ${{ inputs.ref }} persist-credentials: false @@ -85,12 +85,12 @@ jobs: } >> "$GITHUB_OUTPUT" - name: Set up Buildx - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # https://github.com/docker/setup-buildx-action/releases/tag/v4.4.1 + uses: docker/setup-buildx-action@v4.4.1 with: cache-binary: false - name: Build the image - uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # https://github.com/docker/build-push-action/releases/tag/v7.4.0 + uses: docker/build-push-action@v7.4.0 with: context: . file: docker/production/Dockerfile @@ -103,7 +103,7 @@ jobs: GIT_SHA=${{ steps.args.outputs.sha }} - name: Upload the image - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # https://github.com/actions/upload-artifact/releases/tag/v7.0.2 + uses: actions/upload-artifact@v7.0.2 with: name: image-${{ matrix.arch }} path: ${{ runner.temp }}/image diff --git a/.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml b/.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml index 6fd8212e61..2ce86310e0 100644 --- a/.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml +++ b/.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Lock threads after 30 days of inactivity - uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6.0.2 + uses: dessant/lock-threads@v6.0.2 with: github-token: ${{ secrets.GITHUB_TOKEN }} issue-inactive-days: '30' diff --git a/.github/workflows/clean-manifests.yaml b/.github/workflows/clean-manifests.yaml index 1679ad669f..a0b8287afe 100644 --- a/.github/workflows/clean-manifests.yaml +++ b/.github/workflows/clean-manifests.yaml @@ -28,7 +28,7 @@ jobs: timeout-minutes: 60 steps: - name: Check out the repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7.0.1 with: persist-credentials: false @@ -41,7 +41,7 @@ jobs: - name: Upload the list of manifests to delete if: ${{ always() }} - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + uses: actions/upload-artifact@v7.0.2 with: name: manifests-to-delete path: scripts/manifests-to-delete.tsv diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 8f64941d57..f0dbd9a07c 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -29,7 +29,7 @@ jobs: build: ${{ steps.plan.outputs.build }} steps: - name: Check out main - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + uses: actions/checkout@v7.0.1 with: ref: main fetch-depth: 0 @@ -67,13 +67,13 @@ jobs: timeout-minutes: 30 steps: - name: Download the built images - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # https://github.com/actions/download-artifact/releases/tag/v8.0.2 + uses: actions/download-artifact@v8.0.2 with: pattern: image-* path: ${{ runner.temp }}/images - name: Log in to ghcr.io - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # https://github.com/docker/login-action/releases/tag/v4.7.0 + uses: docker/login-action@v4.7.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/merge-forward.yaml b/.github/workflows/merge-forward.yaml index cd18c98edb..9d948ea118 100644 --- a/.github/workflows/merge-forward.yaml +++ b/.github/workflows/merge-forward.yaml @@ -26,7 +26,7 @@ jobs: timeout-minutes: 10 steps: - name: Check out the repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + uses: actions/checkout@v7.0.1 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 49755a44e7..e21e1269f1 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -29,7 +29,7 @@ jobs: nightly: ${{ steps.plan.outputs.nightly }} steps: - name: Check out next - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + uses: actions/checkout@v7.0.1 with: ref: next fetch-depth: 0 @@ -73,20 +73,20 @@ jobs: line: ${{ fromJSON(needs.plan.outputs.nightly).lines }} steps: - name: Download the built images - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # https://github.com/actions/download-artifact/releases/tag/v8.0.2 + uses: actions/download-artifact@v8.0.2 with: pattern: image-* path: ${{ runner.temp }}/images - name: Log in to ghcr.io - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # https://github.com/docker/login-action/releases/tag/v4.7.0 + uses: docker/login-action@v4.7.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Log in to docker.io - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # https://github.com/docker/login-action/releases/tag/v4.7.0 + uses: docker/login-action@v4.7.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -118,7 +118,7 @@ jobs: timeout-minutes: 10 steps: - name: Check out the nightly - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + uses: actions/checkout@v7.0.1 with: ref: ${{ fromJSON(needs.plan.outputs.nightly).revision }} persist-credentials: false diff --git a/.github/workflows/pr-quality.yaml b/.github/workflows/pr-quality.yaml index 5913496ddc..b61bf8d86c 100644 --- a/.github/workflows/pr-quality.yaml +++ b/.github/workflows/pr-quality.yaml @@ -13,7 +13,7 @@ jobs: pr-quality: runs-on: ubuntu-latest steps: - - uses: peakoss/anti-slop@v0 + - uses: peakoss/anti-slop@v0.3.0 with: # General Settings max-failures: 4 diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 0f511c716d..42a585f23d 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -61,13 +61,13 @@ jobs: timeout-minutes: 30 steps: - name: Download the built images - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # https://github.com/actions/download-artifact/releases/tag/v8.0.2 + uses: actions/download-artifact@v8.0.2 with: pattern: image-* path: ${{ runner.temp }}/images - name: Log in to ghcr.io - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # https://github.com/docker/login-action/releases/tag/v4.7.0 + uses: docker/login-action@v4.7.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d6614d9027..ab98ab102a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -37,7 +37,7 @@ jobs: releases: ${{ steps.plan.outputs.releases }} steps: - name: Check out the repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + uses: actions/checkout@v7.0.1 with: fetch-depth: 0 persist-credentials: false @@ -74,25 +74,25 @@ jobs: release: ${{ fromJSON(needs.plan.outputs.releases) }} steps: - name: Check out the repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + uses: actions/checkout@v7.0.1 with: persist-credentials: false - name: Download the built images - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # https://github.com/actions/download-artifact/releases/tag/v8.0.2 + uses: actions/download-artifact@v8.0.2 with: pattern: image-* path: ${{ runner.temp }}/images - name: Log in to ghcr.io - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # https://github.com/docker/login-action/releases/tag/v4.7.0 + uses: docker/login-action@v4.7.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Log in to docker.io - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # https://github.com/docker/login-action/releases/tag/v4.7.0 + uses: docker/login-action@v4.7.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -136,7 +136,7 @@ jobs: tags: ${{ steps.plan.outputs.tags }} steps: - name: Check out the repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + uses: actions/checkout@v7.0.1 with: fetch-depth: 0 persist-credentials: false @@ -148,14 +148,14 @@ jobs: echo "tags=$tags" | tee -a "$GITHUB_OUTPUT" - name: Log in to ghcr.io - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # https://github.com/docker/login-action/releases/tag/v4.7.0 + uses: docker/login-action@v4.7.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Log in to docker.io - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # https://github.com/docker/login-action/releases/tag/v4.7.0 + uses: docker/login-action@v4.7.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -190,7 +190,7 @@ jobs: timeout-minutes: 10 steps: - name: Check out the repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1 + uses: actions/checkout@v7.0.1 with: fetch-depth: 0 persist-credentials: false From 79fd5a8f7ea6123a90fb6c5aa1f673fc8d0c9988 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 29 Oct 2026 23:16:21 +0100 Subject: [PATCH 4/6] ci: fix every finding of jactionlint with the pedantic profile - pull_request_target and workflow_run stay, these workflows run no code of the pull request --- .github/jactionlint.yaml | 1 + ...lock-closed-issues-discussions-and-prs.yml | 17 +++++++++---- .github/workflows/chore-manage-pr-branch.yaml | 13 ++++++---- .../chore-manage-stale-issues-and-prs.yml | 15 ++++++++---- .github/workflows/chore-pr-comments.yml | 24 +++++++++++++++---- .github/workflows/pr-quality.yaml | 21 +++++++++++----- 6 files changed, 67 insertions(+), 24 deletions(-) create mode 100644 .github/jactionlint.yaml diff --git a/.github/jactionlint.yaml b/.github/jactionlint.yaml new file mode 100644 index 0000000000..07d42f9c26 --- /dev/null +++ b/.github/jactionlint.yaml @@ -0,0 +1 @@ +profile: pedantic diff --git a/.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml b/.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml index 2ce86310e0..8420f54689 100644 --- a/.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml +++ b/.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml @@ -4,14 +4,21 @@ on: schedule: - cron: '0 1 * * *' -permissions: - issues: write - discussions: write - pull-requests: write +permissions: {} + +concurrency: + group: lock-threads + cancel-in-progress: false jobs: lock-threads: - runs-on: ubuntu-latest + name: Lock inactive threads + permissions: + issues: write # lock the inactive issues + discussions: write # lock the inactive discussions + pull-requests: write # lock the inactive pull requests + runs-on: ubuntu-26.04 # https://github.com/actions/runner-images + timeout-minutes: 10 steps: - name: Lock threads after 30 days of inactivity uses: dessant/lock-threads@v6.0.2 diff --git a/.github/workflows/chore-manage-pr-branch.yaml b/.github/workflows/chore-manage-pr-branch.yaml index f9c0392934..c66ede6f5e 100644 --- a/.github/workflows/chore-manage-pr-branch.yaml +++ b/.github/workflows/chore-manage-pr-branch.yaml @@ -4,14 +4,14 @@ name: Manage PR Branch # PR Quality may close a PR that fails its checks, so we must wait for it to # complete before deciding whether to retarget the PR's base branch. on: + # Only reads the PR Quality run and the pull request through the API, it checks out and runs no code of the pull request + # jactionlint ignore=dangerous-triggers workflow_run: workflows: ["PR Quality"] types: - completed -permissions: - contents: read - pull-requests: write +permissions: {} concurrency: group: manage-pr-branch-${{ github.event.workflow_run.head_sha }} @@ -19,7 +19,12 @@ concurrency: jobs: manage-branch: - runs-on: ubuntu-latest + name: Retarget the pull request + permissions: + contents: read # read the branches of the repository + pull-requests: write # change the base branch of the pull request and comment on it + runs-on: ubuntu-26.04 # https://github.com/actions/runner-images + timeout-minutes: 10 steps: - name: Retarget PR base branch based on category uses: actions/github-script@v9.0.0 diff --git a/.github/workflows/chore-manage-stale-issues-and-prs.yml b/.github/workflows/chore-manage-stale-issues-and-prs.yml index 86c22cfece..58efa69e3a 100644 --- a/.github/workflows/chore-manage-stale-issues-and-prs.yml +++ b/.github/workflows/chore-manage-stale-issues-and-prs.yml @@ -4,13 +4,20 @@ on: schedule: - cron: '0 2 * * *' -permissions: - issues: write - pull-requests: write +permissions: {} + +concurrency: + group: manage-stale + cancel-in-progress: false jobs: manage-stale: - runs-on: ubuntu-latest + name: Manage stale issues and PRs + permissions: + issues: write # label, comment on and close the stale issues + pull-requests: write # label, comment on and close the stale pull requests + runs-on: ubuntu-26.04 # https://github.com/actions/runner-images + timeout-minutes: 10 steps: - name: Manage stale issues and PRs uses: actions/stale@v11.0.0 diff --git a/.github/workflows/chore-pr-comments.yml b/.github/workflows/chore-pr-comments.yml index 821fef1778..43b5b65682 100644 --- a/.github/workflows/chore-pr-comments.yml +++ b/.github/workflows/chore-pr-comments.yml @@ -1,15 +1,29 @@ name: Add comment based on label + on: + # Comments on pull requests from forks too, it checks out and runs no code of the pull request + # jactionlint ignore=dangerous-triggers pull_request_target: types: - labeled -permissions: - pull-requests: write +permissions: {} + +concurrency: + group: pr-comments-${{ github.event.pull_request.number }} + cancel-in-progress: false + +defaults: + run: + shell: bash jobs: add-comment: - runs-on: ubuntu-latest + name: Ask for the docs of ${{ matrix.label }} + permissions: + pull-requests: write # comment on the pull request + runs-on: ubuntu-26.04 # https://github.com/actions/runner-images + timeout-minutes: 10 strategy: matrix: include: @@ -41,9 +55,9 @@ jobs: steps: - name: Add comment if: >- - (github.event.label.name == matrix.label || github.event.label.name == '📑 Waiting for Docs PR') + ${{ (github.event.label.name == matrix.label || github.event.label.name == '📑 Waiting for Docs PR') && contains(github.event.pull_request.labels.*.name, matrix.label) - && contains(github.event.pull_request.labels.*.name, '📑 Waiting for Docs PR') + && contains(github.event.pull_request.labels.*.name, '📑 Waiting for Docs PR') }} run: gh pr comment "$NUMBER" --body "$BODY" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-quality.yaml b/.github/workflows/pr-quality.yaml index b61bf8d86c..9b7ba14224 100644 --- a/.github/workflows/pr-quality.yaml +++ b/.github/workflows/pr-quality.yaml @@ -1,17 +1,26 @@ name: PR Quality -permissions: - contents: read - issues: read - pull-requests: write - on: + # Checks pull requests from forks too, it checks out and runs no code of the pull request + # jactionlint ignore=dangerous-triggers pull_request_target: types: [opened, reopened] +permissions: {} + +concurrency: + group: pr-quality-${{ github.event.pull_request.number }} + cancel-in-progress: false + jobs: pr-quality: - runs-on: ubuntu-latest + name: Check the pull request + permissions: + contents: read # read the commits and files of the pull request + issues: read # read the linked issues + pull-requests: write # comment on, label and close the pull request + runs-on: ubuntu-26.04 # https://github.com/actions/runner-images + timeout-minutes: 10 steps: - uses: peakoss/anti-slop@v0.3.0 with: From c38ce3caefd5ff59bd0e9376b0890f95a621c44e Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Sat, 31 Oct 2026 01:35:20 +0100 Subject: [PATCH 5/6] ci: clean up the issue and pull request workflows - name the files and workflows like the release workflows and every step after what it does - run all workflows without the GitHub Actions cache like the release workflow --- .github/workflows/actions.lock | 17 +- ...lock-closed-issues-discussions-and-prs.yml | 29 --- .github/workflows/chore-manage-pr-branch.yaml | 187 ------------------ .../chore-manage-stale-issues-and-prs.yml | 39 ---- .github/workflows/lock-threads.yaml | 30 +++ .github/workflows/pr-quality.yaml | 15 +- ...-comments.yml => request-docs-update.yaml} | 24 +-- .github/workflows/stale.yaml | 37 ++++ 8 files changed, 91 insertions(+), 287 deletions(-) delete mode 100644 .github/workflows/chore-lock-closed-issues-discussions-and-prs.yml delete mode 100644 .github/workflows/chore-manage-pr-branch.yaml delete mode 100644 .github/workflows/chore-manage-stale-issues-and-prs.yml create mode 100644 .github/workflows/lock-threads.yaml rename .github/workflows/{chore-pr-comments.yml => request-docs-update.yaml} (78%) create mode 100644 .github/workflows/stale.yaml diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index 1ce7be0db8..0de0334bcc 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -8,16 +8,11 @@ workflows: - 'actions/upload-artifact@v7.0.2' - 'docker/build-push-action@v7.4.0' - 'docker/setup-buildx-action@v4.4.1' - '.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml': - - 'dessant/lock-threads@v6.0.2' - '.github/workflows/chore-manage-pr-branch.yaml': - - 'actions/github-script@v9.0.0' - '.github/workflows/chore-manage-stale-issues-and-prs.yml': - - 'actions/stale@v11.0.0' - '.github/workflows/chore-pr-comments.yml': [] '.github/workflows/clean-manifests.yaml': - 'actions/checkout@v7.0.1' - 'actions/upload-artifact@v7.0.2' + '.github/workflows/lock-threads.yaml': + - 'dessant/lock-threads@v6.0.2' '.github/workflows/main.yaml': - 'actions/checkout@v7.0.1' - 'actions/download-artifact@v8.0.2' @@ -37,6 +32,9 @@ workflows: - 'actions/checkout@v7.0.1' - 'actions/download-artifact@v8.0.2' - 'docker/login-action@v4.7.0' + '.github/workflows/request-docs-update.yaml': [] + '.github/workflows/stale.yaml': + - 'actions/stale@v11.0.0' dependencies: 'actions/checkout@v7.0.1': ref: 'v7.0.1' @@ -48,11 +46,6 @@ dependencies: commit: 'sha1-9000827ccba6bdab643e8b6fd33ac0654aef8333' owner_id: 44036562 repo_id: 192626254 - 'actions/github-script@v9.0.0': - ref: 'v9.0.0' - commit: 'sha1-3a2844b7e9c422d3c10d287c895573f7108da1b3' - owner_id: 44036562 - repo_id: 205262760 'actions/stale@v11.0.0': ref: 'v11.0.0' commit: 'sha1-4391f3da665fdf50b6810c1a66712fb9ba21aa93' diff --git a/.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml b/.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml deleted file mode 100644 index 8420f54689..0000000000 --- a/.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Lock closed Issues, Discussions, and PRs - -on: - schedule: - - cron: '0 1 * * *' - -permissions: {} - -concurrency: - group: lock-threads - cancel-in-progress: false - -jobs: - lock-threads: - name: Lock inactive threads - permissions: - issues: write # lock the inactive issues - discussions: write # lock the inactive discussions - pull-requests: write # lock the inactive pull requests - runs-on: ubuntu-26.04 # https://github.com/actions/runner-images - timeout-minutes: 10 - steps: - - name: Lock threads after 30 days of inactivity - uses: dessant/lock-threads@v6.0.2 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - issue-inactive-days: '30' - discussion-inactive-days: '30' - pr-inactive-days: '30' diff --git a/.github/workflows/chore-manage-pr-branch.yaml b/.github/workflows/chore-manage-pr-branch.yaml deleted file mode 100644 index c66ede6f5e..0000000000 --- a/.github/workflows/chore-manage-pr-branch.yaml +++ /dev/null @@ -1,187 +0,0 @@ -name: Manage PR Branch - -# Runs *after* the "PR Quality" workflow finishes. This is required because -# PR Quality may close a PR that fails its checks, so we must wait for it to -# complete before deciding whether to retarget the PR's base branch. -on: - # Only reads the PR Quality run and the pull request through the API, it checks out and runs no code of the pull request - # jactionlint ignore=dangerous-triggers - workflow_run: - workflows: ["PR Quality"] - types: - - completed - -permissions: {} - -concurrency: - group: manage-pr-branch-${{ github.event.workflow_run.head_sha }} - cancel-in-progress: true - -jobs: - manage-branch: - name: Retarget the pull request - permissions: - contents: read # read the branches of the repository - pull-requests: write # change the base branch of the pull request and comment on it - runs-on: ubuntu-26.04 # https://github.com/actions/runner-images - timeout-minutes: 10 - steps: - - name: Retarget PR base branch based on category - uses: actions/github-script@v9.0.0 - with: - script: | - const run = context.payload.workflow_run; - - // Branch routing based on the "Category" section of the PR body. - // Bug fixes and one-click service changes ship in patch releases -> main. - // Everything else (features, improvements) -> next. - const MAIN_BRANCH = 'main'; - const NEXT_BRANCH = 'next'; - - // Maintainers/collaborators are trusted to pick their own base branch. - const EXEMPT_ASSOCIATIONS = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']); - - // Resolve the open PR from the triggering run. - // - // PR Quality runs on `pull_request_target`, so `run.head_sha` is the - // *base* branch tip, not the PR head — a commit-based lookup finds - // nothing. Instead match on the source branch (`head_branch`) and its - // owner (`head_repository.owner.login`), which uniquely identify the PR - // via the `owner:branch` head filter. This also works for forked PRs, - // where `workflow_run.pull_requests` is empty. - const headOwner = run.head_repository?.owner?.login; - const headBranch = run.head_branch; - - let prRef; - if (headOwner && headBranch) { - const { data: openPrs } = await github.rest.pulls.list({ - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open', - head: `${headOwner}:${headBranch}`, - per_page: 100, - }); - prRef = openPrs[0]; - } - - // Fallback: same-repo PRs may also be resolvable by commit association. - if (!prRef) { - const { data: associated } = await github.rest.repos.listPullRequestsAssociatedWithCommit({ - owner: context.repo.owner, - repo: context.repo.repo, - commit_sha: run.head_sha, - }); - prRef = associated.find(pr => pr.state === 'open'); - } - - if (!prRef) { - core.info('No open PR associated with this run (possibly closed by PR Quality). Skipping.'); - return; - } - - // Fetch the full PR to get an up-to-date body, base ref, and state. - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prRef.number, - }); - - if (pr.state !== 'open') { - core.info(`PR #${pr.number} is not open. Skipping.`); - return; - } - - // Skip PRs opened by owners/members/collaborators — they choose their own base. - if (EXEMPT_ASSOCIATIONS.has(pr.author_association)) { - core.info(`PR #${pr.number} author association is ${pr.author_association}. Skipping.`); - return; - } - - // Skip if a maintainer has already changed the base branch manually. - const timeline = await github.paginate(github.rest.issues.listEventsForTimeline, { - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - per_page: 100, - }); - - const baseChanges = timeline.filter(e => e.event === 'base_ref_changed'); - for (const change of baseChanges) { - const actor = change.actor?.login; - if (!actor) { - continue; - } - try { - const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({ - owner: context.repo.owner, - repo: context.repo.repo, - username: actor, - }); - // admin/maintain/write => trusted maintainer. - if (['admin', 'maintain', 'write'].includes(perm.permission)) { - core.info(`Base branch was changed manually by ${actor} (${perm.permission}). Skipping.`); - return; - } - } catch (error) { - core.info(`Could not resolve permission for ${actor}: ${error.message}`); - } - } - - // Parse the checked category checkboxes from the PR body. - const body = pr.body ?? ''; - const checked = []; - const checkboxRegex = /^\s*-\s*\[([ xX])\]\s*(.+?)\s*$/gm; - let match; - while ((match = checkboxRegex.exec(body)) !== null) { - if (match[1].toLowerCase() === 'x') { - checked.push(match[2].toLowerCase()); - } - } - - const includesAny = (labels) => labels.some(label => checked.some(c => c.includes(label))); - - const mainCategories = ['bug fix', 'adding new one click service', 'fixing or updating existing one click service']; - const nextCategories = ['improvement', 'new feature']; - - const wantsMain = includesAny(mainCategories); - const wantsNext = includesAny(nextCategories); - - if (!wantsMain && !wantsNext) { - core.info('No category selected in the PR body. Skipping.'); - return; - } - - // If categories from both groups are checked, prefer next: features and - // improvements can only be released from the development branch. - const targetBranch = wantsNext ? NEXT_BRANCH : MAIN_BRANCH; - - if (pr.base.ref === targetBranch) { - core.info(`PR #${pr.number} already targets ${targetBranch}. Nothing to do.`); - return; - } - - const previousBranch = pr.base.ref; - - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: pr.number, - base: targetBranch, - }); - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - body: [ - `Based on the selected category, this PR's base branch was automatically changed from \`${previousBranch}\` to \`${targetBranch}\`.`, - '', - targetBranch === MAIN_BRANCH - ? 'Bug fixes and one-click service changes target `main`.' - : 'New features and improvements target `next`.', - '', - 'If you believe this is incorrect, please let a maintainer know.', - ].join('\n'), - }); - - core.info(`Retargeted PR #${pr.number}: ${previousBranch} -> ${targetBranch}.`); diff --git a/.github/workflows/chore-manage-stale-issues-and-prs.yml b/.github/workflows/chore-manage-stale-issues-and-prs.yml deleted file mode 100644 index 58efa69e3a..0000000000 --- a/.github/workflows/chore-manage-stale-issues-and-prs.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Manage Stale Issues and PRs - -on: - schedule: - - cron: '0 2 * * *' - -permissions: {} - -concurrency: - group: manage-stale - cancel-in-progress: false - -jobs: - manage-stale: - name: Manage stale issues and PRs - permissions: - issues: write # label, comment on and close the stale issues - pull-requests: write # label, comment on and close the stale pull requests - runs-on: ubuntu-26.04 # https://github.com/actions/runner-images - timeout-minutes: 10 - steps: - - name: Manage stale issues and PRs - uses: actions/stale@v11.0.0 - id: stale - with: - stale-issue-message: 'This issue will be automatically closed in a few days if no response is received. Please provide an update with the requested information.' - stale-pr-message: 'This pull request requires attention. If no changes or response is received within the next few days, it will be automatically closed. Please update your PR or leave a comment with the requested information.' - close-issue-message: 'This issue has been automatically closed due to inactivity.' - close-pr-message: 'Thank you for your contribution. Due to inactivity, this PR was automatically closed. If you would like to continue working on this change in the future, feel free to reopen this PR or submit a new one.' - days-before-stale: 14 - days-before-close: 7 - stale-issue-label: '⏱︎ Stale' - stale-pr-label: '⏱︎ Stale' - only-labels: '💤 Waiting for feedback, 💤 Waiting for changes' - remove-stale-when-updated: true - operations-per-run: 100 - labels-to-remove-when-unstale: '⏱︎ Stale, 💤 Waiting for feedback, 💤 Waiting for changes' - close-issue-reason: 'not_planned' - exempt-all-milestones: false diff --git a/.github/workflows/lock-threads.yaml b/.github/workflows/lock-threads.yaml new file mode 100644 index 0000000000..aa8c8912e3 --- /dev/null +++ b/.github/workflows/lock-threads.yaml @@ -0,0 +1,30 @@ +name: Lock threads + +on: + schedule: + - cron: "0 1 * * *" + +permissions: {} + +cache-mode: none + +concurrency: + group: lock-threads + cancel-in-progress: false + +jobs: + lock: + name: Lock threads + permissions: + issues: write # lock the closed issues + discussions: write # lock the closed discussions + pull-requests: write # lock the closed pull requests + runs-on: ubuntu-26.04 # https://github.com/actions/runner-images + timeout-minutes: 10 + steps: + - name: Lock closed threads that were inactive for the last 30 days + uses: dessant/lock-threads@v6.0.2 + with: + issue-inactive-days: 30 + discussion-inactive-days: 30 + pr-inactive-days: 30 diff --git a/.github/workflows/pr-quality.yaml b/.github/workflows/pr-quality.yaml index 9b7ba14224..878b453ee2 100644 --- a/.github/workflows/pr-quality.yaml +++ b/.github/workflows/pr-quality.yaml @@ -1,4 +1,4 @@ -name: PR Quality +name: PR quality on: # Checks pull requests from forks too, it checks out and runs no code of the pull request @@ -8,21 +8,24 @@ on: permissions: {} +cache-mode: none + concurrency: group: pr-quality-${{ github.event.pull_request.number }} cancel-in-progress: false jobs: - pr-quality: - name: Check the pull request + check: + name: Check pull request permissions: - contents: read # read the commits and files of the pull request + contents: read issues: read # read the linked issues pull-requests: write # comment on, label and close the pull request runs-on: ubuntu-26.04 # https://github.com/actions/runner-images timeout-minutes: 10 steps: - - uses: peakoss/anti-slop@v0.3.0 + - name: Check the pull request + uses: peakoss/anti-slop@v0.3.0 with: # General Settings max-failures: 4 @@ -46,7 +49,7 @@ jobs: max-emoji-count: 2 max-code-references: 5 require-linked-issue: false - blocked-terms: | + blocked-terms: | STRAWBERRY 🤖 Generated with Claude Code Generated with Claude Code diff --git a/.github/workflows/chore-pr-comments.yml b/.github/workflows/request-docs-update.yaml similarity index 78% rename from .github/workflows/chore-pr-comments.yml rename to .github/workflows/request-docs-update.yaml index 43b5b65682..03db87b200 100644 --- a/.github/workflows/chore-pr-comments.yml +++ b/.github/workflows/request-docs-update.yaml @@ -1,16 +1,17 @@ -name: Add comment based on label +name: Request docs update on: # Comments on pull requests from forks too, it checks out and runs no code of the pull request # jactionlint ignore=dangerous-triggers pull_request_target: - types: - - labeled + types: [labeled] permissions: {} +cache-mode: none + concurrency: - group: pr-comments-${{ github.event.pull_request.number }} + group: request-docs-update-${{ github.event.pull_request.number }} cancel-in-progress: false defaults: @@ -18,8 +19,8 @@ defaults: shell: bash jobs: - add-comment: - name: Ask for the docs of ${{ matrix.label }} + comment: + name: Request docs update (${{ matrix.label }}) permissions: pull-requests: write # comment on the pull request runs-on: ubuntu-26.04 # https://github.com/actions/runner-images @@ -47,20 +48,15 @@ jobs: Coolify Docs Repository: https://github.com/coollabsio/coolify-docs How to Contribute to the Docs: https://coolify.io/docs/get-started/contribute/documentation - # - label: "✨ Enhancement" - # body: | - # It appears to us that you are making an enhancement to Coolify. - # We kindly ask you to also review and update the Coolify Documentation to include information about this enhancement if applicable. - # This will help ensure that our documentation remains accurate and up-to-date for all users. steps: - - name: Add comment + - name: Comment on the pull request if: >- ${{ (github.event.label.name == matrix.label || github.event.label.name == '📑 Waiting for Docs PR') && contains(github.event.pull_request.labels.*.name, matrix.label) && contains(github.event.pull_request.labels.*.name, '📑 Waiting for Docs PR') }} - run: gh pr comment "$NUMBER" --body "$BODY" env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} NUMBER: ${{ github.event.pull_request.number }} BODY: ${{ matrix.body }} + run: gh pr comment "$NUMBER" --body "$BODY" diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml new file mode 100644 index 0000000000..5484314ebd --- /dev/null +++ b/.github/workflows/stale.yaml @@ -0,0 +1,37 @@ +name: Stale + +on: + schedule: + - cron: "0 2 * * *" + +permissions: {} + +cache-mode: none + +concurrency: + group: stale + cancel-in-progress: false + +jobs: + stale: + name: Close stale issues and pull requests + permissions: + issues: write # label, comment on and close the stale issues + pull-requests: write # label, comment on and close the stale pull requests + runs-on: ubuntu-26.04 # https://github.com/actions/runner-images + timeout-minutes: 10 + steps: + - name: Close the issues and pull requests that wait for feedback or changes + uses: actions/stale@v11.0.0 + with: + only-labels: "💤 Waiting for feedback, 💤 Waiting for changes" + days-before-stale: 14 + days-before-close: 7 + stale-issue-label: "⏱︎ Stale" + stale-pr-label: "⏱︎ Stale" + labels-to-remove-when-unstale: "⏱︎ Stale, 💤 Waiting for feedback, 💤 Waiting for changes" + stale-issue-message: This issue will be automatically closed in a few days if no response is received. Please provide an update with the requested information. + stale-pr-message: This pull request requires attention. If no changes or response is received within the next few days, it will be automatically closed. Please update your PR or leave a comment with the requested information. + close-issue-message: This issue has been automatically closed due to inactivity. + close-pr-message: Thank you for your contribution. Due to inactivity, this PR was automatically closed. If you would like to continue working on this change in the future, feel free to reopen this PR or submit a new one. + operations-per-run: 100 From 3f7d9891a836efa16acfebeb6e001e5c81b86d35 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Sun, 1 Nov 2026 15:37:56 +0100 Subject: [PATCH 6/6] docs: add the harden workflows changes to CHANGELOG.md --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d47cb9a17..e8eb867ebf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ## [Unreleased] +### Security + +- Refuse to update, force-push or delete the git tag of a release, with a tag protection rule on GitHub +- Refuse to delete or force-push `main`, `next`, release and maintenance branches, with branch protection rules on GitHub +- Protect archive branches from updates, deletions and force-pushing, with branch protection rules on GitHub +- Harden the CI workflows + - Require every action to be pinned to a full-length commit SHA + - Grant every job the least permissions it needs and state the reason for every write permission + - Keep the Docker Hub credentials and the CDN token in the `release` environment, which only the branches that release can use + - Keep the tokens that delete images in the `cleanup` environment, which only `main` can use + - Lock every action and the actions it calls to a commit in `.github/workflows/actions.lock`, together with the IDs of its owner and repository, which a commit SHA alone does not check + - Run without the GitHub Actions cache, layer caches and cached tool binaries, so no run restores what another run wrote + - Fix every finding of jactionlint pedantic + ### Added - Choose the update channel of an instance on the update page: stable, preview with betas and release candidates, nightly with a warning that breaking changes are possible, or a custom image such as `pr-1234` @@ -48,6 +62,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Removed +- Remove the workflow that deleted every untagged image on ghcr.io, one run would delete the platform images and attestations of every release +- Remove the workflow that moved pull requests to `main` or `next` by their category, for now - Remove the Windows support with Docker Desktop, `docker-compose.windows.yml` and `.env.windows-docker-desktop.example`, and the testing host image on Docker Hub, which only it used - Remove the Claude workflow and the temporary workflow and image of the PHP base image - Remove the git-cliff workflow that generated `CHANGELOG.md`, it is written by hand @@ -60,4 +76,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Maintenance +- Add `scripts/clean-manifests.sh` and a workflow that delete the unused manifests from ghcr.io and docker.io, such as the leftovers of the old workflows and the images of closed pull requests, and keep every release, every moving tag, the nightlies of the last 14 days and the images of open pull requests +- Give every job a name, a timeout, a fixed runner image and a concurrency group +- Bump `actions/stale` to v11, GitHub Actions removed the Node.js 20 it ran on - Describe the new release process in `AGENTS.md`