Publish from CI without a secret
Woodpecker hands every step a forge credential for cloning — an access token of the repository's owner — and a one-off diagnostic in the check step confirmed it is there. scripts/ci-upload.sh now uses it when no `gitea_token` secret is set, so publishing a release needs nothing configured. Gitea takes such a credential as `token …` or `Bearer …` depending on how Woodpecker was set up, so the script probes which of the two `/user` accepts rather than assuming, and says which one it used. The secret mapping is gone from the step as well: referencing a secret that does not exist is a failure mode of its own, and the fallback is the normal path now. Adding a `gitea_token` secret and mapping it back in is how you publish as somebody else. Documents the release flow the pipeline now implements: push a vX.Y.Z tag, the pipeline builds Linux and Windows and creates the release with them in it, and `make release` from a Mac pushes the macOS package onto the same release. Either half can go first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+3
-6
@@ -23,9 +23,6 @@ steps:
|
|||||||
image: *node_image
|
image: *node_image
|
||||||
commands:
|
commands:
|
||||||
- node --version
|
- node --version
|
||||||
# One-off diagnostic: does Woodpecker hand steps a forge credential of their own?
|
|
||||||
# If it does, the release step needs no secret at all.
|
|
||||||
- 'echo "netrc user: ${CI_NETRC_USERNAME:+present}, password: ${CI_NETRC_PASSWORD:+present}"'
|
|
||||||
- npm ci
|
- npm ci
|
||||||
- npm run typecheck
|
- npm run typecheck
|
||||||
- npm run lint
|
- npm run lint
|
||||||
@@ -61,9 +58,9 @@ steps:
|
|||||||
# from the Mac that can sign them.
|
# from the Mac that can sign them.
|
||||||
- name: release
|
- name: release
|
||||||
image: alpine
|
image: alpine
|
||||||
environment:
|
# No secret: the step authenticates with the forge credential Woodpecker already
|
||||||
GITEA_TOKEN:
|
# hands every step, which belongs to the repository's owner. To publish as someone
|
||||||
from_secret: gitea_token
|
# else instead, add a `gitea_token` repository secret and map it here as GITEA_TOKEN.
|
||||||
commands:
|
commands:
|
||||||
- apk add --no-cache curl jq
|
- apk add --no-cache curl jq
|
||||||
# No globs on the command line: the package names have spaces in them.
|
# No globs on the command line: the package names have spaces in them.
|
||||||
|
|||||||
@@ -192,17 +192,28 @@ builds an application and publishes to a release.
|
|||||||
| `check` | `electronuserland/builder:22` | `npm ci`, type-check, lint, and the smoke test |
|
| `check` | `electronuserland/builder:22` | `npm ci`, type-check, lint, and the smoke test |
|
||||||
| `linux` | `electronuserland/builder:22` | AppImage and deb |
|
| `linux` | `electronuserland/builder:22` | AppImage and deb |
|
||||||
| `windows` | `electronuserland/builder:22-wine` | the NSIS installer and the portable exe, built through Wine |
|
| `windows` | `electronuserland/builder:22-wine` | the NSIS installer and the portable exe, built through Wine |
|
||||||
| `release` | `alpine` | on a tag only: attaches what this pipeline built |
|
| `release` | `alpine` | on a tag only: **creates the release** and attaches what this pipeline built |
|
||||||
|
|
||||||
**macOS stays a local build.** Apple's toolchain and its signing only exist on a Mac,
|
**macOS stays a local build.** Apple's toolchain and its signing only exist on a Mac.
|
||||||
so a full release is `make release` here for the macOS package plus this pipeline for
|
So the whole of a release is:
|
||||||
the other two. The window test is local too: it needs a display and a store on the
|
|
||||||
machine.
|
|
||||||
|
|
||||||
The `release` step needs a **`gitea_token`** repository secret in Woodpecker, with
|
1. bump the version, commit, and push the tag: `git tag v1.4.0 && git push origin v1.4.0`;
|
||||||
write access to this repository — CI has a token where a workstation has a `tea`
|
2. the pipeline builds Linux and Windows, **creates the release** with `RELEASE_NOTES.md`
|
||||||
login, which is why `scripts/ci-upload.sh` exists alongside `scripts/release.sh`
|
as its body, and attaches those four packages;
|
||||||
instead of one script with two ways to authenticate.
|
3. on a Mac, `make release` builds the macOS package and pushes it onto the same release.
|
||||||
|
|
||||||
|
The window test is local as well: it needs a display and a store on the machine.
|
||||||
|
|
||||||
|
The `release` step needs **no secret**. It authenticates with the forge credential
|
||||||
|
Woodpecker hands every step for cloning, which is an access token of the repository's
|
||||||
|
owner; Gitea takes it as `token …` or `Bearer …` depending on how Woodpecker was set up,
|
||||||
|
so `scripts/ci-upload.sh` probes which of the two works instead of assuming. To publish
|
||||||
|
as somebody else, add a `gitea_token` repository secret and map it into the step.
|
||||||
|
|
||||||
|
There are two publishers on purpose: `scripts/release.sh` drives `tea`, which is logged
|
||||||
|
in on a workstation, and `scripts/ci-upload.sh` speaks the API with whatever credential
|
||||||
|
CI has. Each is short enough to read in full; one script with two ways to authenticate
|
||||||
|
would not be.
|
||||||
|
|
||||||
Both build steps end by checking what they produced: a package under 10 MB did not
|
Both build steps end by checking what they produced: a package under 10 MB did not
|
||||||
finish. That check exists because a half-finished Wine build leaves a stub *named* like
|
finish. That check exists because a half-finished Wine build leaves a stub *named* like
|
||||||
@@ -224,10 +235,12 @@ convention.
|
|||||||
make release
|
make release
|
||||||
```
|
```
|
||||||
|
|
||||||
The tag comes from `package.json`, so `npm version patch` is the only place a
|
This is the **macOS half** of a release; the Linux and Windows packages come from the
|
||||||
version is set. The release is created if it is not there yet, and an attachment
|
pipeline when the tag is pushed (see above). The tag comes from `package.json`, so
|
||||||
whose name is already on it is **replaced** rather than refused — so a rebuild and
|
`npm version patch` is the only place a version is set. The release is created if it is
|
||||||
a second `make publish` lands rather than erroring.
|
not there yet — either half can go first — and an attachment whose name is already on it
|
||||||
|
is **replaced** rather than refused, so a rebuild and a second `make publish` lands
|
||||||
|
rather than erroring.
|
||||||
|
|
||||||
Release notes come from `RELEASE_NOTES.md` when the file is present, otherwise the
|
Release notes come from `RELEASE_NOTES.md` when the file is present, otherwise the
|
||||||
release gets a one-line note. The repository is read from `origin`, so a fork
|
release gets a one-line note. The repository is read from `origin`, so a fork
|
||||||
|
|||||||
+9
-5
@@ -58,11 +58,15 @@ window failed it.
|
|||||||
|
|
||||||
### Linux and Windows packages now come from CI
|
### Linux and Windows packages now come from CI
|
||||||
|
|
||||||
`.woodpecker.yaml` builds the AppImage, the deb, the NSIS installer and the portable
|
A `vX.Y.Z` tag now starts the pipeline, which builds the AppImage, the deb, the NSIS
|
||||||
exe — Windows through Wine — and on a tag attaches them to this release. macOS stays a
|
installer and the portable exe — Windows through Wine — **creates this release** and
|
||||||
local build, because Apple's toolchain and its signing exist only on a Mac, so a full
|
attaches all four. macOS stays a local build, because Apple's toolchain and its signing
|
||||||
release is one local `make release` plus the pipeline. The Windows installer is not
|
exist only on a Mac, so `make release` from a Mac pushes that package onto the same
|
||||||
signed: Windows will warn about an unknown publisher until there is a certificate.
|
release afterwards. No secret is involved: the pipeline publishes with the forge
|
||||||
|
credential Woodpecker already gives every step.
|
||||||
|
|
||||||
|
The Windows installer is not signed: Windows will warn about an unknown publisher until
|
||||||
|
there is a certificate.
|
||||||
|
|
||||||
### Opening it on macOS
|
### Opening it on macOS
|
||||||
|
|
||||||
|
|||||||
+25
-6
@@ -6,8 +6,11 @@
|
|||||||
# two are deliberately separate scripts rather than one with two ways to authenticate —
|
# two are deliberately separate scripts rather than one with two ways to authenticate —
|
||||||
# each is short enough to read in full.
|
# each is short enough to read in full.
|
||||||
#
|
#
|
||||||
# GITEA_TOKEN=… scripts/ci-upload.sh every package in dist/
|
# scripts/ci-upload.sh every package in dist/
|
||||||
# GITEA_TOKEN=… scripts/ci-upload.sh dist/one.deb just these
|
# scripts/ci-upload.sh dist/one.deb just these
|
||||||
|
#
|
||||||
|
# Authenticates with the `gitea_token` secret when there is one, and otherwise with the
|
||||||
|
# credential Woodpecker gives every step for cloning — so a release needs no secret.
|
||||||
#
|
#
|
||||||
# Creates the release when the tag has none, with RELEASE_NOTES.md as its body. That is
|
# Creates the release when the tag has none, with RELEASE_NOTES.md as its body. That is
|
||||||
# the flow: a `vX.Y.Z` tag starts this pipeline, which publishes the release with the
|
# the flow: a `vX.Y.Z` tag starts this pipeline, which publishes the release with the
|
||||||
@@ -24,13 +27,30 @@ NOTES="${NOTES:-RELEASE_NOTES.md}"
|
|||||||
say() { echo "[ci-upload] $*"; }
|
say() { echo "[ci-upload] $*"; }
|
||||||
die() { echo "[ci-upload] error: $*" >&2; exit 1; }
|
die() { echo "[ci-upload] error: $*" >&2; exit 1; }
|
||||||
|
|
||||||
[ -n "${GITEA_TOKEN:-}" ] || die "GITEA_TOKEN is not set — add the gitea_token secret to the repository"
|
# Who to be. A `gitea_token` secret wins when there is one; otherwise the credential
|
||||||
|
# Woodpecker already hands every step for cloning is used, which is an access token of
|
||||||
|
# the repository's owner — so publishing needs no secret of its own. Gitea accepts a
|
||||||
|
# personal access token as `token …` and an OAuth one as `Bearer …`, and which of the two
|
||||||
|
# this is depends on how Woodpecker was set up, so the scheme is probed once rather than
|
||||||
|
# assumed.
|
||||||
|
TOKEN="${GITEA_TOKEN:-${CI_NETRC_PASSWORD:-}}"
|
||||||
|
[ -n "$TOKEN" ] || die "no credential: set GITEA_TOKEN, or run this where Woodpecker provides CI_NETRC_PASSWORD"
|
||||||
[ -n "$REPO" ] || die "cannot work out the repository — set REPO=owner/name"
|
[ -n "$REPO" ] || die "cannot work out the repository — set REPO=owner/name"
|
||||||
[ -n "$TAG" ] || die "cannot work out the tag — set TAG=v1.2.3"
|
[ -n "$TAG" ] || die "cannot work out the tag — set TAG=v1.2.3"
|
||||||
|
|
||||||
|
AUTH=""
|
||||||
|
for scheme in token Bearer; do
|
||||||
|
if curl -fsS -H "Authorization: $scheme $TOKEN" "$FORGE/user" >/dev/null 2>&1; then
|
||||||
|
AUTH="Authorization: $scheme $TOKEN"
|
||||||
|
say "authenticated with the $scheme scheme"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ -n "$AUTH" ] || die "the credential was refused by $FORGE — it cannot read /user"
|
||||||
|
|
||||||
api() {
|
api() {
|
||||||
method="$1"; path="$2"; shift 2
|
method="$1"; path="$2"; shift 2
|
||||||
curl -fsS -X "$method" -H "Authorization: token $GITEA_TOKEN" "$FORGE$path" "$@"
|
curl -fsS -X "$method" -H "$AUTH" "$FORGE$path" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Package names contain spaces — "WarpEngine Store Setup 1.4.0.exe" does — so the list
|
# Package names contain spaces — "WarpEngine Store Setup 1.4.0.exe" does — so the list
|
||||||
@@ -52,8 +72,7 @@ say "$REPO $TAG"
|
|||||||
|
|
||||||
# `curl -f` fails on the 404 a missing release answers, so the lookup is allowed to
|
# `curl -f` fails on the 404 a missing release answers, so the lookup is allowed to
|
||||||
# fail and judged by what came back rather than by its exit status.
|
# fail and judged by what came back rather than by its exit status.
|
||||||
release_id="$(curl -sS -H "Authorization: token $GITEA_TOKEN" \
|
release_id="$(curl -sS -H "$AUTH" "$FORGE/repos/$REPO/releases/tags/$TAG" | jq -r '.id // empty')"
|
||||||
"$FORGE/repos/$REPO/releases/tags/$TAG" | jq -r '.id // empty')"
|
|
||||||
|
|
||||||
if [ -z "$release_id" ]; then
|
if [ -z "$release_id" ]; then
|
||||||
say "no release for $TAG yet — creating it"
|
say "no release for $TAG yet — creating it"
|
||||||
|
|||||||
Reference in New Issue
Block a user