ci: publish to batch-archive with a scoped bee-runner App token - #17
ci: publish to batch-archive with a scoped bee-runner App token#17darkobas2 wants to merge 2 commits into
Conversation
The GHA_PAT_BASIC org secret was never shared with this repo, so `Checkout batch-archive` resolved its token to empty and failed with "Input required and not supplied: token". Rather than widen that PAT (contents:write across 16 repos, tied to a personal account), mint a token from the bee-runner GitHub App, which is already installed org-wide with contents:write. The token is scoped to batch-archive alone, narrowed to contents:write, and expires in ~1h. Because of that expiry it is minted after the export, not at checkout — the export can easily outrun an hour. batch-archive is public, so the initial clone needs no App token at all; it uses GITHUB_TOKEN with persist-credentials: false so nothing long-lived sits in .git/config while the export runs, and the final push carries the App token itself.
gacevicljubisa
left a comment
There was a problem hiding this comment.
@darkobas2 this works — I triggered Batch Sync from this branch and it completed successfully (run 33431048291), so the App-token flow is doing its job and the scoping looks right.
Two things below that could be improved later. Neither needs to hold up this PR.
| # Minted here, not at checkout: an App token lives ~1h and the export | ||
| # above can outrun that. permission-contents keeps it to what the push | ||
| # needs, and repositories keeps it off every other repo in the org. | ||
| - name: Generate token for batch-archive |
There was a problem hiding this comment.
Minting the token here rather than at checkout makes sense given the ~1h token lifetime, but it does move credential failures to the end of the run. Previously a bad credential failed at Checkout batch-archive within seconds — that is how the empty GHA_PAT_BASIC got caught quickly. Now, if the App loses contents:write or the key visibility changes, it surfaces after an export that can run up to the full 120-minute timeout.
That is worse than just lost time: snapshot.ndjson.gzip only exists in the runner workspace, and the next run's git clean -ffdx wipes it, so the retry re-exports everything from scratch.
Two cheap options if you want to keep the late mint:
- a pre-flight before
Exportthat mints and discards a token — it validates the installation and the permission in a couple of seconds; - an
actions/upload-artifactwithif: failure()so a failed publish does not cost the whole export.
Worth noting the same loss applies to a non-fast-forward rejection on the final push.
| # orphaned tag can become a later run's resume point. | ||
| git push --atomic origin HEAD:main "refs/tags/${new_tag}" | ||
| git push --atomic \ | ||
| "https://x-access-token:${ARCHIVE_TOKEN}@github.com/ethersphere/batch-archive.git" \ |
There was a problem hiding this comment.
Small hardening idea: passing the token inside the URL puts it in git's argv, so while the push runs it is readable from ps -eo args or /proc/<pid>/cmdline by anything else on the host. With runs-on: [self-hosted, linux, bee] those are long-lived shared machines rather than ephemeral VMs, so another job on the same runner could read it and use it for contents:write on batch-archive until the post step revokes it.
Keeping the value in the environment avoids that:
git -c credential.helper= \
-c credential.helper='!f(){ echo username=x-access-token; echo "password=${ARCHIVE_TOKEN}"; };f' \
push --atomic origin HEAD:main "refs/tags/${new_tag}"The single quotes are what keep ${ARCHIVE_TOKEN} off the command line. As a side benefit the push can stay on origin instead of a hardcoded github.com/ethersphere/batch-archive.git, which is currently the third literal copy of the repo slug in this file and the one place a GHES/mirror setup would break.
Addresses both review comments on #17. Minting the publish token after the export means an App misconfiguration surfaces up to 120 minutes in, where the empty GHA_PAT_BASIC failed in seconds. Mint once at the top of the job purely as a check; the token is never used and is revoked by the post step. Passing the token in the remote URL puts it in git argv, readable via ps -eo args or /proc/<pid>/cmdline. On the self-hosted bee runners those are long-lived shared machines, so a co-tenant job could lift it and hold contents:write on batch-archive until revocation. Read it from the environment through a credential helper instead.
The first real run of
Batch Sync(run 33394634626) failed atCheckout batch-archive:GHA_PAT_BASICis an org secret withselectedvisibility and this repo was never added to its list, so${{ secrets.GHA_PAT_BASIC }}resolved to an empty string.Why not just share the PAT
That PAT carries
contents:writeacross 16 repos and is tied to a personal account. Thebee-runnerGitHub App (app_id 84297) is already installed org-wide withcontents: write, and is the established pattern here — seeethersphere/bee/.github/workflows/swarm-cli-bee-version.yaml. A minted App token is scoped to one repo, narrowed to one permission, short-lived, and rotatable without touching anyone's account.Changes
Checkout batch-archiveusedGHA_PAT_BASICgithub.tokenwithpersist-credentials: false— batch-archive is public, so the clone needs no elevated tokenPublishThe token is minted after the export rather than at checkout: App tokens expire in ~1h and the export can run far longer, so minting early would leave the final push holding an expired credential. As a side effect no write-capable credential exists on disk for the length of the run.
Also configured (outside this diff)
batch-exportadded to thebee-runnersrunner group — jobs were queueing forever with no eligible runner, which is what stalled the run for 2h53m before it was cancelled.BEE_RUNNER_KEYshared with this repo (BEE_RUNNER_APP_IDwas already visibilityall).PRIVATE_GNOSIS_RPC_URLset as a repo secret.GNOSIS_RPC_USER/GNOSIS_RPC_PASSWORDremain unset — the self-hostedbeerunners are inside the RPC IP allowlist, soCompose endpointtakes the unauthenticated path.