Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
274 linesCodeBlameRaw
1# Releases the desktop app, g1t (apps/desktop): builds it for Linux on
2# g1t's machines, for Windows and macOS on machines registered for them,
3# signs every updater file, publishes the release to
4# g1t.sh/downloads/desktop/ (the g1t-downloads R2 bucket), and points
5# latest.json at it. Apps already installed update themselves to it;
6# g1t.sh/download offers it, and says which platforms a release lacks.
7#
8# A release is a tag `desktop-v<version>`, where the version is the one in
9# apps/desktop/src-tauri/Cargo.toml; or run it by hand. scripts/
10# desktop-release.mjs does the work, and its header says how to make the
11# signing key the first time; CONTRIBUTING.md ("Deploying") says how a
12# release is made; the guide is docs.g1t.sh/guides/desktop/.
13#
14# Needs the repository secret DESKTOP_SIGNING_KEY (the updater's private
15# key; its public half is in tauri.conf.json), and CLOUDFLARE_API_TOKEN and
16# CLOUDFLARE_ACCOUNT_ID as the deploy has them. The platform jobs beyond
17# Linux each wait on a repository variable, so a release goes out with what
18# can be built:
19#
20# DESKTOP_WINDOWS_RUNNER=true a self-hosted runner on Windows, labels `self-hosted` and `windows`,
21# with Rust, Node and the Visual Studio build tools (the NSIS installer is fetched)
22# DESKTOP_WINDOWS_FROM_LINUX=true no Windows machine: cross-build on g1t's Linux machine with cargo-xwin and NSIS.
23# Needs aka.ms and download.visualstudio.microsoft.com among the project's
24# workflow-only domains for desktop-release.yml in production (Settings, Guardrails),
25# where cargo-xwin fetches the MSVC CRT from.
26# DESKTOP_MACOS_RUNNER=true a self-hosted runner on a Mac, labels `self-hosted` and `macos`.
27name: Desktop release
28
29on:
30 push:
31 tags: ["desktop-v*"]
32 workflow_dispatch:
33 inputs:
34 notes:
35 description: "A line about the release, shown in the manifest"
36 type: string
37
38permissions:
39 contents: read
40
41concurrency:
42 group: desktop-release
43 cancel-in-progress: false
44
45env:
46 CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
47 WRANGLER_SEND_METRICS: "false"
48
49jobs:
50 version:
51 name: The tag names this version
52 runs-on: ubuntu-latest
53 outputs:
54 version: ${{ steps.read.outputs.version }}
55 steps:
56 - uses: actions/checkout@v5
57 - id: read
58 run: |
59 version="$(sed -n 's/^version = "\(.*\)"/\1/p' apps/desktop/src-tauri/Cargo.toml | head -1)"
60 echo "version=$version" >> "$GITHUB_OUTPUT"
61 if [ "${GITHUB_REF_TYPE}" = tag ] && [ "desktop-v$version" != "${GITHUB_REF_NAME}" ]; then
62 echo "::error::The tag is ${GITHUB_REF_NAME}, but apps/desktop is $version"; exit 1
63 fi
64
65 linux:
66 name: Linux
67 needs: version
68 # The larger machine: linking Tauri on the small one dies with a bus error.
69 runs-on: g1t-4core
70 # Production's secrets, without a deployment: a release is not a deploy of
71 # g1t.sh, and would otherwise show on its card as one in progress.
72 environment:
73 name: production
74 deployment: false
75 timeout-minutes: 60 # the most g1t's own machines run a job
76 steps:
77 - uses: actions/checkout@v5
78 - name: What Tauri needs on Linux
79 # Tried three times: the mirror has answered with a mismatched package before.
80 run: |
81 ok=0
82 for try in 1 2 3; do
83 if sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev; then ok=1; break; fi
84 sudo apt-get clean; sleep 15
85 done
86 [ "$ok" = 1 ]
87 - uses: actions/cache@v4
88 with:
89 path: |
90 ~/.cargo/registry
91 apps/desktop/src-tauri/target
92 key: desktop-release-linux-${{ hashFiles('apps/desktop/src-tauri/Cargo.lock') }}
93 # npm's downloads, kept between runs: an install reads them first
94 # and asks the registry only for what changed, so a dropped
95 # connection to it rarely stops the job.
96 - name: Cache npm's downloads
97 uses: actions/cache@v4
98 with:
99 path: ~/.npm
100 key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
101 restore-keys: npm-${{ runner.os }}-
102 - name: Install
103 run: npm ci --prefer-offline --no-audit --no-fund
104 - name: Build and sign
105 env:
106 TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.DESKTOP_SIGNING_KEY }}
107 TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""
108 run: node scripts/desktop-release.mjs build
109 - name: Collect
110 run: node scripts/desktop-release.mjs collect
111 - uses: actions/upload-artifact@v4
112 with:
113 name: desktop-linux
114 path: target/desktop-release/${{ needs.version.outputs.version }}/
115
116 windows:
117 name: Windows
118 needs: version
119 if: vars.DESKTOP_WINDOWS_RUNNER == 'true'
120 runs-on: [self-hosted, windows]
121 # Production's secrets, without a deployment: a release is not a deploy of
122 # g1t.sh, and would otherwise show on its card as one in progress.
123 environment:
124 name: production
125 deployment: false
126 timeout-minutes: 90
127 steps:
128 - uses: actions/checkout@v5
129 # A runner that is a Windows service runs as the system account, whose
130 # PATH and profile hold no Rust even when the machine's user has one;
131 # the job brings its own, once, into that account's profile.
132 - name: Rust, if this account lacks it
133 run: |
134 if (-not (Get-Command cargo -ErrorAction SilentlyContinue) -and -not (Test-Path "$env:USERPROFILE\.cargo\bin\cargo.exe")) {
135 Invoke-WebRequest https://win.rustup.rs/x86_64 -OutFile "$env:TEMP\rustup-init.exe" -UseBasicParsing
136 & "$env:TEMP\rustup-init.exe" -y --profile minimal --default-toolchain stable | Out-Null
137 }
138 "$env:USERPROFILE\.cargo\bin" | Out-File -Append -Encoding utf8 $env:GITHUB_PATH
139 - name: Install
140 run: npm ci --no-audit --no-fund
141 - name: Build and sign
142 env:
143 TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.DESKTOP_SIGNING_KEY }}
144 TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""
145 run: node scripts/desktop-release.mjs build
146 - name: Collect
147 run: node scripts/desktop-release.mjs collect
148 - uses: actions/upload-artifact@v4
149 with:
150 name: desktop-windows
151 path: target/desktop-release/${{ needs.version.outputs.version }}/
152
153 windows_cross:
154 name: Windows, built on Linux
155 needs: version
156 if: vars.DESKTOP_WINDOWS_FROM_LINUX == 'true' && vars.DESKTOP_WINDOWS_RUNNER != 'true'
157 runs-on: g1t-4core
158 # Production's secrets, without a deployment: a release is not a deploy of
159 # g1t.sh, and would otherwise show on its card as one in progress.
160 environment:
161 name: production
162 deployment: false
163 timeout-minutes: 60 # the most g1t's own machines run a job
164 steps:
165 - uses: actions/checkout@v5
166 - name: What the cross build needs
167 run: |
168 ok=0
169 for try in 1 2 3; do
170 if sudo apt-get update && sudo apt-get install -y build-essential curl wget file libssl-dev nsis lld llvm; then ok=1; break; fi
171 sudo apt-get clean; sleep 15
172 done
173 [ "$ok" = 1 ]
174 cargo install --locked cargo-xwin
175 - uses: actions/cache@v4
176 with:
177 path: |
178 ~/.cargo/registry
179 apps/desktop/src-tauri/target
180 key: desktop-release-windows-${{ hashFiles('apps/desktop/src-tauri/Cargo.lock') }}
181 # npm's downloads, kept between runs: an install reads them first
182 # and asks the registry only for what changed, so a dropped
183 # connection to it rarely stops the job.
184 - name: Cache npm's downloads
185 uses: actions/cache@v4
186 with:
187 path: ~/.npm
188 key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
189 restore-keys: npm-${{ runner.os }}-
190 - name: Install
191 run: npm ci --prefer-offline --no-audit --no-fund
192 - name: Build and sign
193 env:
194 TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.DESKTOP_SIGNING_KEY }}
195 TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""
196 run: node scripts/desktop-release.mjs build --windows-from-linux --only-windows
197 - name: Collect
198 run: node scripts/desktop-release.mjs collect
199 - uses: actions/upload-artifact@v4
200 with:
201 name: desktop-windows
202 path: target/desktop-release/${{ needs.version.outputs.version }}/
203
204 macos:
205 name: macOS
206 needs: version
207 if: vars.DESKTOP_MACOS_RUNNER == 'true'
208 runs-on: [self-hosted, macos]
209 # Production's secrets, without a deployment: a release is not a deploy of
210 # g1t.sh, and would otherwise show on its card as one in progress.
211 environment:
212 name: production
213 deployment: false
214 timeout-minutes: 90
215 steps:
216 - uses: actions/checkout@v5
217 - name: Install
218 run: npm ci --no-audit --no-fund
219 - name: Build and sign, for Apple silicon and Intel
220 env:
221 TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.DESKTOP_SIGNING_KEY }}
222 TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""
223 run: |
224 rustup target add aarch64-apple-darwin x86_64-apple-darwin
225 npx tauri build --ci --target aarch64-apple-darwin
226 npx tauri build --ci --target x86_64-apple-darwin
227 working-directory: apps/desktop
228 - name: Collect
229 run: node scripts/desktop-release.mjs collect
230 - uses: actions/upload-artifact@v4
231 with:
232 name: desktop-macos
233 path: target/desktop-release/${{ needs.version.outputs.version }}/
234
235 publish:
236 name: Publish to g1t.sh/downloads/desktop
237 needs: [version, linux, windows, windows_cross, macos]
238 # A platform with no machine registered is skipped, and the release still goes out with the rest.
239 if: >-
240 always() && needs.linux.result == 'success'
241 && contains(fromJSON('["success","skipped"]'), needs.windows.result)
242 && contains(fromJSON('["success","skipped"]'), needs.windows_cross.result)
243 && contains(fromJSON('["success","skipped"]'), needs.macos.result)
244 runs-on: ubuntu-latest
245 # Production's secrets, without a deployment: a release is not a deploy of
246 # g1t.sh, and would otherwise show on its card as one in progress.
247 environment:
248 name: production
249 deployment: false
250 timeout-minutes: 20
251 steps:
252 - uses: actions/checkout@v5
253 - uses: actions/download-artifact@v4
254 with:
255 pattern: desktop-*
256 merge-multiple: true
257 path: target/desktop-release/${{ needs.version.outputs.version }}/
258 # npm's downloads, kept between runs: an install reads them first
259 # and asks the registry only for what changed, so a dropped
260 # connection to it rarely stops the job.
261 - name: Cache npm's downloads
262 uses: actions/cache@v4
263 with:
264 path: ~/.npm
265 key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
266 restore-keys: npm-${{ runner.os }}-
267 - name: Install Wrangler
268 run: npm ci --prefer-offline --workspaces=false --no-audit --no-fund
269 - name: The manifest
270 run: node scripts/desktop-release.mjs manifest --notes "${{ inputs.notes }}"
271 - name: Publish
272 env:
273 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
274 run: node scripts/desktop-release.mjs publish