-
Notifications
You must be signed in to change notification settings - Fork 0
280 lines (251 loc) · 11.2 KB
/
Copy pathrelease.yml
File metadata and controls
280 lines (251 loc) · 11.2 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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
name: Release artifacts
# Triggered manually or by pushing a `vX.Y.Z` tag.
# Builds the backend & frontend Docker images for both linux/amd64 and
# linux/arm64 (RPi 4/5), exports them as `.tar.gz` files, and uploads
# them as artifacts. The maintainer then attaches the artifacts to a
# GitHub Release manually — we deliberately don't push to a registry
# from CI so a misclick on tag doesn't ship a broken build.
#
# Why tar.gz instead of `docker push`?
# - PiTun installs are typically air-gapped (RPi behind a captive portal,
# factory reset, etc.). Loadable tarballs match that workflow.
# - No registry credentials needed in CI; release stays under the
# maintainer's control end-to-end.
on:
push:
tags: ['v*.*.*']
workflow_dispatch:
inputs:
version:
description: 'Version tag to use in artifact filenames (e.g. v1.2.3)'
required: true
default: 'v0.0.0-dev'
permissions:
# `contents: write` is required for the auto-publish step at the end
# to create / update a GitHub Release and attach the built tarballs
# as Release assets. Without it the run can build everything but
# can't expose them on the Releases page.
contents: write
# `actions: read` is required by actions/download-artifact@v4 (the
# combine job below pulls every artifact uploaded by this run).
actions: read
jobs:
# Frontend dist is architecture-independent — single build, attached
# to the release once. RPi nginx will serve it just fine.
frontend:
name: Frontend dist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: 'frontend/package-lock.json'
# `npm ci` for reproducible release builds — see ci.yml for the
# `--legacy-peer-deps` rationale.
- name: Install dependencies (npm ci, locked)
working-directory: frontend
run: npm ci --legacy-peer-deps --no-audit --no-fund
- run: npm run build
working-directory: frontend
- name: Package dist
env:
VERSION: ${{ github.ref_name || inputs.version }}
run: |
tar -czf "pitun-frontend-${VERSION}.tar.gz" -C frontend/dist .
ls -lh pitun-frontend-*.tar.gz
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pitun-frontend
path: pitun-frontend-*.tar.gz
retention-days: 14
# Backend image must be built per-architecture because the Python wheels
# (uvloop, httptools, watchfiles, etc.) are native binaries.
#
# Build on NATIVE runners per arch — `ubuntu-24.04-arm` for arm64,
# `ubuntu-24.04` for amd64 — instead of cross-building amd64 →
# arm64 via QEMU on a single amd64 runner. v1.3.0-beta.1 / .2 went
# out cross-built and the resulting arm64 wheel binaries SIGILL'd on
# an RPi 4 (Cortex-A72): same uvloop==0.22.1, identical pip freeze,
# identical Python 3.11.15, but different .so md5 vs the v1.2.4 build
# — `import uvicorn` exited 132 (illegal instruction). Native runners
# eliminate the cross-build path entirely.
#
# GitHub now provides free public ARM runners (announced 2025-01); the
# `ubuntu-24.04-arm` label maps to a 4-core Cobalt 100 (Neoverse N2)
# runner, which is binary-compatible with RPi 4 / 5.
backend:
name: Backend image (${{ matrix.platform }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
arch: amd64
runner: ubuntu-24.04
- platform: linux/arm64
arch: arm64
runner: ubuntu-24.04-arm
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# No QEMU needed any more — runner is native arch. Buildx still
# required for the load: true output, but since target == host
# platform, no emulation runs under the hood.
- name: Set up Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
# Pull the version into an env var BEFORE shell sees it. Direct
# `${{ ... }}` interpolation into a `run:` block is a script-
# injection vector if the input is collaborator-controllable
# (workflow_dispatch). With `env:`, GitHub passes the value to
# the shell as a string, no template-time substitution.
- name: Resolve version
id: ver
env:
INPUT_VERSION: ${{ github.ref_name || inputs.version }}
run: |
echo "version=${INPUT_VERSION}" >> "$GITHUB_OUTPUT"
# Build for the target arch and load into the local docker daemon
# so we can `docker save` it into a tarball.
- name: Build & export image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: ./backend
file: ./backend/Dockerfile
target: production
platforms: ${{ matrix.platform }}
load: true
tags: pitun-backend:${{ steps.ver.outputs.version }}
cache-from: type=gha,scope=backend-${{ matrix.arch }}
cache-to: type=gha,scope=backend-${{ matrix.arch }},mode=max
- name: Save image to tarball
env:
VERSION: ${{ steps.ver.outputs.version }}
ARCH: ${{ matrix.arch }}
run: |
docker save "pitun-backend:${VERSION}" \
| gzip > "pitun-backend-${VERSION}-${ARCH}.tar.gz"
ls -lh pitun-backend-*.tar.gz
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pitun-backend-${{ matrix.arch }}
path: pitun-backend-*.tar.gz
retention-days: 14
# Naive sidecar image — same native-runner story as backend.
# naive doesn't have Python wheels (it's a Caddy + naive_forwardproxy
# binary), so it's less likely to hit instruction-set issues, but
# cross-build via QEMU has been a reliable source of weird subtleties
# (file corruption, mtime drift) — keep it consistent with backend.
naive:
name: Naive sidecar image (${{ matrix.platform }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
arch: amd64
runner: ubuntu-24.04
- platform: linux/arm64
arch: arm64
runner: ubuntu-24.04-arm
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
# See backend job above for why we use `env:` here.
- name: Resolve version
id: ver
env:
INPUT_VERSION: ${{ github.ref_name || inputs.version }}
run: |
echo "version=${INPUT_VERSION}" >> "$GITHUB_OUTPUT"
- name: Build & export image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: ./docker/naive
file: ./docker/naive/Dockerfile
platforms: ${{ matrix.platform }}
load: true
tags: pitun-naive:${{ steps.ver.outputs.version }}
cache-from: type=gha,scope=naive-${{ matrix.arch }}
cache-to: type=gha,scope=naive-${{ matrix.arch }},mode=max
- name: Save image to tarball
env:
VERSION: ${{ steps.ver.outputs.version }}
ARCH: ${{ matrix.arch }}
run: |
docker save "pitun-naive:${VERSION}" \
| gzip > "pitun-naive-${VERSION}-${ARCH}.tar.gz"
ls -lh pitun-naive-*.tar.gz
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pitun-naive-${{ matrix.arch }}
path: pitun-naive-*.tar.gz
retention-days: 14
# Bundle everything into a single artifact for convenient download.
bundle:
name: Combine artifacts
needs: [frontend, backend, naive]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
# Restrict to our own artifacts. `build-push-action` also
# uploads opaque internal artifacts named `<owner>~<repo>~XXXX.dockerbuild`
# that we don't want to pull. `pattern: 'pitun-*'` matches the
# five we actually publish: pitun-frontend,
# pitun-{backend,naive}-{amd64,arm64}.
pattern: 'pitun-*'
path: artifacts/
- name: List downloaded artifacts
run: ls -la artifacts/ && find artifacts/ -type f -exec ls -lh {} \;
- name: Flatten into single dir
run: |
mkdir -p release
find artifacts/ -name '*.tar.gz' -exec cp {} release/ \;
ls -lh release/
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pitun-release-bundle
path: release/*
retention-days: 14
# Auto-publish to the GitHub Releases page so install.sh can
# discover assets via the public `/releases/latest` API. Only
# runs on a real tag push (`v*.*.*`); a manual `workflow_dispatch`
# against an arbitrary version string would skip this step,
# since the inputs.version may not correspond to a real tag.
#
# Pre-release detection: any tag containing a hyphen-suffix
# (`-beta.1`, `-rc.2`, `-alpha.0`, etc., per semver 2.0) is
# marked `prerelease: true` and `make_latest: false`. This
# keeps the GitHub `/releases/latest` API pointing at the most
# recent STABLE release, so users running `curl … | sudo bash`
# without an explicit `--version` flag never auto-upgrade onto
# an in-flight beta. Beta testers opt in by running the
# installer with `--version vX.Y.Z-beta.N`.
- name: Detect pre-release tag
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
id: prerelease
run: |
if [[ "${{ github.ref_name }}" == *-* ]]; then
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
echo "Tag ${{ github.ref_name }} contains a hyphen suffix → publishing as pre-release."
else
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
echo "Tag ${{ github.ref_name }} → publishing as stable release (Latest)."
fi
- name: Create / update GitHub Release with assets
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
generate_release_notes: true
fail_on_unmatched_files: true
files: release/*
prerelease: ${{ steps.prerelease.outputs.is_prerelease }}
# `make_latest: legacy` (default) gives Latest only to
# non-prerelease tags. Be explicit so this stays correct
# if a future action version changes the default.
make_latest: ${{ steps.prerelease.outputs.is_prerelease == 'false' && 'true' || 'false' }}