Use one Docker Hub credential for images and the overview

This commit is contained in:
2026-08-22 14:27:31 +02:00
parent bcff5967ad
commit 47f4498fff
3 changed files with 8 additions and 10 deletions
+2 -2
View File
@@ -88,8 +88,8 @@ jobs:
ref: refs/tags/v${{ inputs.overview_version }} ref: refs/tags/v${{ inputs.overview_version }}
- name: Publish the Docker Hub overview - name: Publish the Docker Hub overview
env: env:
DOCKERHUB_OVERVIEW_TOKEN: ${{ secrets.DOCKERHUB_OVERVIEW_TOKEN }}
DOCKERHUB_REPO: ${{ github.repository }} DOCKERHUB_REPO: ${{ github.repository }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
DOCKERHUB_USER: ${{ vars.DOCKERHUB_USER }} DOCKERHUB_USER: ${{ vars.DOCKERHUB_USER }}
GIT_TAG: ${{ github.ref_name }} GIT_TAG: ${{ github.ref_name }}
INPUT_VERSION: ${{ inputs.overview_version }} INPUT_VERSION: ${{ inputs.overview_version }}
@@ -101,6 +101,6 @@ jobs:
docker run --rm -v "$PWD:/repo" -w /repo node:24.19.0-alpine3.24 \ docker run --rm -v "$PWD:/repo" -w /repo node:24.19.0-alpine3.24 \
node release-tooling/contract-version.ts "$VERSION" src/plugin-host/plugin.ts node release-tooling/contract-version.ts "$VERSION" src/plugin-host/plugin.ts
docker run --rm -v "$PWD:/repo" -w /repo \ docker run --rm -v "$PWD:/repo" -w /repo \
-e DOCKERHUB_OVERVIEW_TOKEN -e DOCKERHUB_REPO -e DOCKERHUB_USER \ -e DOCKERHUB_REPO -e DOCKERHUB_TOKEN -e DOCKERHUB_USER \
node:24.19.0-alpine3.24 \ node:24.19.0-alpine3.24 \
node release-tooling/dockerhub-overview.ts "$VERSION" node release-tooling/dockerhub-overview.ts "$VERSION"
+2 -4
View File
@@ -1424,8 +1424,7 @@ outage leaves the promotion green and the images untouched; and the page has its
workflow manually with an `overview_version` input to republish it without cutting a release. That workflow manually with an `overview_version` input to republish it without cutting a release. That
input goes through the same contract check as a tag: a non-semver value, or one whose `major.minor` input goes through the same contract check as a tag: a non-semver value, or one whose `major.minor`
disagrees with the tree being published, is refused. It uses disagrees with the tree being published, is refused. It uses
`DOCKERHUB_OVERVIEW_TOKEN`, separate from the image-push token because editing repository metadata is the same `DOCKERHUB_TOKEN` the image push uses, which is why that token needs the **delete** scope.
a different permission and widening the push credential to cover it would widen what a leak costs.
**GitHub mirror** — [github.com/larvit/plainpages](https://github.com/larvit/plainpages) is **GitHub mirror** — [github.com/larvit/plainpages](https://github.com/larvit/plainpages) is
read-only; after every merge `mirror.yml` force-pushes `main` and all tags, overwriting any drift. read-only; after every merge `mirror.yml` force-pushes `main` and all tags, overwriting any drift.
@@ -1462,8 +1461,7 @@ the built-in Actions token wouldn't trigger it). `HOST_API_VERSION` is never tou
| Actions var / secret | Value | | Actions var / secret | Value |
| --- | --- | | --- | --- |
| `DOCKER_REGISTRY_USER` (var) + `DOCKER_REGISTRY_TOKEN` (secret) | A Gitea account with package write in the `larvit` org, and its access token with `read:package` + `write:package`. Reused by `registry-cleanup.yml`. | | `DOCKER_REGISTRY_USER` (var) + `DOCKER_REGISTRY_TOKEN` (secret) | A Gitea account with package write in the `larvit` org, and its access token with `read:package` + `write:package`. Reused by `registry-cleanup.yml`. |
| `DOCKERHUB_USER` (var) + `DOCKERHUB_TOKEN` (secret) | The public `larvit/plainpages` Docker Hub repo, and a read/write token **scoped to that repository** (an org access token, or one on a dedicated account — an account-wide PAT can push to every repo under it). | | `DOCKERHUB_USER` (var) + `DOCKERHUB_TOKEN` (secret) | The public `larvit/plainpages` Docker Hub repo, and a **read/write/delete** token **scoped to that repository** (an org access token, or one on a dedicated account — an account-wide PAT reaches every repo under it, and delete is destructive). Delete is what publishing the overview needs; pushing images alone would not. |
| `DOCKERHUB_OVERVIEW_TOKEN` (secret) | A Docker Hub PAT with **read/write/delete** scope — editing the overview needs delete, pushing images does not. Kept separate so that scope never reaches `docker login`, which writes it to the runner's shared config; without it the `publish-overview` job fails and the released images are unaffected. |
| `MIRROR_GITHUB_TOKEN` (secret) | A fine-grained PAT (Contents: read & write) for a GitHub machine account with write access to the mirror. Its `main` must not block force-pushes and must carry no tag protection, which would reject the prune. | | `MIRROR_GITHUB_TOKEN` (secret) | A fine-grained PAT (Contents: read & write) for a GitHub machine account with write access to the mirror. Its `main` must not block force-pushes and must carry no tag protection, which would reject the prune. |
| `RENOVATE_TOKEN` (secret) | The shared `renovate@larvit.se` bot's Gitea PAT, with write access to this repo. | | `RENOVATE_TOKEN` (secret) | The shared `renovate@larvit.se` bot's Gitea PAT, with write access to this repo. |
| `RENOVATE_GITHUB_TOKEN` (secret) | A **scopeless** (read-only) github.com PAT, so Renovate's lookups of github.com-hosted deps run authenticated instead of tripping the anonymous 60-req/hour limit. | | `RENOVATE_GITHUB_TOKEN` (secret) | A **scopeless** (read-only) github.com PAT, so Renovate's lookups of github.com-hosted deps run authenticated instead of tripping the anonymous 60-req/hour limit. |
+4 -4
View File
@@ -49,11 +49,11 @@ async function main(): Promise<number> {
const [, , version] = process.argv; const [, , version] = process.argv;
const repo = process.env["DOCKERHUB_REPO"]; const repo = process.env["DOCKERHUB_REPO"];
const user = process.env["DOCKERHUB_USER"]; const user = process.env["DOCKERHUB_USER"];
const token = process.env["DOCKERHUB_OVERVIEW_TOKEN"]; const token = process.env["DOCKERHUB_TOKEN"];
if (!version || !repo || !user || !token) { if (!version || !repo || !user || !token) {
return fail( return fail(
"usage: dockerhub-overview.ts <X.Y.Z>; needs DOCKERHUB_REPO, DOCKERHUB_USER and " + "usage: dockerhub-overview.ts <X.Y.Z>; needs DOCKERHUB_REPO, DOCKERHUB_USER and " +
"DOCKERHUB_OVERVIEW_TOKEN (README -> CI/CD)", "DOCKERHUB_TOKEN (README -> CI/CD)",
); );
} }
// The page is public, so never render a version that resolves to no image. // The page is public, so never render a version that resolves to no image.
@@ -90,8 +90,8 @@ async function main(): Promise<number> {
return fail( return fail(
`Docker Hub overview PATCH failed: ${res.status} ${res.text}` + `Docker Hub overview PATCH failed: ${res.status} ${res.text}` +
(res.status === 403 (res.status === 403
? "\n403 means DOCKERHUB_OVERVIEW_TOKEN lacks the delete scope — editing the overview needs " + ? "\n403 means DOCKERHUB_TOKEN lacks the delete scope — editing the overview needs " +
"read/write/delete, which pushing images does not (README -> CI/CD)." "read/write/delete, which pushing images alone does not (README -> CI/CD)."
: ""), : ""),
); );
} }