-
Notifications
You must be signed in to change notification settings - Fork 1
172 lines (160 loc) · 6.5 KB
/
Copy pathrelease.yml
File metadata and controls
172 lines (160 loc) · 6.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
name: release
# Publishes the binaries that scripts/install.sh downloads.
#
# Until this existed, `curl -fsSL https://nan.builders/install | bash` could
# never work: the script asks the GitHub API for the latest release, and the
# repo had none, so it had nothing to fetch. Anyone who wanted the CLI had to
# clone the repo and build it with Go.
#
# The artifact names here are a contract with scripts/install.sh, which builds
# the download URL as:
#
# nan-cli_${version}_${os}_${arch}.tar.gz
#
# where ${version} is the tag verbatim, `v` included, because the script reads
# it from the release's `tag_name`. Renaming these breaks the installer
# silently: the release looks fine on GitHub and the one-liner 404s.
#
# The Windows archives are .zip and sit outside that contract: install.sh is
# bash and never asks for them. They are published so that installing on
# Windows stops meaning `go build`.
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Tag to build and publish (e.g. v0.1.1)'
required: true
permissions:
contents: write
# For the attestation below. The runner asks GitHub's OIDC provider for a
# token that says which workflow, in which repository, at which commit is
# running, and that token is what signs the archives - there is no key here
# to keep, rotate or lose.
id-token: write
attestations: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
# Through the environment, and checked before it goes anywhere near a
# shell. A workflow_dispatch input pasted straight into a `run:` block is
# substituted before bash ever sees it, so whatever was typed into the
# box becomes part of the script - and every step below puts this value
# on a command line. Only something shaped like one of our tags gets
# past here.
- name: Resolve the tag
id: tag
env:
INPUT_TAG: ${{ github.event.inputs.tag }}
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
tag="${INPUT_TAG:-$REF_NAME}"
if ! printf '%s' "$tag" | grep -Eq '^v[0-9]+[.][0-9]+[.][0-9]+([-][A-Za-z0-9.]+)?$'; then
echo "not a version tag: $tag" >&2
exit 1
fi
echo "value=$tag" >> "$GITHUB_OUTPUT"
# The version shown by `nan --version` is a constant in the source
# (internal/tui.Version). A tag that disagrees with it ships a binary
# that lies about which build it is, and the first person to report a
# bug reports the wrong version.
- name: Check the tag matches internal/tui.Version
env:
TAG: ${{ steps.tag.outputs.value }}
run: |
declared="$(grep -oP 'const Version = "\K[^"]+' internal/tui/tui.go)"
tag="$TAG"
if [ "v$declared" != "$tag" ]; then
echo "tag $tag does not match internal/tui.Version ($declared)" >&2
echo "bump the constant or retag" >&2
exit 1
fi
- name: Build
env:
VERSION: ${{ steps.tag.outputs.value }}
run: |
set -euo pipefail
mkdir -p dist
for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64; do
os="${target%/*}"
arch="${target#*/}"
binary="nan"
if [ "$os" = "windows" ]; then
binary="nan.exe"
fi
# -trimpath keeps build paths out of the binary; -s -w drops the
# symbol and DWARF tables, which is most of the size.
CGO_ENABLED=0 GOOS="$os" GOARCH="$arch" \
go build -trimpath -ldflags "-s -w" -o "dist/$binary" .
# Windows gets a .zip, not a .tar.gz: nothing on a stock Windows
# unpacks a tarball by double-clicking, and scripts/install.sh is
# bash, so nobody reaches these through the one-liner anyway.
if [ "$os" = "windows" ]; then
(cd dist && zip -q "nan-cli_${VERSION}_${os}_${arch}.zip" "$binary")
else
tar -czf "dist/nan-cli_${VERSION}_${os}_${arch}.tar.gz" -C dist "$binary"
fi
rm "dist/$binary"
done
cd dist && sha256sum *.tar.gz *.zip > checksums.txt
cat checksums.txt
# Installing what we are about to publish, the same way a member would,
# so a broken artifact fails here instead of on their machine.
- name: Smoke test the linux/amd64 archive
env:
TAG: ${{ steps.tag.outputs.value }}
run: |
set -euo pipefail
tar -xzf "dist/nan-cli_${TAG}_linux_amd64.tar.gz" -C /tmp
/tmp/nan --version
/tmp/nan --help > /dev/null
# The Windows binaries cannot be run here, but an archive that unpacks
# to nothing, or to a name Windows will not execute, can still be caught.
- name: Check the Windows archives carry an .exe
env:
TAG: ${{ steps.tag.outputs.value }}
run: |
set -euo pipefail
for arch in amd64 arm64; do
zip="dist/nan-cli_${TAG}_windows_${arch}.zip"
unzip -l "$zip" | grep -q "nan.exe" || {
echo "$zip does not contain nan.exe" >&2
exit 1
}
done
# checksums.txt proves the bytes arrived whole. It proves nothing about
# where they came from: whoever can replace an archive on a release can
# replace the checksum sitting next to it, and the installer would verify
# the download against the attacker's own number and call it good.
#
# This signs the archives themselves. GitHub keeps the attestation, so
# anyone can ask what built a binary they already have:
#
# gh attestation verify nan-cli_v1.2.3_linux_amd64.tar.gz --repo helmcode/nan-cli
#
# and get back the workflow, the repository and the commit - none of
# which a release someone else published can produce.
- name: Attest what was built
uses: actions/attest-build-provenance@v4
with:
subject-path: |
dist/*.tar.gz
dist/*.zip
- name: Publish the release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.tag.outputs.value }}
run: |
gh release create "$TAG" \
--title "$TAG" \
--generate-notes \
dist/*.tar.gz dist/*.zip dist/checksums.txt