rickub/clipublic Fork
⑂ 900561e
Commits
⬇ Clone ▾
git clone https://git.rickub.com/rickub/cli.git
git clone ssh://git@rickub.com/rickub/cli.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

ci: use the marketplace actions with plain @vN tags, as a normal job wouldUnverified

Back to actions/checkout@v4, actions/setup-go@v5, actions/upload-artifact@v4
and softprops/action-gh-release@v2, and drop scripts/publish-release.sh.
These workflows are meant to read as an ordinary GitHub Actions job;
anonymous action fetches being rate-limited on the fleet is a runner
configuration matter (RICKUB_ACTIONS_GITHUB_TOKEN), documented in
RELEASING.md rather than worked around here. Keeps the GITHUB_WORKFLOW fix.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgkcT3UPxwxukBKojB5eg4
Olivier Girardot committed 2026-09-02T21:44:32+02:00 Browse files
900561e parent: 5bbf166
modified .github/workflows/ci.yml +2 -2
@@ -23,10 +23,10 @@ jobs:
2323 runs-on: ubuntu-latest
2424 steps:
2525 - name: Checkout
26- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
26+ uses: actions/checkout@v4
2727
2828 - name: Set up Go
29- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
29+ uses: actions/setup-go@v5
3030 with:
3131 go-version-file: go.mod
3232 cache: true
@@ -23,10 +23,10 @@ jobs:
23 runs-on: ubuntu-latest23 runs-on: ubuntu-latest
24 steps:24 steps:
25 - name: Checkout25 - name: Checkout
26- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v426+ uses: actions/checkout@v4
27 27
28 - name: Set up Go28 - name: Set up Go
29- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v529+ uses: actions/setup-go@v5
30 with:30 with:
31 go-version-file: go.mod31 go-version-file: go.mod
32 cache: true32 cache: true
modified .github/workflows/rc.yml +17 -12
@@ -26,10 +26,10 @@ jobs:
2626 runs-on: ubuntu-latest
2727 steps:
2828 - name: Checkout
29- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
29+ uses: actions/checkout@v4
3030
3131 - name: Set up Go
32- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
32+ uses: actions/setup-go@v5
3333 with:
3434 go-version-file: go.mod
3535 cache: true
@@ -89,7 +89,7 @@ jobs:
8989 echo "path=${RUNNER_TEMP}/rc-notes.md" >> "$GITHUB_OUTPUT"
9090
9191 - name: Upload build artifacts
92- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
92+ uses: actions/upload-artifact@v4
9393 with:
9494 name: rickub-${{ steps.version.outputs.version }}
9595 path: dist/
@@ -97,12 +97,17 @@ jobs:
9797 retention-days: 14
9898
9999 - name: Publish prerelease
100- env:
101- TAG: ${{ steps.version.outputs.tag }}
102- NOTES: ${{ steps.notes.outputs.path }}
103- # The tag does not exist yet: the release shim creates it at --target,
104- # so CI cuts its own RC tags.
105- run: |
106- bash scripts/publish-release.sh --prerelease \
107- --tag "${TAG}" --target "${GITHUB_SHA}" --notes "${NOTES}" \
108- dist/*.tar.gz dist/SHA256SUMS
100+ uses: softprops/action-gh-release@v2
101+ with:
102+ tag_name: ${{ steps.version.outputs.tag }}
103+ name: ${{ steps.version.outputs.tag }}
104+ body_path: ${{ steps.notes.outputs.path }}
105+ # The tag does not exist yet: the rickub release shim creates it at
106+ # target_commitish, so CI cuts its own RC tags.
107+ target_commitish: ${{ github.sha }}
108+ draft: false
109+ prerelease: true
110+ files: |
111+ dist/*.tar.gz
112+ dist/SHA256SUMS
113+ fail_on_unmatched_files: true
@@ -26,10 +26,10 @@ jobs:
26 runs-on: ubuntu-latest26 runs-on: ubuntu-latest
27 steps:27 steps:
28 - name: Checkout28 - name: Checkout
29- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v429+ uses: actions/checkout@v4
30 30
31 - name: Set up Go31 - name: Set up Go
32- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v532+ uses: actions/setup-go@v5
33 with:33 with:
34 go-version-file: go.mod34 go-version-file: go.mod
35 cache: true35 cache: true
@@ -89,7 +89,7 @@ jobs:
89 echo "path=${RUNNER_TEMP}/rc-notes.md" >> "$GITHUB_OUTPUT"89 echo "path=${RUNNER_TEMP}/rc-notes.md" >> "$GITHUB_OUTPUT"
90 90
91 - name: Upload build artifacts91 - name: Upload build artifacts
92- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v492+ uses: actions/upload-artifact@v4
93 with:93 with:
94 name: rickub-${{ steps.version.outputs.version }}94 name: rickub-${{ steps.version.outputs.version }}
95 path: dist/95 path: dist/
@@ -97,12 +97,17 @@ jobs:
97 retention-days: 1497 retention-days: 14
98 98
99 - name: Publish prerelease99 - name: Publish prerelease
100- env:100+ uses: softprops/action-gh-release@v2
101- TAG: ${{ steps.version.outputs.tag }}101+ with:
102- NOTES: ${{ steps.notes.outputs.path }}102+ tag_name: ${{ steps.version.outputs.tag }}
103- # The tag does not exist yet: the release shim creates it at --target,103+ name: ${{ steps.version.outputs.tag }}
104- # so CI cuts its own RC tags.104+ body_path: ${{ steps.notes.outputs.path }}
105- run: |105+ # The tag does not exist yet: the rickub release shim creates it at
106- bash scripts/publish-release.sh --prerelease \106+ # target_commitish, so CI cuts its own RC tags.
107- --tag "${TAG}" --target "${GITHUB_SHA}" --notes "${NOTES}" \107+ target_commitish: ${{ github.sha }}
108- dist/*.tar.gz dist/SHA256SUMS108+ draft: false
109+ prerelease: true
110+ files: |
111+ dist/*.tar.gz
112+ dist/SHA256SUMS
113+ fail_on_unmatched_files: true
modified .github/workflows/release.yml +17 -12
@@ -36,7 +36,7 @@ jobs:
3636 runs-on: ubuntu-latest
3737 steps:
3838 - name: Checkout
39- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
39+ uses: actions/checkout@v4
4040 with:
4141 # Full history + tags so the "tag already exists" check is meaningful.
4242 fetch-depth: 0
@@ -133,7 +133,7 @@ jobs:
133133 esac
134134
135135 - name: Set up Go
136- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
136+ uses: actions/setup-go@v5
137137 with:
138138 go-version-file: go.mod
139139 cache: true
@@ -180,7 +180,7 @@ jobs:
180180 echo "path=${RUNNER_TEMP}/release-notes.md" >> "$GITHUB_OUTPUT"
181181
182182 - name: Upload build artifacts
183- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
183+ uses: actions/upload-artifact@v4
184184 with:
185185 name: rickub-${{ steps.check.outputs.version }}
186186 path: dist/
@@ -189,15 +189,20 @@ jobs:
189189
190190 - name: Publish release
191191 if: steps.check.outputs.dry_run != 'true'
192- env:
193- TAG: ${{ steps.check.outputs.tag }}
194- NOTES: ${{ steps.notes.outputs.path }}
195- # The tag does not exist yet: the release shim creates it at --target,
196- # so the dispatched ref's commit is what gets tagged.
197- run: |
198- bash scripts/publish-release.sh \
199- --tag "${TAG}" --target "${GITHUB_SHA}" --notes "${NOTES}" \
200- dist/*.tar.gz dist/SHA256SUMS
192+ uses: softprops/action-gh-release@v2
193+ with:
194+ tag_name: ${{ steps.check.outputs.tag }}
195+ name: ${{ steps.check.outputs.tag }}
196+ body_path: ${{ steps.notes.outputs.path }}
197+ # The tag does not exist yet: the rickub release shim creates it at
198+ # target_commitish, so the dispatched ref's commit is what gets tagged.
199+ target_commitish: ${{ github.sha }}
200+ draft: false
201+ prerelease: false
202+ files: |
203+ dist/*.tar.gz
204+ dist/SHA256SUMS
205+ fail_on_unmatched_files: true
201206
202207 - name: Dry run summary
203208 if: steps.check.outputs.dry_run == 'true'
@@ -36,7 +36,7 @@ jobs:
36 runs-on: ubuntu-latest36 runs-on: ubuntu-latest
37 steps:37 steps:
38 - name: Checkout38 - name: Checkout
39- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v439+ uses: actions/checkout@v4
40 with:40 with:
41 # Full history + tags so the "tag already exists" check is meaningful.41 # Full history + tags so the "tag already exists" check is meaningful.
42 fetch-depth: 042 fetch-depth: 0
@@ -133,7 +133,7 @@ jobs:
133 esac133 esac
134 134
135 - name: Set up Go135 - name: Set up Go
136- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5136+ uses: actions/setup-go@v5
137 with:137 with:
138 go-version-file: go.mod138 go-version-file: go.mod
139 cache: true139 cache: true
@@ -180,7 +180,7 @@ jobs:
180 echo "path=${RUNNER_TEMP}/release-notes.md" >> "$GITHUB_OUTPUT"180 echo "path=${RUNNER_TEMP}/release-notes.md" >> "$GITHUB_OUTPUT"
181 181
182 - name: Upload build artifacts182 - name: Upload build artifacts
183- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4183+ uses: actions/upload-artifact@v4
184 with:184 with:
185 name: rickub-${{ steps.check.outputs.version }}185 name: rickub-${{ steps.check.outputs.version }}
186 path: dist/186 path: dist/
@@ -189,15 +189,20 @@ jobs:
189 189
190 - name: Publish release190 - name: Publish release
191 if: steps.check.outputs.dry_run != 'true'191 if: steps.check.outputs.dry_run != 'true'
192- env:192+ uses: softprops/action-gh-release@v2
193- TAG: ${{ steps.check.outputs.tag }}193+ with:
194- NOTES: ${{ steps.notes.outputs.path }}194+ tag_name: ${{ steps.check.outputs.tag }}
195- # The tag does not exist yet: the release shim creates it at --target,195+ name: ${{ steps.check.outputs.tag }}
196- # so the dispatched ref's commit is what gets tagged.196+ body_path: ${{ steps.notes.outputs.path }}
197- run: |197+ # The tag does not exist yet: the rickub release shim creates it at
198- bash scripts/publish-release.sh \198+ # target_commitish, so the dispatched ref's commit is what gets tagged.
199- --tag "${TAG}" --target "${GITHUB_SHA}" --notes "${NOTES}" \199+ target_commitish: ${{ github.sha }}
200- dist/*.tar.gz dist/SHA256SUMS200+ draft: false
201+ prerelease: false
202+ files: |
203+ dist/*.tar.gz
204+ dist/SHA256SUMS
205+ fail_on_unmatched_files: true
201 206
202 - name: Dry run summary207 - name: Dry run summary
203 if: steps.check.outputs.dry_run == 'true'208 if: steps.check.outputs.dry_run == 'true'
modified RELEASING.md +42 -53
@@ -9,50 +9,40 @@ GitHub-Actions-compatible CI:
99 | [`.github/workflows/rc.yml`](.github/workflows/rc.yml) | every push to `main` | tests, cross-compiles all four targets, publishes a **prerelease** `v<BASE>-rc.<run_number>` |
1010 | [`.github/workflows/release.yml`](.github/workflows/release.yml) | **manual only** (`workflow_dispatch`) | tests, cross-compiles, publishes a real release `v<version>` |
1111
12-Both release-producing workflows call the same two scripts,
13-[`scripts/build-dist.sh`](scripts/build-dist.sh) to build and
14-[`scripts/publish-release.sh`](scripts/publish-release.sh) to publish, so an RC
15-and a real release are byte-for-byte the same pipeline with a different version
16-stamp and the `--prerelease` flag.
12+Both release-producing workflows call the same script,
13+[`scripts/build-dist.sh`](scripts/build-dist.sh), so an RC and a real release
14+are byte-for-byte the same pipeline with a different version stamp.
1715
1816 ---
1917
2018 ## How the pipeline reaches the release API
2119
22-Two things about rickub's CI shape this repository's workflows; both are
23-enforced in the files, and both bite as a failed step if forgotten.
24-
25-**`uses:` actions are pinned to commit SHAs, not tags.** The runner fetches
26-every action from github.com. A full-SHA ref is served from the fleet's
27-SHA-keyed action cache with no GitHub round-trip; a tag such as `@v5` first
28-needs an anonymous `ls-remote` against github.com, which GitHub rate-limits per
29-datacenter IP — the run then dies in "Set up Go" with
30-`could not read Username for 'https://github.com'`. The three actions used here
31-(`actions/checkout`, `actions/setup-go`, `actions/upload-artifact`) are pinned
32-to the same SHAs as rickub's own `.rickub/workflows/ci.yml`, so they are always
33-warm in that cache. When bumping one, pin the new SHA and add the `# vN`
34-comment.
35-
36-**Releases are published by [`scripts/publish-release.sh`](scripts/publish-release.sh),
37-not by a marketplace action.** It POSTs the release, then each asset, to the
38-GitHub-compatible REST shim the job is handed as `$GITHUB_API_URL`
39-(`…/gh`), authenticated with the per-job `GITHUB_TOKEN`. Two `curl` calls do
40-not justify fetching `softprops/action-gh-release` (which would be a cold,
41-rate-limited GitHub fetch), and the shim's exact contract — `target_commitish`
42-cuts the tag, `422` on a duplicate release or asset name, `upload_url`
43-template — is handled in one readable place. Both `rc.yml` and `release.yml`
44-call it; a real release and a candidate differ only in `--prerelease`.
45-
46-**Server-side requirement (rickub, not this repo).** The CI guest reaches the
47-control plane only through the node's results proxy (`web/ciproxy` in the
48-rickub server), and `$GITHUB_API_URL` points at that proxy. The proxy must
49-forward the `/gh/` prefix — shipped in rickub PR
50-[ogirardot/rickub#3](https://github.com/ogirardot/rickub/pull/3) and active on a
20+The workflows are written as ordinary GitHub Actions jobs — `actions/checkout@v4`,
21+`actions/setup-go@v5`, `actions/upload-artifact@v4`,
22+`softprops/action-gh-release@v2` — and rickub's CI is expected to run them as
23+such. Two platform facts matter when a run fails before or at publishing:
24+
25+**Fetching `uses:` actions.** rickub's runner fetches every action from
26+github.com. A `@vN` tag is resolved with a `git ls-remote` against github.com;
27+when that call is anonymous, GitHub rate-limits it per datacenter IP and the run
28+dies in "Set up Go" with `could not read Username for 'https://github.com'`.
29+That is a fleet configuration matter, not a workflow one: the runner
30+authenticates those fetches when `RICKUB_ACTIONS_GITHUB_TOKEN` is set
31+(`web/actions/runtime/stage.go` in the rickub server). Do not work around it by
32+pinning SHAs here — the point of this repository's workflows is to be a normal
33+job.
34+
35+**Publishing goes through the node's CI proxy.** The CI guest reaches the
36+control plane only through the results proxy (`web/ciproxy` in the rickub
37+server), and `$GITHUB_API_URL` — what `softprops/action-gh-release` talks to —
38+points at that proxy with the `/gh` REST shim behind it. The proxy must forward
39+the `/gh/` prefix: shipped in rickub PR
40+[ogirardot/rickub#3](https://github.com/ogirardot/rickub/pull/3), active on a
5141 node once its `rickub-fleetd` has been rebuilt from that change. On a node
5242 running an older fleetd, both workflows build, test and upload their
53-`actions/upload-artifact` bundle, then fail on the "Publish …" step with
54-`HTTP 403 creating the release`; the archives are still downloadable from the
55-run's artifacts, so a release can be published by hand in the meantime.
43+`actions/upload-artifact` bundle, then fail on the "Publish …" step with a
44+`403`; the archives are still downloadable from the run's artifacts, so a
45+release can be published by hand in the meantime.
5646
5747 You can rehearse the whole pipeline without publishing by dispatching
5848 `release.yml` with **`dry_run: true`** — it builds, tests, verifies the tag is
@@ -87,13 +77,12 @@ Every push to `main` runs `rc.yml`:
8777
8878 1. `go test ./...`
8979 2. `scripts/build-dist.sh <BASE>-rc.<run_number>`
90-3. `scripts/publish-release.sh --prerelease …` publishes a **prerelease** at
91- tag `v<BASE>-rc.<run_number>`, with the four `.tar.gz` archives and
92- `SHA256SUMS` attached.
80+3. publishes a **prerelease** at tag `v<BASE>-rc.<run_number>`, with the four
81+ `.tar.gz` archives and `SHA256SUMS` attached.
9382
9483 The tag does not exist beforehand — the release shim creates it at
95-`target_commitish`, which the script is given as `${{ github.sha }}`, so CI
96-cuts its own RC tags.
84+`target_commitish`, which the workflow sets to `${{ github.sha }}`, so CI cuts
85+its own RC tags.
9786
9887 `concurrency: { group: rc, cancel-in-progress: true }` means only the newest
9988 push to `main` is building at any moment; superseded RC runs are cancelled, so
@@ -124,9 +113,9 @@ The workflow then:
124113 non-404 answer is only a warning — the git tag check is the real gate);
125114 * runs `go vet` and `go test`;
126115 * builds the four archives + `SHA256SUMS` via `scripts/build-dist.sh`;
127-* runs `scripts/publish-release.sh` to publish a non-prerelease release at
128- `v<version>`, targeting `${{ github.sha }}` — the exact commit of the ref you
129- dispatched — so the tag is created at that commit.
116+* publishes a non-prerelease release at `v<version>`, targeting
117+ `${{ github.sha }}` — the exact commit of the ref you dispatched — so the tag
118+ is created at that commit.
130119
131120 There is no approval gate: rickub CI silently drops `environment:`, so the
132121 permission to run this workflow *is* the permission to release.
@@ -209,11 +198,12 @@ leaves nothing untracked behind — `dist/` is already in `.gitignore`.
209198
210199 ## Troubleshooting
211200
212-**`HTTP 403 creating the release`.** The node's CI proxy does not forward
213-`/gh/` yet — see "Server-side requirement" above.
201+**`403` publishing the release.** The node's CI proxy does not forward `/gh/`
202+yet — see "Publishing goes through the node's CI proxy" above.
214203
215-**`could not read Username for 'https://github.com'` in "Set up Go".** A
216-`uses:` reference is a tag, not a pinned SHA — see the pinning note above.
204+**`could not read Username for 'https://github.com'` in "Set up Go".** The
205+fleet is fetching actions from GitHub anonymously and got rate-limited — see
206+"Fetching `uses:` actions" above; the fix is on the runner, not here.
217207
218208 **`Resource not accessible by integration` / `404` on release creation.** The
219209 `GITHUB_TOKEN` is read-only by default; the workflow must declare
@@ -224,10 +214,9 @@ or an asset with that name, already exists — the shim rejects duplicates. This
224214 normally means a partially completed run is being retried; delete the release
225215 (or the asset) and re-run.
226216
227-**Editing a release via `PATCH`.** The workflows never edit a release, but if
228-you script one: the shim reads `draft` and `prerelease` unconditionally, so
229-**always send both fields** in a PATCH body or you will silently flip a
230-release to draft.
217+**Editing a release via `PATCH`.** The shim reads `draft` and `prerelease`
218+unconditionally, so **always send both fields** in a PATCH body or you will
219+silently flip a release to draft.
231220
232221 **Nothing to release / `make_latest` ignored.** The shim does not implement
233222 `make_latest` or `generate_release_notes`, and there is no
@@ -9,50 +9,40 @@ GitHub-Actions-compatible CI:
9 | [`.github/workflows/rc.yml`](.github/workflows/rc.yml) | every push to `main` | tests, cross-compiles all four targets, publishes a **prerelease** `v<BASE>-rc.<run_number>` |9 | [`.github/workflows/rc.yml`](.github/workflows/rc.yml) | every push to `main` | tests, cross-compiles all four targets, publishes a **prerelease** `v<BASE>-rc.<run_number>` |
10 | [`.github/workflows/release.yml`](.github/workflows/release.yml) | **manual only** (`workflow_dispatch`) | tests, cross-compiles, publishes a real release `v<version>` |10 | [`.github/workflows/release.yml`](.github/workflows/release.yml) | **manual only** (`workflow_dispatch`) | tests, cross-compiles, publishes a real release `v<version>` |
11 11
12-Both release-producing workflows call the same two scripts,12+Both release-producing workflows call the same script,
13-[`scripts/build-dist.sh`](scripts/build-dist.sh) to build and13+[`scripts/build-dist.sh`](scripts/build-dist.sh), so an RC and a real release
14-[`scripts/publish-release.sh`](scripts/publish-release.sh) to publish, so an RC14+are byte-for-byte the same pipeline with a different version stamp.
15-and a real release are byte-for-byte the same pipeline with a different version
16-stamp and the `--prerelease` flag.
17 15
18 ---16 ---
19 17
20 ## How the pipeline reaches the release API18 ## How the pipeline reaches the release API
21 19
22-Two things about rickub's CI shape this repository's workflows; both are20+The workflows are written as ordinary GitHub Actions jobs — `actions/checkout@v4`,
23-enforced in the files, and both bite as a failed step if forgotten.21+`actions/setup-go@v5`, `actions/upload-artifact@v4`,
24-22+`softprops/action-gh-release@v2` — and rickub's CI is expected to run them as
25-**`uses:` actions are pinned to commit SHAs, not tags.** The runner fetches23+such. Two platform facts matter when a run fails before or at publishing:
26-every action from github.com. A full-SHA ref is served from the fleet's24+
27-SHA-keyed action cache with no GitHub round-trip; a tag such as `@v5` first25+**Fetching `uses:` actions.** rickub's runner fetches every action from
28-needs an anonymous `ls-remote` against github.com, which GitHub rate-limits per26+github.com. A `@vN` tag is resolved with a `git ls-remote` against github.com;
29-datacenter IP — the run then dies in "Set up Go" with27+when that call is anonymous, GitHub rate-limits it per datacenter IP and the run
30-`could not read Username for 'https://github.com'`. The three actions used here28+dies in "Set up Go" with `could not read Username for 'https://github.com'`.
31-(`actions/checkout`, `actions/setup-go`, `actions/upload-artifact`) are pinned29+That is a fleet configuration matter, not a workflow one: the runner
32-to the same SHAs as rickub's own `.rickub/workflows/ci.yml`, so they are always30+authenticates those fetches when `RICKUB_ACTIONS_GITHUB_TOKEN` is set
33-warm in that cache. When bumping one, pin the new SHA and add the `# vN`31+(`web/actions/runtime/stage.go` in the rickub server). Do not work around it by
34-comment.32+pinning SHAs here — the point of this repository's workflows is to be a normal
35-33+job.
36-**Releases are published by [`scripts/publish-release.sh`](scripts/publish-release.sh),34+
37-not by a marketplace action.** It POSTs the release, then each asset, to the35+**Publishing goes through the node's CI proxy.** The CI guest reaches the
38-GitHub-compatible REST shim the job is handed as `$GITHUB_API_URL`36+control plane only through the results proxy (`web/ciproxy` in the rickub
39-(`…/gh`), authenticated with the per-job `GITHUB_TOKEN`. Two `curl` calls do37+server), and `$GITHUB_API_URL` — what `softprops/action-gh-release` talks to —
40-not justify fetching `softprops/action-gh-release` (which would be a cold,38+points at that proxy with the `/gh` REST shim behind it. The proxy must forward
41-rate-limited GitHub fetch), and the shim's exact contract — `target_commitish`39+the `/gh/` prefix: shipped in rickub PR
42-cuts the tag, `422` on a duplicate release or asset name, `upload_url`40+[ogirardot/rickub#3](https://github.com/ogirardot/rickub/pull/3), active on a
43-template — is handled in one readable place. Both `rc.yml` and `release.yml`
44-call it; a real release and a candidate differ only in `--prerelease`.
45-
46-**Server-side requirement (rickub, not this repo).** The CI guest reaches the
47-control plane only through the node's results proxy (`web/ciproxy` in the
48-rickub server), and `$GITHUB_API_URL` points at that proxy. The proxy must
49-forward the `/gh/` prefix — shipped in rickub PR
50-[ogirardot/rickub#3](https://github.com/ogirardot/rickub/pull/3) and active on a
51 node once its `rickub-fleetd` has been rebuilt from that change. On a node41 node once its `rickub-fleetd` has been rebuilt from that change. On a node
52 running an older fleetd, both workflows build, test and upload their42 running an older fleetd, both workflows build, test and upload their
53-`actions/upload-artifact` bundle, then fail on the "Publish …" step with43+`actions/upload-artifact` bundle, then fail on the "Publish …" step with a
54-`HTTP 403 creating the release`; the archives are still downloadable from the44+`403`; the archives are still downloadable from the run's artifacts, so a
55-run's artifacts, so a release can be published by hand in the meantime.45+release can be published by hand in the meantime.
56 46
57 You can rehearse the whole pipeline without publishing by dispatching47 You can rehearse the whole pipeline without publishing by dispatching
58 `release.yml` with **`dry_run: true`** — it builds, tests, verifies the tag is48 `release.yml` with **`dry_run: true`** — it builds, tests, verifies the tag is
@@ -87,13 +77,12 @@ Every push to `main` runs `rc.yml`:
87 77
88 1. `go test ./...`78 1. `go test ./...`
89 2. `scripts/build-dist.sh <BASE>-rc.<run_number>`79 2. `scripts/build-dist.sh <BASE>-rc.<run_number>`
90-3. `scripts/publish-release.sh --prerelease …` publishes a **prerelease** at80+3. publishes a **prerelease** at tag `v<BASE>-rc.<run_number>`, with the four
91- tag `v<BASE>-rc.<run_number>`, with the four `.tar.gz` archives and81+ `.tar.gz` archives and `SHA256SUMS` attached.
92- `SHA256SUMS` attached.
93 82
94 The tag does not exist beforehand — the release shim creates it at83 The tag does not exist beforehand — the release shim creates it at
95-`target_commitish`, which the script is given as `${{ github.sha }}`, so CI84+`target_commitish`, which the workflow sets to `${{ github.sha }}`, so CI cuts
96-cuts its own RC tags.85+its own RC tags.
97 86
98 `concurrency: { group: rc, cancel-in-progress: true }` means only the newest87 `concurrency: { group: rc, cancel-in-progress: true }` means only the newest
99 push to `main` is building at any moment; superseded RC runs are cancelled, so88 push to `main` is building at any moment; superseded RC runs are cancelled, so
@@ -124,9 +113,9 @@ The workflow then:
124 non-404 answer is only a warning — the git tag check is the real gate);113 non-404 answer is only a warning — the git tag check is the real gate);
125 * runs `go vet` and `go test`;114 * runs `go vet` and `go test`;
126 * builds the four archives + `SHA256SUMS` via `scripts/build-dist.sh`;115 * builds the four archives + `SHA256SUMS` via `scripts/build-dist.sh`;
127-* runs `scripts/publish-release.sh` to publish a non-prerelease release at116+* publishes a non-prerelease release at `v<version>`, targeting
128- `v<version>`, targeting `${{ github.sha }}` — the exact commit of the ref you117+ `${{ github.sha }}` — the exact commit of the ref you dispatched — so the tag
129- dispatched — so the tag is created at that commit.118+ is created at that commit.
130 119
131 There is no approval gate: rickub CI silently drops `environment:`, so the120 There is no approval gate: rickub CI silently drops `environment:`, so the
132 permission to run this workflow *is* the permission to release.121 permission to run this workflow *is* the permission to release.
@@ -209,11 +198,12 @@ leaves nothing untracked behind — `dist/` is already in `.gitignore`.
209 198
210 ## Troubleshooting199 ## Troubleshooting
211 200
212-**`HTTP 403 creating the release`.** The node's CI proxy does not forward201+**`403` publishing the release.** The node's CI proxy does not forward `/gh/`
213-`/gh/` yet — see "Server-side requirement" above.202+yet — see "Publishing goes through the node's CI proxy" above.
214 203
215-**`could not read Username for 'https://github.com'` in "Set up Go".** A204+**`could not read Username for 'https://github.com'` in "Set up Go".** The
216-`uses:` reference is a tag, not a pinned SHA — see the pinning note above.205+fleet is fetching actions from GitHub anonymously and got rate-limited — see
206+"Fetching `uses:` actions" above; the fix is on the runner, not here.
217 207
218 **`Resource not accessible by integration` / `404` on release creation.** The208 **`Resource not accessible by integration` / `404` on release creation.** The
219 `GITHUB_TOKEN` is read-only by default; the workflow must declare209 `GITHUB_TOKEN` is read-only by default; the workflow must declare
@@ -224,10 +214,9 @@ or an asset with that name, already exists — the shim rejects duplicates. This
224 normally means a partially completed run is being retried; delete the release214 normally means a partially completed run is being retried; delete the release
225 (or the asset) and re-run.215 (or the asset) and re-run.
226 216
227-**Editing a release via `PATCH`.** The workflows never edit a release, but if217+**Editing a release via `PATCH`.** The shim reads `draft` and `prerelease`
228-you script one: the shim reads `draft` and `prerelease` unconditionally, so218+unconditionally, so **always send both fields** in a PATCH body or you will
229-**always send both fields** in a PATCH body or you will silently flip a219+silently flip a release to draft.
230-release to draft.
231 220
232 **Nothing to release / `make_latest` ignored.** The shim does not implement221 **Nothing to release / `make_latest` ignored.** The shim does not implement
233 `make_latest` or `generate_release_notes`, and there is no222 `make_latest` or `generate_release_notes`, and there is no
deleted scripts/publish-release.sh +0 -110
deleted file mode 100755
@@ -1,110 +0,0 @@
1-#!/usr/bin/env bash
2-#
3-# publish-release.sh — create a release on the rickub host and attach assets,
4-# straight against the GitHub-compatible REST shim the CI job is handed.
5-#
6-# Usage: scripts/publish-release.sh --tag v1.2.3 --target <sha> --notes notes.md \
7-# [--name v1.2.3] [--prerelease] [--draft] file...
8-#
9-# Environment (all injected by rickub CI; set them by hand to run elsewhere):
10-# GITHUB_API_URL shim base, e.g. http://172.30.255.1:8888/gh
11-# GITHUB_TOKEN the per-job token (contents: write)
12-# GITHUB_REPOSITORY owner/repo
13-#
14-# Why a script and not softprops/action-gh-release: rickub's runner fetches every
15-# `uses:` action from github.com, and an action that is not already in the fleet's
16-# SHA-keyed cache costs an anonymous GitHub fetch that github.com rate-limits per
17-# datacenter IP. The two calls the release needs — POST the release, POST each
18-# asset — are trivial with curl, and this way the release pipeline depends on
19-# nothing that has to be fetched at run time. It needs curl and jq, both present
20-# in the CI guest.
21-
22-set -euo pipefail
23-
24-usage() { sed -n '2,20p' "${BASH_SOURCE[0]}" >&2; exit 2; }
25-
26-tag=""; target=""; notes=""; name=""; prerelease=false; draft=false
27-files=()
28-while [ $# -gt 0 ]; do
29- case "$1" in
30- --tag) tag="$2"; shift 2 ;;
31- --target) target="$2"; shift 2 ;;
32- --notes) notes="$2"; shift 2 ;;
33- --name) name="$2"; shift 2 ;;
34- --prerelease) prerelease=true; shift ;;
35- --draft) draft=true; shift ;;
36- -h|--help) usage ;;
37- --) shift; files+=("$@"); break ;;
38- -*) echo "publish-release.sh: unknown flag $1" >&2; usage ;;
39- *) files+=("$1"); shift ;;
40- esac
41-done
42-
43-[ -n "${tag}" ] || { echo "publish-release.sh: --tag is required" >&2; exit 2; }
44-[ -n "${notes}" ] && [ -f "${notes}" ] || { echo "publish-release.sh: --notes must name a file" >&2; exit 2; }
45-[ "${#files[@]}" -gt 0 ] || { echo "publish-release.sh: no asset files given" >&2; exit 2; }
46-for f in "${files[@]}"; do
47- [ -f "${f}" ] || { echo "publish-release.sh: asset not found: ${f}" >&2; exit 2; }
48-done
49-for v in GITHUB_API_URL GITHUB_TOKEN GITHUB_REPOSITORY; do
50- [ -n "${!v:-}" ] || { echo "publish-release.sh: ${v} is not set" >&2; exit 2; }
51-done
52-command -v curl >/dev/null || { echo "publish-release.sh: curl not found" >&2; exit 2; }
53-command -v jq >/dev/null || { echo "publish-release.sh: jq not found" >&2; exit 2; }
54-
55-api="${GITHUB_API_URL%/}"
56-name="${name:-${tag}}"
57-
58-# Never echo the token: -H is passed through a config file, not the command line
59-# (the guest's own process list is untrusted-code territory).
60-auth_cfg="$(mktemp)"
61-trap 'rm -f "${auth_cfg}"' EXIT
62-printf 'header = "Authorization: Bearer %s"\n' "${GITHUB_TOKEN}" > "${auth_cfg}"
63-
64-body="$(mktemp)"
65-trap 'rm -f "${auth_cfg}" "${body}"' EXIT
66-
67-# request METHOD URL [curl args...]: the response body lands in ${body}, the
68-# HTTP status is printed (000 when curl itself failed).
69-request() {
70- local method="$1" url="$2"; shift 2
71- curl -sS -o "${body}" -w '%{http_code}' -K "${auth_cfg}" \
72- -H 'Accept: application/vnd.github+json' -X "${method}" "$@" "${url}" || true
73-}
74-message() { jq -r '.message // .' "${body}" 2>/dev/null || cat "${body}"; }
75-
76-echo "==> creating release ${tag} on ${GITHUB_REPOSITORY} (prerelease=${prerelease}, draft=${draft})"
77-payload="$(jq -n \
78- --arg tag "${tag}" --arg target "${target}" --arg name "${name}" \
79- --rawfile body "${notes}" --argjson prerelease "${prerelease}" --argjson draft "${draft}" \
80- '{tag_name: $tag, name: $name, body: $body, draft: $draft, prerelease: $prerelease}
81- + (if $target == "" then {} else {target_commitish: $target} end)')"
82-status="$(request POST "${api}/repos/${GITHUB_REPOSITORY}/releases" \
83- -H 'Content-Type: application/json' --data-binary "${payload}")"
84-case "${status}" in
85- 200|201) ;;
86- 422) echo "::error::release ${tag} was refused (HTTP 422): $(message)"; exit 1 ;;
87- 403) echo "::error::HTTP 403 creating the release — the node's CI proxy does not forward ${api}; see RELEASING.md"; exit 1 ;;
88- *) echo "::error::creating release ${tag} failed (HTTP ${status}): $(message)"; exit 1 ;;
89-esac
90-release_id="$(jq -r '.id // empty' "${body}")"
91-upload_url="$(jq -r '.upload_url // empty' "${body}" | sed 's/{[^}]*}$//')"
92-html_url="$(jq -r '.html_url // empty' "${body}")"
93-[ -n "${release_id}" ] || { echo "::error::release response carried no id: $(cat "${body}")"; exit 1; }
94-[ -n "${upload_url}" ] || { echo "::error::release response carried no upload_url: $(cat "${body}")"; exit 1; }
95-echo " release id ${release_id}${html_url:+ — ${html_url}}"
96-
97-for f in "${files[@]}"; do
98- asset="$(basename "${f}")"
99- echo "==> uploading ${asset} ($(wc -c < "${f}" | tr -d ' ') bytes)"
100- encoded="$(jq -rn --arg n "${asset}" '$n|@uri')"
101- status="$(request POST "${upload_url}?name=${encoded}" \
102- -H 'Content-Type: application/octet-stream' --data-binary "@${f}")"
103- case "${status}" in
104- 200|201) ;;
105- 422) echo "::error::asset ${asset} was refused (HTTP 422): $(message) — a partial earlier run? delete the release and retry"; exit 1 ;;
106- *) echo "::error::uploading ${asset} failed (HTTP ${status}): $(message)"; exit 1 ;;
107- esac
108-done
109-
110-echo "==> published ${tag} with ${#files[@]} asset(s)"
deleted file mode 100755
@@ -1,110 +0,0 @@
1-#!/usr/bin/env bash
2-#
3-# publish-release.sh — create a release on the rickub host and attach assets,
4-# straight against the GitHub-compatible REST shim the CI job is handed.
5-#
6-# Usage: scripts/publish-release.sh --tag v1.2.3 --target <sha> --notes notes.md \
7-# [--name v1.2.3] [--prerelease] [--draft] file...
8-#
9-# Environment (all injected by rickub CI; set them by hand to run elsewhere):
10-# GITHUB_API_URL shim base, e.g. http://172.30.255.1:8888/gh
11-# GITHUB_TOKEN the per-job token (contents: write)
12-# GITHUB_REPOSITORY owner/repo
13-#
14-# Why a script and not softprops/action-gh-release: rickub's runner fetches every
15-# `uses:` action from github.com, and an action that is not already in the fleet's
16-# SHA-keyed cache costs an anonymous GitHub fetch that github.com rate-limits per
17-# datacenter IP. The two calls the release needs — POST the release, POST each
18-# asset — are trivial with curl, and this way the release pipeline depends on
19-# nothing that has to be fetched at run time. It needs curl and jq, both present
20-# in the CI guest.
21-
22-set -euo pipefail
23-
24-usage() { sed -n '2,20p' "${BASH_SOURCE[0]}" >&2; exit 2; }
25-
26-tag=""; target=""; notes=""; name=""; prerelease=false; draft=false
27-files=()
28-while [ $# -gt 0 ]; do
29- case "$1" in
30- --tag) tag="$2"; shift 2 ;;
31- --target) target="$2"; shift 2 ;;
32- --notes) notes="$2"; shift 2 ;;
33- --name) name="$2"; shift 2 ;;
34- --prerelease) prerelease=true; shift ;;
35- --draft) draft=true; shift ;;
36- -h|--help) usage ;;
37- --) shift; files+=("$@"); break ;;
38- -*) echo "publish-release.sh: unknown flag $1" >&2; usage ;;
39- *) files+=("$1"); shift ;;
40- esac
41-done
42-
43-[ -n "${tag}" ] || { echo "publish-release.sh: --tag is required" >&2; exit 2; }
44-[ -n "${notes}" ] && [ -f "${notes}" ] || { echo "publish-release.sh: --notes must name a file" >&2; exit 2; }
45-[ "${#files[@]}" -gt 0 ] || { echo "publish-release.sh: no asset files given" >&2; exit 2; }
46-for f in "${files[@]}"; do
47- [ -f "${f}" ] || { echo "publish-release.sh: asset not found: ${f}" >&2; exit 2; }
48-done
49-for v in GITHUB_API_URL GITHUB_TOKEN GITHUB_REPOSITORY; do
50- [ -n "${!v:-}" ] || { echo "publish-release.sh: ${v} is not set" >&2; exit 2; }
51-done
52-command -v curl >/dev/null || { echo "publish-release.sh: curl not found" >&2; exit 2; }
53-command -v jq >/dev/null || { echo "publish-release.sh: jq not found" >&2; exit 2; }
54-
55-api="${GITHUB_API_URL%/}"
56-name="${name:-${tag}}"
57-
58-# Never echo the token: -H is passed through a config file, not the command line
59-# (the guest's own process list is untrusted-code territory).
60-auth_cfg="$(mktemp)"
61-trap 'rm -f "${auth_cfg}"' EXIT
62-printf 'header = "Authorization: Bearer %s"\n' "${GITHUB_TOKEN}" > "${auth_cfg}"
63-
64-body="$(mktemp)"
65-trap 'rm -f "${auth_cfg}" "${body}"' EXIT
66-
67-# request METHOD URL [curl args...]: the response body lands in ${body}, the
68-# HTTP status is printed (000 when curl itself failed).
69-request() {
70- local method="$1" url="$2"; shift 2
71- curl -sS -o "${body}" -w '%{http_code}' -K "${auth_cfg}" \
72- -H 'Accept: application/vnd.github+json' -X "${method}" "$@" "${url}" || true
73-}
74-message() { jq -r '.message // .' "${body}" 2>/dev/null || cat "${body}"; }
75-
76-echo "==> creating release ${tag} on ${GITHUB_REPOSITORY} (prerelease=${prerelease}, draft=${draft})"
77-payload="$(jq -n \
78- --arg tag "${tag}" --arg target "${target}" --arg name "${name}" \
79- --rawfile body "${notes}" --argjson prerelease "${prerelease}" --argjson draft "${draft}" \
80- '{tag_name: $tag, name: $name, body: $body, draft: $draft, prerelease: $prerelease}
81- + (if $target == "" then {} else {target_commitish: $target} end)')"
82-status="$(request POST "${api}/repos/${GITHUB_REPOSITORY}/releases" \
83- -H 'Content-Type: application/json' --data-binary "${payload}")"
84-case "${status}" in
85- 200|201) ;;
86- 422) echo "::error::release ${tag} was refused (HTTP 422): $(message)"; exit 1 ;;
87- 403) echo "::error::HTTP 403 creating the release — the node's CI proxy does not forward ${api}; see RELEASING.md"; exit 1 ;;
88- *) echo "::error::creating release ${tag} failed (HTTP ${status}): $(message)"; exit 1 ;;
89-esac
90-release_id="$(jq -r '.id // empty' "${body}")"
91-upload_url="$(jq -r '.upload_url // empty' "${body}" | sed 's/{[^}]*}$//')"
92-html_url="$(jq -r '.html_url // empty' "${body}")"
93-[ -n "${release_id}" ] || { echo "::error::release response carried no id: $(cat "${body}")"; exit 1; }
94-[ -n "${upload_url}" ] || { echo "::error::release response carried no upload_url: $(cat "${body}")"; exit 1; }
95-echo " release id ${release_id}${html_url:+ — ${html_url}}"
96-
97-for f in "${files[@]}"; do
98- asset="$(basename "${f}")"
99- echo "==> uploading ${asset} ($(wc -c < "${f}" | tr -d ' ') bytes)"
100- encoded="$(jq -rn --arg n "${asset}" '$n|@uri')"
101- status="$(request POST "${upload_url}?name=${encoded}" \
102- -H 'Content-Type: application/octet-stream' --data-binary "@${f}")"
103- case "${status}" in
104- 200|201) ;;
105- 422) echo "::error::asset ${asset} was refused (HTTP 422): $(message) — a partial earlier run? delete the release and retry"; exit 1 ;;
106- *) echo "::error::uploading ${asset} failed (HTTP ${status}): $(message)"; exit 1 ;;
107- esac
108-done
109-
110-echo "==> published ${tag} with ${#files[@]} asset(s)"