Repository navigation
Merge pull request #19 from TomCohenDev/fork-sync/2026-11-05-2031 #24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Fork Release | |
| # Builds the desktop apps and the headless server archives from fork main and | |
| # publishes a nightly-channel release. The desktop apps update from it with | |
| # electron-updater. Servers update from it with the upstream boot-service | |
| # updater (`t3 update`, "Update everything"), which reads the server archives | |
| # t3-<version>-linux-<arch>.tar.gz and SHA256SUMS. SHA256SUMS is uploaded | |
| # last, so a release without it is not ready for servers. It does not touch | |
| # main: .github/workflows/fork-sync.yml merges upstream. See docs/fork/operations.md "Fork Release". | |
| on: | |
| # Every push to main, so that servers get each change from a release. A push | |
| # that changes only docs, markdown or the portal builds no release. The next | |
| # release lists its commits, because the notes start at the previous tag. | |
| push: | |
| branches: [main] | |
| paths-ignore: | |
| - docs/** | |
| - "**/*.md" | |
| workflow_dispatch: | |
| inputs: | |
| notify_agent: | |
| description: Call the agent webhook if this run fails. The agent sets false on its own retries. | |
| type: boolean | |
| default: true | |
| # One run at a time, and a run is never cancelled, so a release is never half | |
| # published. GitHub keeps one waiting run: a newer push replaces it, so a | |
| # burst of pushes builds only the running commit and the newest one. | |
| concurrency: | |
| group: fork-release | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| jobs: | |
| # Also builds the JS bundle: a second job spent a minute on its own checkout and setup. | |
| prepare: | |
| name: Prepare release and build JS bundle | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 30 | |
| outputs: | |
| sha: ${{ steps.commit.outputs.sha }} | |
| released: ${{ steps.released.outputs.released }} | |
| version: ${{ steps.version.outputs.version }} | |
| tag: ${{ steps.version.outputs.tag }} | |
| name: ${{ steps.version.outputs.name }} | |
| notes: ${{ steps.notes.outputs.notes }} | |
| steps: | |
| # The full history and the release tags, without file contents, for the release notes. | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| filter: blob:none | |
| sparse-checkout: | | |
| /* | |
| !/.repos/ | |
| sparse-checkout-cone-mode: false | |
| - id: commit | |
| run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" | |
| - id: released | |
| name: Check for an existing release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| sha="${{ steps.commit.outputs.sha }}" | |
| existing="$(gh release list --repo "$GITHUB_REPOSITORY" --exclude-drafts --limit 30 --json tagName,name \ | |
| --jq "[.[] | select(.name | contains(\"(${sha:0:12})\"))][0].tagName // empty")" | |
| if [[ -n "$existing" ]]; then | |
| echo "Commit ${sha} is already released as ${existing}." | |
| echo "released=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "released=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - uses: voidzero-dev/setup-vp@v1 | |
| with: | |
| node-version-file: package.json | |
| cache: true | |
| run-install: false | |
| - run: vp install --filter=t3... --filter=@t3tools/web... --filter=@t3tools/desktop... --filter=@t3tools/scripts... | |
| # <base>-nightly.<UTC date>.<run number x 10000>. The date and the run number | |
| # only grow. The gap of 10000 leaves room for local builds between two CI | |
| # releases (scripts/fork-deploy-local.sh). The app needs one integer here. | |
| - id: version | |
| name: Resolve release version | |
| run: | | |
| node scripts/resolve-nightly-release.ts \ | |
| --date "$(date -u +%Y%m%d)" \ | |
| --run-number "$(( ${{ github.run_number }} * 10000 ))" \ | |
| --sha "${{ steps.commit.outputs.sha }}" \ | |
| --github-output | |
| # The commits after the previous release tag, from git. See scripts/fork-release-notes.ts. | |
| # A failed builder never stops the release: it warns and falls back to a one-line note. | |
| - id: notes | |
| name: Write release notes | |
| run: | | |
| set -euo pipefail | |
| sha="${{ steps.commit.outputs.sha }}" | |
| if ! node scripts/fork-release-notes.ts "$sha" > "$RUNNER_TEMP/notes.md" 2> "$RUNNER_TEMP/notes-error.log"; then | |
| cat "$RUNNER_TEMP/notes-error.log" >&2 | |
| echo "::warning title=Release notes failed::$(tail -n 1 "$RUNNER_TEMP/notes-error.log")" | |
| echo "Fork build of pingdotgg/t3code main plus fork commits, from ${sha}. (Release notes failed; see the workflow log.)" > "$RUNNER_TEMP/notes.md" | |
| fi | |
| cat "$RUNNER_TEMP/notes.md" | |
| { | |
| echo "notes<<FORK_RELEASE_NOTES_EOF" | |
| cat "$RUNNER_TEMP/notes.md" | |
| echo "FORK_RELEASE_NOTES_EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| - if: steps.released.outputs.released != 'true' | |
| run: node scripts/update-release-package-versions.ts "${{ steps.version.outputs.version }}" | |
| - if: steps.released.outputs.released != 'true' | |
| run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config | |
| - if: steps.released.outputs.released != 'true' | |
| env: | |
| # Optional T3 Connect config. Empty values build the app without T3 Connect. | |
| T3CODE_CLERK_PUBLISHABLE_KEY: ${{ vars.CLERK_PUBLISHABLE_KEY }} | |
| T3CODE_CLERK_JWT_TEMPLATE: ${{ vars.CLERK_JWT_TEMPLATE }} | |
| T3CODE_CLERK_CLI_OAUTH_CLIENT_ID: ${{ vars.CLERK_CLI_OAUTH_CLIENT_ID }} | |
| T3CODE_RELAY_URL: ${{ vars.RELAY_URL }} | |
| run: vp run build:desktop | |
| - if: steps.released.outputs.released != 'true' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: js-bundle | |
| path: | | |
| apps/server/dist | |
| apps/desktop/dist-electron | |
| if-no-files-found: error | |
| retention-days: 1 | |
| desktop: | |
| name: Desktop ${{ matrix.label }} | |
| needs: [prepare] | |
| if: needs.prepare.outputs.released != 'true' | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: write | |
| # Each platform publishes its own files, so the Mac release does not wait | |
| # for the slower Windows build, and a Windows failure does not stop it. | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - label: macOS arm64 | |
| runner: macos-15 | |
| platform: mac | |
| target: dmg | |
| arch: arm64 | |
| rust_target: aarch64-apple-darwin | |
| - label: macOS x64 | |
| runner: macos-15-intel | |
| platform: mac | |
| target: dmg | |
| arch: x64 | |
| rust_target: x86_64-apple-darwin | |
| - label: Windows x64 | |
| runner: windows-2025 | |
| platform: win | |
| target: nsis | |
| arch: x64 | |
| rust_target: x86_64-pc-windows-msvc | |
| # node-pty compiles from source on Linux, so each arch builds on its own hardware. | |
| - label: Linux x64 | |
| runner: ubuntu-24.04 | |
| platform: linux | |
| target: AppImage | |
| arch: x64 | |
| rust_target: x86_64-unknown-linux-gnu | |
| resource_key: linux-x64 | |
| - label: Linux arm64 | |
| runner: ubuntu-24.04-arm | |
| platform: linux | |
| target: AppImage | |
| arch: arm64 | |
| rust_target: aarch64-unknown-linux-gnu | |
| resource_key: linux-arm64 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.prepare.outputs.sha }} | |
| sparse-checkout: | | |
| /* | |
| !/.repos/ | |
| sparse-checkout-cone-mode: false | |
| - uses: voidzero-dev/setup-vp@v1 | |
| with: | |
| node-version-file: package.json | |
| cache: ${{ matrix.platform != 'win' }} | |
| run-install: false | |
| - run: vp install --filter=@t3tools/desktop... --filter=t3... --filter=@t3tools/scripts... | |
| - run: node scripts/update-release-package-versions.ts "${{ needs.prepare.outputs.version }}" | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: js-bundle | |
| path: apps | |
| # Same packages and toolchain as upstream's release-desktop.yml. | |
| - name: Install Linux desktop build libraries | |
| if: matrix.platform == 'linux' | |
| run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config imagemagick | |
| # Same caches as upstream's release-desktop.yml. An uncached build took up to 105 seconds. | |
| # Limit: the keys hold the crate sources, not the Rust version, so a new stable Rust | |
| # alone rebuilds nothing. Add the Rust version to the keys if a toolchain change matters. | |
| - name: Cache resource monitor | |
| id: resource_monitor_cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: native/resource-monitor/target/${{ matrix.rust_target }}/release/t3-resource-monitor${{ matrix.platform == 'win' && '.exe' || '' }} | |
| key: resource-monitor-${{ matrix.rust_target }}-${{ hashFiles('native/resource-monitor/Cargo.lock', 'native/resource-monitor/Cargo.toml', 'native/resource-monitor/src/**') }} | |
| - name: Cache Linux capture helpers | |
| id: capture_helper_cache | |
| if: matrix.platform == 'linux' | |
| uses: actions/cache@v6 | |
| with: | |
| path: | | |
| native/kde-snap-shot/target/${{ matrix.rust_target }}/release/t3-kde-snap-shot | |
| native/hyprland-snap-shot/target/${{ matrix.rust_target }}/release/t3-hyprland-snap-shot | |
| key: linux-capture-helpers-${{ matrix.rust_target }}-${{ hashFiles('native/kde-snap-shot/Cargo.lock', 'native/kde-snap-shot/Cargo.toml', 'native/kde-snap-shot/src/**', 'native/hyprland-snap-shot/Cargo.lock', 'native/hyprland-snap-shot/Cargo.toml', 'native/hyprland-snap-shot/src/**', 'native/hyprland-snap-shot/protocols/**') }} | |
| - if: matrix.platform == 'linux' && (steps.resource_monitor_cache.outputs.cache-hit != 'true' || steps.capture_helper_cache.outputs.cache-hit != 'true') | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.rust_target }} | |
| # The resource monitor build needs the Spectre-mitigated MSVC libraries. | |
| # The Visual Studio Installer takes minutes, so cache the library folder. | |
| - id: spectre | |
| name: Find Spectre-mitigated MSVC lib folder | |
| if: matrix.platform == 'win' && steps.resource_monitor_cache.outputs.cache-hit != 'true' | |
| shell: pwsh | |
| run: | | |
| $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" | |
| $installPath = & $vswhere -products * -latest -property installationPath | |
| $msvc = Get-ChildItem "$installPath\VC\Tools\MSVC" | Sort-Object Name | Select-Object -Last 1 | |
| "path=$($msvc.FullName)\lib\spectre" >> $env:GITHUB_OUTPUT | |
| "key=msvc-spectre-${env:ImageOS}-${env:ImageVersion}-$($msvc.Name)" >> $env:GITHUB_OUTPUT | |
| - id: spectre_cache | |
| if: matrix.platform == 'win' && steps.resource_monitor_cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/restore@v6 | |
| with: | |
| path: ${{ steps.spectre.outputs.path }} | |
| key: ${{ steps.spectre.outputs.key }} | |
| - name: Install Spectre-mitigated MSVC libs | |
| if: matrix.platform == 'win' && steps.resource_monitor_cache.outputs.cache-hit != 'true' && steps.spectre_cache.outputs.cache-hit != 'true' | |
| shell: pwsh | |
| run: | | |
| $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" | |
| $installPath = & $vswhere -products * -latest -property installationPath | |
| $setupExe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\setup.exe" | |
| $proc = Start-Process -FilePath $setupExe ` | |
| -ArgumentList "modify", "--installPath", "`"$installPath`"", "--add", ` | |
| "Microsoft.VisualStudio.Component.VC.Runtimes.x86.x64.Spectre", "--quiet", "--norestart" ` | |
| -Wait -PassThru -NoNewWindow | |
| if ($null -eq $proc -or $proc.ExitCode -ne 0) { | |
| Write-Error "Visual Studio Installer failed" | |
| exit 1 | |
| } | |
| - if: matrix.platform == 'win' && steps.resource_monitor_cache.outputs.cache-hit != 'true' && steps.spectre_cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@v6 | |
| with: | |
| path: ${{ steps.spectre.outputs.path }} | |
| key: ${{ steps.spectre.outputs.key }} | |
| # Built here, so the desktop build below always reuses it. | |
| - name: Build resource monitor | |
| if: steps.resource_monitor_cache.outputs.cache-hit != 'true' | |
| shell: bash | |
| run: cargo build --locked --release --manifest-path native/resource-monitor/Cargo.toml --target "${{ matrix.rust_target }}" | |
| # electron-builder only signs with a trusted identity. The self-made | |
| # certificate is its own root, so trust it on the runner for code signing. | |
| - name: Trust fork signing certificate | |
| if: matrix.platform == 'mac' | |
| env: | |
| CSC_LINK: ${{ secrets.CSC_LINK }} | |
| CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} | |
| run: | | |
| set -euo pipefail | |
| if [[ -z "$CSC_LINK" || -z "$CSC_KEY_PASSWORD" ]]; then | |
| echo "CSC_LINK and CSC_KEY_PASSWORD are required. Squirrel.Mac rejects updates with another signature." >&2 | |
| exit 1 | |
| fi | |
| printf '%s' "$CSC_LINK" | base64 --decode > "$RUNNER_TEMP/fork.p12" | |
| /usr/bin/openssl pkcs12 -in "$RUNNER_TEMP/fork.p12" -nokeys -clcerts -passin env:CSC_KEY_PASSWORD -out "$RUNNER_TEMP/fork.pem" | |
| sudo security add-trusted-cert -d -r trustRoot -p codeSign -k /Library/Keychains/System.keychain "$RUNNER_TEMP/fork.pem" | |
| rm -f "$RUNNER_TEMP/fork.p12" | |
| - name: Build desktop artifact | |
| shell: bash | |
| env: | |
| T3CODE_DESKTOP_UPDATE_REPOSITORY: ${{ github.repository }} | |
| T3CODE_DESKTOP_MAC_SELF_SIGNED: "true" | |
| T3CODE_DESKTOP_REUSE_RESOURCE_MONITOR: "true" | |
| T3CODE_DESKTOP_REUSE_LINUX_CAPTURE_HELPERS: ${{ steps.capture_helper_cache.outputs.cache-hit == 'true' }} | |
| CSC_LINK: ${{ matrix.platform == 'mac' && secrets.CSC_LINK || '' }} | |
| CSC_KEY_PASSWORD: ${{ matrix.platform == 'mac' && secrets.CSC_KEY_PASSWORD || '' }} | |
| run: | | |
| set -euo pipefail | |
| args=(--platform "${{ matrix.platform }}" --target "${{ matrix.target }}" --arch "${{ matrix.arch }}" | |
| --build-version "${{ needs.prepare.outputs.version }}" --skip-build --verbose) | |
| # Windows stays unsigned. No Apple API key is set, so macOS is not notarized. | |
| if [[ "${{ matrix.platform }}" == "mac" ]]; then | |
| args+=(--signed) | |
| fi | |
| vp run dist:desktop:artifact "${args[@]}" | |
| - name: Verify macOS signature | |
| if: matrix.platform == 'mac' | |
| run: | | |
| set -euo pipefail | |
| # The build script keeps only the dmg and zip, so check the app inside the zip. | |
| ditto -x -k release/*-${{ matrix.arch }}.zip "$RUNNER_TEMP/verify" | |
| app="$(ls -d "$RUNNER_TEMP"/verify/*.app)" | |
| codesign -dvv "$app" 2>&1 | tee "$RUNNER_TEMP/codesign.txt" | |
| codesign --verify --deep --strict --verbose=2 "$app" | |
| grep -q "Tom Cohen T3 Fork" "$RUNNER_TEMP/codesign.txt" | |
| # The headless server: upstream's self-contained CLI archive with the `t3` | |
| # single-executable, the web client and the native packages. It needs no | |
| # Node on the machine. The steps copy upstream's release-desktop.yml. | |
| # Setup Vite+ selects the exact engines.node version from package.json, | |
| # which a native build must run on. | |
| - name: Build server single-executable | |
| if: matrix.platform == 'linux' | |
| run: node apps/server/scripts/cli.ts build-exe --verbose | |
| - name: Build server archive | |
| if: matrix.platform == 'linux' | |
| run: | | |
| set -euo pipefail | |
| monitor_dir="$RUNNER_TEMP/cli-resource-monitor" | |
| mkdir -p "$monitor_dir/${{ matrix.resource_key }}" | |
| cp "native/resource-monitor/target/${{ matrix.rust_target }}/release/t3-resource-monitor" \ | |
| "$monitor_dir/${{ matrix.resource_key }}/" | |
| node scripts/build-cli-archive.ts \ | |
| --platform linux \ | |
| --arch "${{ matrix.arch }}" \ | |
| --version "${{ needs.prepare.outputs.version }}" \ | |
| --resource-monitor-dir "$monitor_dir" \ | |
| --output-dir release-cli | |
| node scripts/smoke-cli-archive.ts --archive release-cli/* --expect-version "${{ needs.prepare.outputs.version }}" | |
| - name: Collect release assets | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob | |
| mkdir -p release-publish | |
| for file in release/*.dmg release/*.zip release/*.exe release/*.AppImage release/*.blockmap release/*.yml release-cli/*; do | |
| [[ "$file" == */builder-debug.yml ]] && continue | |
| cp "$file" release-publish/ | |
| done | |
| # Both Mac builds write nightly-mac.yml. The mac_manifest job merges the x64 one into it. | |
| if [[ "${{ matrix.platform }}" == mac && "${{ matrix.arch }}" == x64 ]]; then | |
| mv release-publish/nightly-mac.yml release-publish/nightly-mac-x64.yml | |
| fi | |
| ls -la release-publish | |
| # The first platform to finish creates the release, the others add their files. | |
| # Each platform has its own updater manifest: nightly-mac.yml (nightly-mac-x64.yml | |
| # until mac_manifest merges it), nightly.yml (Windows), nightly-linux.yml (x64) and | |
| # nightly-linux-arm64.yml. | |
| # The release creates a tag. GITHUB_TOKEN cannot create a tag on a commit | |
| # whose workflow files differ from main, which happens when main moved | |
| # during the run. FORK_SYNC_TOKEN has the workflow scope. | |
| - name: Publish to the release | |
| if: github.ref == 'refs/heads/main' | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ secrets.FORK_SYNC_TOKEN || github.token }} | |
| NOTES: ${{ needs.prepare.outputs.notes }} | |
| MANIFEST: ${{ matrix.platform == 'mac' && matrix.arch == 'x64' && 'nightly-mac-x64.yml' || matrix.platform == 'mac' && 'nightly-mac.yml' || matrix.platform == 'win' && 'nightly.yml' || matrix.arch == 'arm64' && 'nightly-linux-arm64.yml' || 'nightly-linux.yml' }} | |
| run: | | |
| set -euo pipefail | |
| test -s "release-publish/$MANIFEST" | |
| tag="${{ needs.prepare.outputs.tag }}" | |
| if ! gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 && | |
| gh release create "$tag" release-publish/* \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --target "${{ needs.prepare.outputs.sha }}" \ | |
| --title "${{ needs.prepare.outputs.name }}" \ | |
| --notes "$NOTES" \ | |
| --prerelease; then | |
| exit 0 | |
| fi | |
| # Another platform may still be creating the release: it stays a draft, which its | |
| # tag cannot find, until that platform's files are up. Wait for it, 20 minutes at most. | |
| for attempt in $(seq 1 40); do | |
| gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 && break | |
| if [ "$attempt" -eq 40 ]; then | |
| echo "Release $tag did not appear." >&2 | |
| exit 1 | |
| fi | |
| sleep 30 | |
| done | |
| gh release upload "$tag" release-publish/* --repo "$GITHUB_REPOSITORY" --clobber | |
| # A manual run from another branch tests the build without a release. | |
| - name: Upload assets of a branch run | |
| if: github.ref != 'refs/heads/main' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-${{ matrix.platform }}-${{ matrix.arch }} | |
| path: release-publish/* | |
| if-no-files-found: error | |
| retention-days: 3 | |
| # The checksums job writes SHA256SUMS from these exact bytes. This step | |
| # runs after the publish step, so the archive is on the release. | |
| - name: Upload server archive for the checksums | |
| if: matrix.platform == 'linux' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: cli-linux-${{ matrix.arch }} | |
| path: release-cli/t3-*.tar.gz | |
| if-no-files-found: error | |
| retention-days: 1 | |
| # electron-updater reads one nightly-mac.yml and picks the file entry of the running arch, | |
| # so the x64 manifest merges into the arm64 one. Without both, the release keeps what it has: | |
| # Intel Macs then skip this release, and Apple silicon Macs never get an x64 entry. | |
| mac_manifest: | |
| name: Merge macOS updater manifests | |
| needs: [prepare, desktop] | |
| if: always() && needs.prepare.result == 'success' && needs.prepare.outputs.released != 'true' && github.ref == 'refs/heads/main' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.prepare.outputs.sha }} | |
| sparse-checkout: | | |
| /* | |
| !/.repos/ | |
| sparse-checkout-cone-mode: false | |
| - uses: voidzero-dev/setup-vp@v1 | |
| with: | |
| node-version-file: package.json | |
| cache: true | |
| run-install: false | |
| - run: vp install --filter=@t3tools/scripts... | |
| - name: Merge and publish nightly-mac.yml | |
| env: | |
| GH_TOKEN: ${{ secrets.FORK_SYNC_TOKEN || github.token }} | |
| TAG: ${{ needs.prepare.outputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p manifests | |
| for name in nightly-mac.yml nightly-mac-x64.yml; do | |
| gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --pattern "$name" --dir manifests || true | |
| done | |
| if [[ ! -s manifests/nightly-mac.yml || ! -s manifests/nightly-mac-x64.yml ]]; then | |
| echo "Both macOS manifests are needed to merge. Found: $(ls manifests)" | |
| exit 0 | |
| fi | |
| node scripts/merge-update-manifests.ts --platform mac manifests/nightly-mac.yml manifests/nightly-mac-x64.yml | |
| cat manifests/nightly-mac.yml | |
| gh release upload "$TAG" manifests/nightly-mac.yml --repo "$GITHUB_REPOSITORY" --clobber | |
| gh release delete-asset "$TAG" nightly-mac-x64.yml --repo "$GITHUB_REPOSITORY" --yes | |
| # Writes SHA256SUMS for the server archives and uploads it last. The server | |
| # updaters need it, so a release without it is not installable on servers. | |
| # It runs only when every server archive exists, so it never marks a release | |
| # with a missing archive as ready. A desktop failure does not stop it. | |
| checksums: | |
| name: Server checksums | |
| needs: [prepare, desktop] | |
| if: always() && needs.prepare.result == 'success' && needs.prepare.outputs.released != 'true' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| pattern: cli-* | |
| merge-multiple: true | |
| path: release-cli | |
| # Keep in step with the Linux rows of the desktop matrix. | |
| - name: Write SHA256SUMS | |
| run: | | |
| set -euo pipefail | |
| cd release-cli | |
| version="${{ needs.prepare.outputs.version }}" | |
| archives=("t3-${version}-linux-arm64.tar.gz" "t3-${version}-linux-x64.tar.gz") | |
| for archive in "${archives[@]}"; do | |
| test -s "$archive" || { echo "Missing server archive $archive." >&2; exit 1; } | |
| done | |
| sha256sum "${archives[@]}" > SHA256SUMS | |
| cat SHA256SUMS | |
| - name: Publish SHA256SUMS | |
| if: github.ref == 'refs/heads/main' | |
| env: | |
| GH_TOKEN: ${{ secrets.FORK_SYNC_TOKEN || github.token }} | |
| run: gh release upload "${{ needs.prepare.outputs.tag }}" release-cli/SHA256SUMS --repo "$GITHUB_REPOSITORY" --clobber | |
| - name: Upload SHA256SUMS of a branch run | |
| if: github.ref != 'refs/heads/main' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-checksums | |
| path: release-cli/SHA256SUMS | |
| if-no-files-found: error | |
| retention-days: 3 | |
| # Starts a repair agent on a T3 server through a scheduled-task webhook. | |
| # Off until the T3_AGENT_WEBHOOK_URL secret is set. | |
| notify_agent: | |
| name: Notify repair agent | |
| needs: [prepare, desktop, mac_manifest, checksums] | |
| if: failure() && github.event.inputs.notify_agent != 'false' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| permissions: | |
| actions: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| sparse-checkout: scripts/fork-notify-agent.sh | |
| sparse-checkout-cone-mode: false | |
| - name: Call agent webhook | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| WEBHOOK_URL: ${{ secrets.T3_AGENT_WEBHOOK_URL }} | |
| CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }} | |
| CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }} | |
| NEEDS_JSON: ${{ toJSON(needs) }} | |
| run: scripts/fork-notify-agent.sh |