Skip to content
227 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 environment: production
71 timeout-minutes: 60 # the most g1t's own machines run a job
72 steps:
73 - uses: actions/checkout@v5
74 - name: What Tauri needs on Linux
75 # Tried three times: the mirror has answered with a mismatched package before.
76 run: |
77 ok=0
78 for try in 1 2 3; do
79 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
80 sudo apt-get clean; sleep 15
81 done
82 [ "$ok" = 1 ]
83 - uses: actions/cache@v4
84 with:
85 path: |
86 ~/.cargo/registry
87 apps/desktop/src-tauri/target
88 key: desktop-release-linux-${{ hashFiles('apps/desktop/src-tauri/Cargo.lock') }}
89 - name: Install
90 run: npm ci --no-audit --no-fund
91 - name: Build and sign
92 env:
93 TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.DESKTOP_SIGNING_KEY }}
94 TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""
95 run: node scripts/desktop-release.mjs build
96 - name: Collect
97 run: node scripts/desktop-release.mjs collect
98 - uses: actions/upload-artifact@v4
99 with:
100 name: desktop-linux
101 path: target/desktop-release/${{ needs.version.outputs.version }}/
102
103 windows:
104 name: Windows
105 needs: version
106 if: vars.DESKTOP_WINDOWS_RUNNER == 'true'
107 runs-on: [self-hosted, windows]
108 environment: production
109 timeout-minutes: 90
110 steps:
111 - uses: actions/checkout@v5
112 # A runner that is a Windows service runs as the system account, whose
113 # PATH and profile hold no Rust even when the machine's user has one;
114 # the job brings its own, once, into that account's profile.
115 - name: Rust, if this account lacks it
116 run: |
117 if (-not (Get-Command cargo -ErrorAction SilentlyContinue) -and -not (Test-Path "$env:USERPROFILE\.cargo\bin\cargo.exe")) {
118 Invoke-WebRequest https://win.rustup.rs/x86_64 -OutFile "$env:TEMP\rustup-init.exe" -UseBasicParsing
119 & "$env:TEMP\rustup-init.exe" -y --profile minimal --default-toolchain stable | Out-Null
120 }
121 "$env:USERPROFILE\.cargo\bin" | Out-File -Append -Encoding utf8 $env:GITHUB_PATH
122 - name: Install
123 run: npm ci --no-audit --no-fund
124 - name: Build and sign
125 env:
126 TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.DESKTOP_SIGNING_KEY }}
127 TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""
128 run: node scripts/desktop-release.mjs build
129 - name: Collect
130 run: node scripts/desktop-release.mjs collect
131 - uses: actions/upload-artifact@v4
132 with:
133 name: desktop-windows
134 path: target/desktop-release/${{ needs.version.outputs.version }}/
135
136 windows_cross:
137 name: Windows, built on Linux
138 needs: version
139 if: vars.DESKTOP_WINDOWS_FROM_LINUX == 'true' && vars.DESKTOP_WINDOWS_RUNNER != 'true'
140 runs-on: g1t-4core
141 environment: production
142 timeout-minutes: 60 # the most g1t's own machines run a job
143 steps:
144 - uses: actions/checkout@v5
145 - name: What the cross build needs
146 run: |
147 ok=0
148 for try in 1 2 3; do
149 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
150 sudo apt-get clean; sleep 15
151 done
152 [ "$ok" = 1 ]
153 cargo install --locked cargo-xwin
154 - uses: actions/cache@v4
155 with:
156 path: |
157 ~/.cargo/registry
158 apps/desktop/src-tauri/target
159 key: desktop-release-windows-${{ hashFiles('apps/desktop/src-tauri/Cargo.lock') }}
160 - name: Install
161 run: npm ci --no-audit --no-fund
162 - name: Build and sign
163 env:
164 TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.DESKTOP_SIGNING_KEY }}
165 TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""
166 run: node scripts/desktop-release.mjs build --windows-from-linux --only-windows
167 - name: Collect
168 run: node scripts/desktop-release.mjs collect
169 - uses: actions/upload-artifact@v4
170 with:
171 name: desktop-windows
172 path: target/desktop-release/${{ needs.version.outputs.version }}/
173
174 macos:
175 name: macOS
176 needs: version
177 if: vars.DESKTOP_MACOS_RUNNER == 'true'
178 runs-on: [self-hosted, macos]
179 environment: production
180 timeout-minutes: 90
181 steps:
182 - uses: actions/checkout@v5
183 - name: Install
184 run: npm ci --no-audit --no-fund
185 - name: Build and sign, for Apple silicon and Intel
186 env:
187 TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.DESKTOP_SIGNING_KEY }}
188 TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""
189 run: |
190 rustup target add aarch64-apple-darwin x86_64-apple-darwin
191 npx tauri build --ci --target aarch64-apple-darwin
192 npx tauri build --ci --target x86_64-apple-darwin
193 working-directory: apps/desktop
194 - name: Collect
195 run: node scripts/desktop-release.mjs collect
196 - uses: actions/upload-artifact@v4
197 with:
198 name: desktop-macos
199 path: target/desktop-release/${{ needs.version.outputs.version }}/
200
201 publish:
202 name: Publish to g1t.sh/downloads/desktop
203 needs: [version, linux, windows, windows_cross, macos]
204 # A platform with no machine registered is skipped, and the release still goes out with the rest.
205 if: >-
206 always() && needs.linux.result == 'success'
207 && contains(fromJSON('["success","skipped"]'), needs.windows.result)
208 && contains(fromJSON('["success","skipped"]'), needs.windows_cross.result)
209 && contains(fromJSON('["success","skipped"]'), needs.macos.result)
210 runs-on: ubuntu-latest
211 environment: production
212 timeout-minutes: 20
213 steps:
214 - uses: actions/checkout@v5
215 - uses: actions/download-artifact@v4
216 with:
217 pattern: desktop-*
218 merge-multiple: true
219 path: target/desktop-release/${{ needs.version.outputs.version }}/
220 - name: Install Wrangler
221 run: npm ci --workspaces=false --no-audit --no-fund
222 - name: The manifest
223 run: node scripts/desktop-release.mjs manifest --notes "${{ inputs.notes }}"
224 - name: Publish
225 env:
226 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
227 run: node scripts/desktop-release.mjs publish