A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so.
5 files+129−300/5 viewed
| 1 | − | # Releases the desktop app, g1t (apps/desktop): builds it for Windows and | |
| 2 | − | # Linux on a Linux machine, for macOS on a Mac when one is registered, signs | |
| 3 | − | # every updater file, publishes the release to g1t.sh/downloads/desktop/ | |
| 4 | − | # (the g1t-downloads R2 bucket), and points latest.json at it. Apps already | |
| 5 | − | # installed update themselves to it; g1t.sh/download offers it. | |
| 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. | |
| 6 | 7 | # | |
| 7 | 8 | # A release is a tag `desktop-v<version>`, where the version is the one in | |
| 8 | 9 | # apps/desktop/src-tauri/Cargo.toml; or run it by hand. scripts/ | |
| 9 | 10 | # desktop-release.mjs does the work, and its header says how to make the | |
| 10 | 11 | # signing key the first time; CONTRIBUTING.md ("Deploying") says how a | |
| 11 | − | # release is made. | |
| 12 | + | # release is made; the guide is docs.g1t.sh/guides/desktop/. | |
| 12 | 13 | # | |
| 13 | 14 | # Needs the repository secret DESKTOP_SIGNING_KEY (the updater's private | |
| 14 | − | # key; its public half is in tauri.conf.json), CLOUDFLARE_API_TOKEN and | |
| 15 | − | # CLOUDFLARE_ACCOUNT_ID as the deploy has them, and, for macOS, a | |
| 16 | − | # self-hosted runner on a Mac with the labels `self-hosted` and `macos` | |
| 17 | − | # plus the repository variable DESKTOP_MACOS_RUNNER set to `true`; without | |
| 18 | − | # it the macOS job is skipped and the release carries Windows and Linux. | |
| 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`. | |
| 19 | 27 | name: Desktop release | |
| 20 | 28 | ||
| 21 | 29 | on: | |
| ⋯ | |||
| 55 | 63 | fi | |
| 56 | 64 | ||
| 57 | 65 | linux: | |
| 58 | − | name: Linux and Windows | |
| 66 | + | name: Linux | |
| 59 | 67 | needs: version | |
| 60 | 68 | # The larger machine: linking Tauri on the small one dies with a bus error. | |
| 61 | 69 | runs-on: g1t-4core | |
| ⋯ | |||
| 63 | 71 | timeout-minutes: 90 | |
| 64 | 72 | steps: | |
| 65 | 73 | - uses: actions/checkout@v5 | |
| 66 | − | - name: What Tauri needs on Linux, and to build Windows from here | |
| 74 | + | - name: What Tauri needs on Linux | |
| 67 | 75 | # Tried three times: the mirror has answered with a mismatched package before. | |
| 68 | 76 | run: | | |
| 69 | 77 | ok=0 | |
| 70 | 78 | for try in 1 2 3; do | |
| 71 | − | 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 nsis lld llvm; then ok=1; break; fi | |
| 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 | |
| 72 | 80 | sudo apt-get clean; sleep 15 | |
| 73 | 81 | done | |
| 74 | 82 | [ "$ok" = 1 ] | |
| 75 | − | cargo install --locked cargo-xwin | |
| 76 | 83 | - uses: actions/cache@v4 | |
| 77 | 84 | with: | |
| 78 | 85 | path: | | |
| ⋯ | |||
| 85 | 92 | env: | |
| 86 | 93 | TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.DESKTOP_SIGNING_KEY }} | |
| 87 | 94 | TAURI_SIGNING_PRIVATE_KEY_PASSWORD: "" | |
| 88 | − | run: node scripts/desktop-release.mjs build --windows-from-linux | |
| 95 | + | run: node scripts/desktop-release.mjs build | |
| 89 | 96 | - name: Collect | |
| 90 | 97 | run: node scripts/desktop-release.mjs collect | |
| 91 | 98 | - uses: actions/upload-artifact@v4 | |
| ⋯ | |||
| 93 | 100 | name: desktop-linux | |
| 94 | 101 | path: target/desktop-release/${{ needs.version.outputs.version }}/ | |
| 95 | 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 | + | - name: Install | |
| 113 | + | run: npm ci --no-audit --no-fund | |
| 114 | + | - name: Build and sign | |
| 115 | + | env: | |
| 116 | + | TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.DESKTOP_SIGNING_KEY }} | |
| 117 | + | TAURI_SIGNING_PRIVATE_KEY_PASSWORD: "" | |
| 118 | + | run: node scripts/desktop-release.mjs build | |
| 119 | + | - name: Collect | |
| 120 | + | run: node scripts/desktop-release.mjs collect | |
| 121 | + | - uses: actions/upload-artifact@v4 | |
| 122 | + | with: | |
| 123 | + | name: desktop-windows | |
| 124 | + | path: target/desktop-release/${{ needs.version.outputs.version }}/ | |
| 125 | + | ||
| 126 | + | windows_cross: | |
| 127 | + | name: Windows, built on Linux | |
| 128 | + | needs: version | |
| 129 | + | if: vars.DESKTOP_WINDOWS_FROM_LINUX == 'true' && vars.DESKTOP_WINDOWS_RUNNER != 'true' | |
| 130 | + | runs-on: g1t-4core | |
| 131 | + | environment: production | |
| 132 | + | timeout-minutes: 90 | |
| 133 | + | steps: | |
| 134 | + | - uses: actions/checkout@v5 | |
| 135 | + | - name: What the cross build needs | |
| 136 | + | run: | | |
| 137 | + | ok=0 | |
| 138 | + | for try in 1 2 3; do | |
| 139 | + | 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 | |
| 140 | + | sudo apt-get clean; sleep 15 | |
| 141 | + | done | |
| 142 | + | [ "$ok" = 1 ] | |
| 143 | + | cargo install --locked cargo-xwin | |
| 144 | + | - uses: actions/cache@v4 | |
| 145 | + | with: | |
| 146 | + | path: | | |
| 147 | + | ~/.cargo/registry | |
| 148 | + | apps/desktop/src-tauri/target | |
| 149 | + | key: desktop-release-windows-${{ hashFiles('apps/desktop/src-tauri/Cargo.lock') }} | |
| 150 | + | - name: Install | |
| 151 | + | run: npm ci --no-audit --no-fund | |
| 152 | + | - name: Build and sign | |
| 153 | + | env: | |
| 154 | + | TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.DESKTOP_SIGNING_KEY }} | |
| 155 | + | TAURI_SIGNING_PRIVATE_KEY_PASSWORD: "" | |
| 156 | + | run: node scripts/desktop-release.mjs build --windows-from-linux --only-windows | |
| 157 | + | - name: Collect | |
| 158 | + | run: node scripts/desktop-release.mjs collect | |
| 159 | + | - uses: actions/upload-artifact@v4 | |
| 160 | + | with: | |
| 161 | + | name: desktop-windows | |
| 162 | + | path: target/desktop-release/${{ needs.version.outputs.version }}/ | |
| 163 | + | ||
| 96 | 164 | macos: | |
| 97 | 165 | name: macOS | |
| 98 | 166 | needs: version | |
| ⋯ | |||
| 122 | 190 | ||
| 123 | 191 | publish: | |
| 124 | 192 | name: Publish to g1t.sh/downloads/desktop | |
| 125 | − | needs: [version, linux, macos] | |
| 126 | − | # With no Mac registered the macOS job is skipped, and the release still goes out. | |
| 127 | − | if: always() && needs.linux.result == 'success' && (needs.macos.result == 'success' || needs.macos.result == 'skipped') | |
| 193 | + | needs: [version, linux, windows, windows_cross, macos] | |
| 194 | + | # A platform with no machine registered is skipped, and the release still goes out with the rest. | |
| 195 | + | if: >- | |
| 196 | + | always() && needs.linux.result == 'success' | |
| 197 | + | && contains(fromJSON('["success","skipped"]'), needs.windows.result) | |
| 198 | + | && contains(fromJSON('["success","skipped"]'), needs.windows_cross.result) | |
| 199 | + | && contains(fromJSON('["success","skipped"]'), needs.macos.result) | |
| 128 | 200 | runs-on: ubuntu-latest | |
| 129 | 201 | environment: production | |
| 130 | 202 | timeout-minutes: 20 | |
| 103 | 103 | - **The desktop app** is released the same way: bump `version` in | |
| 104 | 104 | `apps/desktop/src-tauri/Cargo.toml`, merge, and push a | |
| 105 | 105 | `desktop-v<version>` tag. `.g1t/workflows/desktop-release.yml` builds | |
| 106 | − | the installers (Windows and Linux on Linux; macOS on a Mac runner when | |
| 107 | − | one is registered), signs the updater's files and publishes them, and | |
| 108 | − | installed apps update themselves. `scripts/desktop-release.mjs` does the | |
| 106 | + | the installers (Linux on g1t's machines; Windows and macOS on machines | |
| 107 | + | registered for them, or Windows cross-built on Linux once its download | |
| 108 | + | hosts are allowed, each switched on by a repository variable the | |
| 109 | + | workflow's header names; a platform with no machine is left out of that | |
| 110 | + | release), signs the updater's files and publishes them, and installed | |
| 111 | + | apps update themselves. `scripts/desktop-release.mjs` does the | |
| 109 | 112 | same by hand. The app loads the live site, so a change to the site needs | |
| 110 | 113 | no release; only a change under `apps/desktop` does. The guide is | |
| 111 | 114 | [The desktop app](https://docs.g1t.sh/guides/desktop/). |
| 129 | 129 | | What | Where | | |
| 130 | 130 | | --- | --- | | |
| 131 | 131 | | The updater's signing key | `node scripts/desktop-release.mjs keygen`, once. The private key is the repository secret `DESKTOP_SIGNING_KEY`; the public key is `plugins.updater.pubkey` in `tauri.conf.json`. Losing the private key means every installed app stops updating: keep it. | | |
| 132 | − | | Windows and Linux builds | A Linux machine builds both: Linux natively, Windows with `cargo-xwin` and NSIS. | | |
| 133 | − | | macOS builds | A Mac, registered as a [self-hosted runner](/guides/self-hosted-runners/) with the labels `self-hosted` and `macos`, and the repository variable `DESKTOP_MACOS_RUNNER` set to `true`. Without one, the macOS job is skipped and the release carries Windows and Linux. | | |
| 132 | + | | Linux builds | g1t's own Linux machines, in every release. | | |
| 133 | + | | Windows builds | Either a Windows machine registered as a [self-hosted runner](/guides/self-hosted-runners/) with the labels `self-hosted` and `windows` (Rust, Node and the Visual Studio build tools on it) and the repository variable `DESKTOP_WINDOWS_RUNNER` set to `true`; or, with no Windows machine, `DESKTOP_WINDOWS_FROM_LINUX` set to `true`, which cross-builds on Linux with `cargo-xwin` and NSIS and needs `aka.ms` and `download.visualstudio.microsoft.com` among the project's [workflow-only domains](/guides/guardrails/#workflow-only-domains) for `desktop-release.yml` in `production`, where the MSVC runtime is fetched from. | | |
| 134 | + | | macOS builds | A Mac, registered as a self-hosted runner with the labels `self-hosted` and `macos`, and the repository variable `DESKTOP_MACOS_RUNNER` set to `true`. | | |
| 135 | + | | A platform with no machine | Its job is skipped and the release goes out with the rest; the download page says which platforms a release lacks, and the next release fills them in. | | |
| 134 | 136 | | Where releases are served | `g1t.sh/downloads/desktop/<version>/<file>`, from the same bucket as the runner's and the CLI's releases. | | |
| 135 | 137 | ||
| 136 | 138 | A self-hosted g1t does not serve the app's releases: the app always |
| 194 | 194 | </Card> | |
| 195 | 195 | )} | |
| 196 | 196 | ||
| 197 | + | {release && !featured && platform && ( | |
| 198 | + | <Card className="p-5"> | |
| 199 | + | <p className="font-medium">Not yet for {PLATFORMS.find((p) => p.id === platform)?.name}.</p> | |
| 200 | + | <p className="mt-1 text-sm text-muted"> | |
| 201 | + | Version {release.version} is out for {Array.from(new Set(release.downloads.map((d) => PLATFORMS.find((p) => p.id === d.platform)?.name))).join(" and ")}; | |
| 202 | + | yours is built in a later release. Until then, g1t works as an installed web app: in your browser's menu, choose Install or Add to Dock. | |
| 203 | + | </p> | |
| 204 | + | </Card> | |
| 205 | + | )} | |
| 206 | + | ||
| 197 | 207 | <div className="mt-10 grid gap-3 sm:grid-cols-2"> | |
| 198 | 208 | {WHAT.map((item) => ( | |
| 199 | 209 | <div key={item.title} className="flex gap-3 rounded-xl border border-line bg-surface p-5"> | |
| ⋯ | |||
| 219 | 229 | <div className="mt-4 space-y-3"> | |
| 220 | 230 | {ordered.map((p) => { | |
| 221 | 231 | const files = release.downloads.filter((d) => d.platform === p.id); | |
| 222 | − | if (files.length === 0) return null; | |
| 232 | + | // A platform this release lacks: it is built on a machine not yet registered. | |
| 233 | + | if (files.length === 0) { | |
| 234 | + | return ( | |
| 235 | + | <Card key={p.id} tone="plain" className="flex flex-wrap items-center gap-2 rounded-xl px-4 py-3"> | |
| 236 | + | <span className="text-faint">{p.icon}</span> | |
| 237 | + | <span className="font-medium text-muted">{p.name}</span> | |
| 238 | + | <span className="text-sm text-muted">· Coming in a later release. {p.note}</span> | |
| 239 | + | </Card> | |
| 240 | + | ); | |
| 241 | + | } | |
| 223 | 242 | return ( | |
| 224 | 243 | <Card key={p.id} tone="plain" divided className="rounded-xl"> | |
| 225 | 244 | <div className="flex items-center gap-2 px-4 py-3"> | |
| 8 | 8 | // release (apps/web/app/routes/download.tsx). | |
| 9 | 9 | // | |
| 10 | 10 | // node scripts/desktop-release.mjs keygen # once: the updater's signing key | |
| 11 | − | // node scripts/desktop-release.mjs build [--windows-from-linux] | |
| 11 | + | // node scripts/desktop-release.mjs build [--windows-from-linux [--only-windows]] | |
| 12 | 12 | // node scripts/desktop-release.mjs collect # this machine's bundles, into the release folder | |
| 13 | 13 | // node scripts/desktop-release.mjs manifest [--notes "…"] # latest.json and SHA256SUMS from what is collected | |
| 14 | 14 | // node scripts/desktop-release.mjs publish [--dry-run] | |
| ⋯ | |||
| 155 | 155 | run("npx", ["tauri", "signer", "generate", "--ci"], { cwd: APP }); | |
| 156 | 156 | } | |
| 157 | 157 | ||
| 158 | − | function build(windowsFromLinux) { | |
| 158 | + | /** `windowsFromLinux` adds the Windows cross build; `onlyWindows` skips this machine's own. */ | |
| 159 | + | function build(windowsFromLinux, onlyWindows = false) { | |
| 159 | 160 | if (!process.env.TAURI_SIGNING_PRIVATE_KEY) console.error("warning: TAURI_SIGNING_PRIVATE_KEY is not set; these bundles cannot be fetched by the updater"); | |
| 160 | − | console.error(`building ${hostPlatform()}`); | |
| 161 | − | run("npx", ["tauri", "build", "--ci"], { cwd: APP }); | |
| 161 | + | if (!onlyWindows) { | |
| 162 | + | console.error(`building ${hostPlatform()}`); | |
| 163 | + | run("npx", ["tauri", "build", "--ci"], { cwd: APP }); | |
| 164 | + | } | |
| 162 | 165 | if (windowsFromLinux) { | |
| 163 | 166 | // Tauri's cross build: cargo-xwin for the MSVC target, NSIS for the | |
| 164 | 167 | // installer, both on PATH (the release workflow installs them). | |
| ⋯ | |||
| 249 | 252 | keygen(); | |
| 250 | 253 | break; | |
| 251 | 254 | case "build": | |
| 252 | − | build(rest.includes("--windows-from-linux")); | |
| 255 | + | build(rest.includes("--windows-from-linux"), rest.includes("--only-windows")); | |
| 253 | 256 | break; | |
| 254 | 257 | case "collect": | |
| 255 | 258 | collect(); | |