cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

fix/website-download-wiring
goq / .github / workflows / portal-release.yml
portal-release.yml 10.5K
1name: Portal release23on:4 workflow_dispatch:5 inputs:6 tag:7 description: Existing immutable vVERSION tag with a Sigil draft release8 required: true9 type: string1011permissions:12 contents: read1314concurrency:15 group: public-release-${{ inputs.tag }}16 cancel-in-progress: false1718jobs:19 policy:20 name: Verify release policy21 runs-on: ubuntu-24.0422 timeout-minutes: 1023 steps:24 - name: Check out exact tag25 uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.126 with:27 ref: ${{ inputs.tag }}28 fetch-depth: 02930 - name: Verify source, tests, and website31 env:32 RELEASE_TAG: ${{ inputs.tag }}33 run: |34 python3 scripts/verify-portal-release.py source \35 --repo-dir "$GITHUB_WORKSPACE" \36 --release-tag "$RELEASE_TAG"37 ./scripts/tests/portal-release.sh38 ./scripts/verify-website.sh3940 preview:41 name: Build unpublished ${{ matrix.target }} preview42 needs: policy43 runs-on: ${{ matrix.os }}44 timeout-minutes: 9045 permissions:46 attestations: write47 contents: read48 id-token: write49 strategy:50 fail-fast: false51 matrix:52 include:53 - os: macos-2654 target: x86_64-apple-darwin55 platform: macos56 bundles: app,dmg57 - os: ubuntu-24.0458 target: x86_64-unknown-linux-gnu59 platform: linux60 bundles: deb,rpm,appimage61 - os: windows-202562 target: x86_64-pc-windows-msvc63 platform: windows64 bundles: msi,nsis65 env:66 RELEASE_TAG: ${{ inputs.tag }}67 steps:68 - name: Check out exact tag69 uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.170 with:71 ref: ${{ inputs.tag }}72 fetch-depth: 07374 - name: Require workflow execution from the exact tag75 shell: bash76 run: |77 test "$GITHUB_REF" = "refs/tags/$RELEASE_TAG"78 test "$GITHUB_SHA" = "$(git rev-parse HEAD)"7980 - name: Install Linux build dependencies81 if: matrix.platform == 'linux'82 run: |83 sudo apt-get update84 sudo apt-get install --yes --no-install-recommends \85 build-essential \86 libayatana-appindicator3-dev \87 librsvg2-dev \88 libssl-dev \89 libudev-dev \90 libwebkit2gtk-4.1-dev \91 libxdo-dev \92 pkg-config9394 - name: Install pinned Tauri CLI95 shell: bash96 run: |97 rustup target add "${{ matrix.target }}"98 cargo install tauri-cli --version 2.11.3 --locked99100 # No optional features: a preview build must differ from the published101 # macOS arm64 release only by target and by the absence of signing.102 - name: Build unsigned preview bundles103 shell: bash104 run: |105 cargo tauri build --locked --ci \106 --target "${{ matrix.target }}" \107 --bundles "${{ matrix.bundles }}"108109 - name: Collect preview artifacts and digests110 id: collect111 shell: bash112 env:113 TARGET: ${{ matrix.target }}114 PLATFORM: ${{ matrix.platform }}115 run: |116 digest() {117 if command -v sha256sum >/dev/null 2>&1; then118 sha256sum "$1" | awk '{ print $1 }'119 else120 shasum -a 256 "$1" | awk '{ print $1 }'121 fi122 }123124 version="${RELEASE_TAG#v}"125 staging="$RUNNER_TEMP/portal-preview"126 mkdir -p "$staging"127 bundle_root="target/$TARGET/release/bundle"128 test -d "$bundle_root" || {129 echo "the bundler produced no output at $bundle_root" >&2130 exit 1131 }132133 entries=""134 count=0135 while IFS= read -r artifact; do136 name="$(basename "$artifact")"137 # The manifest is assembled textually, so refuse any name that138 # could alter its structure rather than escaping after the fact.139 printf '%s' "$name" | grep -Eq '^[A-Za-z0-9][A-Za-z0-9._-]*$' || {140 echo "unsafe preview artifact name: $name" >&2141 exit 1142 }143 test ! -e "$staging/$name" || {144 echo "duplicate preview artifact name: $name" >&2145 exit 1146 }147 cp "$artifact" "$staging/$name"148 sha256="$(digest "$staging/$name")"149 printf '%s %s\n' "$sha256" "$name" >"$staging/$name.sha256"150 test "$count" -eq 0 || entries="$entries,"151 entries="$entries{\"name\":\"$name\",\"sha256\":\"$sha256\"}"152 count=$((count + 1))153 done < <(find "$bundle_root" -type f \154 \( -name '*.dmg' -o -name '*.deb' -o -name '*.rpm' \155 -o -name '*.AppImage' -o -name '*.msi' -o -name '*.exe' \) \156 | sort)157158 test "$count" -gt 0 || {159 echo "the bundler produced no recognised preview artifact" >&2160 exit 1161 }162163 cat >"$staging/preview-manifest.json" <<EOF164 {165 "artifacts": [$entries166 ],167 "format": 1,168 "git_commit": "$(git rev-parse HEAD)",169 "platform": "$PLATFORM",170 "product": "portal-client-preview",171 "published": false,172 "release_tag": "$RELEASE_TAG",173 "signed": false,174 "target": "$TARGET",175 "version": "$version"176 }177 EOF178 echo "staging=$staging" >>"$GITHUB_OUTPUT"179180 - name: Attest preview build provenance181 uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.1.0182 with:183 subject-path: ${{ steps.collect.outputs.staging }}/*184185 # Deliberately an Actions artifact, never a release asset. The published186 # release contract stays exactly the macOS arm64 DMG set.187 - name: Retain unpublished preview build188 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2189 with:190 name: portal-preview-${{ inputs.tag }}-${{ matrix.target }}191 path: ${{ steps.collect.outputs.staging }}192 if-no-files-found: error193 retention-days: 14194195 publish:196 name: Build and publish ad-hoc macOS arm64197 needs: policy198 runs-on: macos-26199 timeout-minutes: 90200 environment:201 name: main202 permissions:203 artifact-metadata: write204 attestations: write205 contents: write206 id-token: write207 env:208 RELEASE_TAG: ${{ inputs.tag }}209 steps:210 - name: Check out exact tag211 uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1212 with:213 ref: ${{ inputs.tag }}214 fetch-depth: 0215216 - name: Require workflow execution from the exact tag217 run: |218 test "$GITHUB_REF" = "refs/tags/$RELEASE_TAG"219 test "$GITHUB_SHA" = "$(git rev-parse HEAD)"220221 - name: Verify native arm64 runner222 run: |223 test "$(uname -s)" = Darwin224 test "$(uname -m)" = arm64225 rustup target add aarch64-apple-darwin226227 - name: Install pinned Tauri CLI228 run: cargo install tauri-cli --version 2.11.3 --locked229230 # Portal has no Apple Developer ID. The packaging gate refuses to run in231 # ad-hoc mode if any Apple credential is present in the environment, so232 # this job deliberately provides none.233 - name: Build and verify ad-hoc release assets234 run: |235 release_dir="$RUNNER_TEMP/portal-release"236 mkdir -m 0700 "$release_dir"237 ./scripts/package-macos-client.sh \238 --release-tag "$RELEASE_TAG" \239 --signing adhoc \240 --output-dir "$release_dir"241242 - name: Attest exact Portal build provenance243 uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.1.0244 with:245 subject-path: '${{ runner.temp }}/portal-release/Portal-*'246247 - name: Require the exact Sigil candidate draft248 env:249 GH_TOKEN: ${{ github.token }}250 run: |251 release_json="$(gh release view "$RELEASE_TAG" --json isDraft,tagName,assets)"252 test "$(jq -r '.isDraft' <<<"$release_json")" = true253 test "$(jq -r '.tagName' <<<"$release_json")" = "$RELEASE_TAG"254 target_contract="$(<release/sigil-target-contract.txt)"255 test "$target_contract" = linux-glibc2.17-x86_64256 sigil_asset="sigil-$RELEASE_TAG-$target_contract.tar.gz"257 expected="$(printf '%s\n' "$sigil_asset" "$sigil_asset.sha256" | sort)"258 actual="$(jq -r '.assets[].name' <<<"$release_json" | sort)"259 test "$actual" = "$expected" || {260 echo "draft must contain only the two verified Sigil candidate assets before Portal upload" >&2261 exit 1262 }263264 - name: Upload exact Portal assets265 env:266 GH_TOKEN: ${{ github.token }}267 run: |268 version="${RELEASE_TAG#v}"269 release_dir="$RUNNER_TEMP/portal-release"270 dmg="Portal-${version}-arm64.dmg"271 checksum="${dmg}.sha256"272 manifest="Portal-${version}-arm64.json"273 gh release upload "$RELEASE_TAG" \274 "$release_dir/$dmg" \275 "$release_dir/$checksum" \276 "$release_dir/$manifest"277278 - name: Verify the combined draft remains complete and unpublished279 env:280 GH_TOKEN: ${{ github.token }}281 run: |282 release_json="$(gh release view "$RELEASE_TAG" --json isDraft,tagName,assets)"283 test "$(jq -r '.isDraft' <<<"$release_json")" = true284 test "$(jq -r '.tagName' <<<"$release_json")" = "$RELEASE_TAG"285 version="${RELEASE_TAG#v}"286 target_contract="$(<release/sigil-target-contract.txt)"287 test "$target_contract" = linux-glibc2.17-x86_64288 sigil_asset="sigil-$RELEASE_TAG-$target_contract.tar.gz"289 expected="$(printf '%s\n' \290 "$sigil_asset" \291 "$sigil_asset.sha256" \292 "Portal-${version}-arm64.dmg" \293 "Portal-${version}-arm64.dmg.sha256" \294 "Portal-${version}-arm64.json" | sort)"295 actual="$(jq -r '.assets[].name' <<<"$release_json" | sort)"296 test "$actual" = "$expected" || {297 echo "combined draft does not match the five-asset pre-signature contract" >&2298 exit 1299 }300 asset_names="$RUNNER_TEMP/portal-release-assets.json"301 jq '[.assets[].name | select(startswith("Portal-"))]' \302 <<<"$release_json" >"$asset_names"303 python3 scripts/verify-portal-release.py release-assets \304 --release-tag "$RELEASE_TAG" \305 --names-json "$asset_names"306