cat / goq
clone

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

main
goq / .github / workflows / portal-release.yml
portal-release.yml 10.7K
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 # portal-dist is build.rs output and is not committed; tauri checks106 # frontendDist before cargo runs, so materialize it first.107 cargo check --locked -p portal108 cargo tauri build --ci \109 --target "${{ matrix.target }}" \110 --bundles "${{ matrix.bundles }}" \111 -- --locked112113 - name: Collect preview artifacts and digests114 id: collect115 shell: bash116 env:117 TARGET: ${{ matrix.target }}118 PLATFORM: ${{ matrix.platform }}119 run: |120 digest() {121 if command -v sha256sum >/dev/null 2>&1; then122 sha256sum "$1" | awk '{ print $1 }'123 else124 shasum -a 256 "$1" | awk '{ print $1 }'125 fi126 }127128 version="${RELEASE_TAG#v}"129 staging="$RUNNER_TEMP/portal-preview"130 mkdir -p "$staging"131 bundle_root="target/$TARGET/release/bundle"132 test -d "$bundle_root" || {133 echo "the bundler produced no output at $bundle_root" >&2134 exit 1135 }136137 entries=""138 count=0139 while IFS= read -r artifact; do140 name="$(basename "$artifact")"141 # The manifest is assembled textually, so refuse any name that142 # could alter its structure rather than escaping after the fact.143 printf '%s' "$name" | grep -Eq '^[A-Za-z0-9][A-Za-z0-9._-]*$' || {144 echo "unsafe preview artifact name: $name" >&2145 exit 1146 }147 test ! -e "$staging/$name" || {148 echo "duplicate preview artifact name: $name" >&2149 exit 1150 }151 cp "$artifact" "$staging/$name"152 sha256="$(digest "$staging/$name")"153 printf '%s %s\n' "$sha256" "$name" >"$staging/$name.sha256"154 test "$count" -eq 0 || entries="$entries,"155 entries="$entries{\"name\":\"$name\",\"sha256\":\"$sha256\"}"156 count=$((count + 1))157 done < <(find "$bundle_root" -type f \158 \( -name '*.dmg' -o -name '*.deb' -o -name '*.rpm' \159 -o -name '*.AppImage' -o -name '*.msi' -o -name '*.exe' \) \160 | sort)161162 test "$count" -gt 0 || {163 echo "the bundler produced no recognised preview artifact" >&2164 exit 1165 }166167 cat >"$staging/preview-manifest.json" <<EOF168 {169 "artifacts": [$entries170 ],171 "format": 1,172 "git_commit": "$(git rev-parse HEAD)",173 "platform": "$PLATFORM",174 "product": "portal-client-preview",175 "published": false,176 "release_tag": "$RELEASE_TAG",177 "signed": false,178 "target": "$TARGET",179 "version": "$version"180 }181 EOF182 echo "staging=$staging" >>"$GITHUB_OUTPUT"183184 - name: Attest preview build provenance185 uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.1.0186 with:187 subject-path: ${{ steps.collect.outputs.staging }}/*188189 # Deliberately an Actions artifact, never a release asset. The published190 # release contract stays exactly the macOS arm64 DMG set.191 - name: Retain unpublished preview build192 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2193 with:194 name: portal-preview-${{ inputs.tag }}-${{ matrix.target }}195 path: ${{ steps.collect.outputs.staging }}196 if-no-files-found: error197 retention-days: 14198199 publish:200 name: Build and publish ad-hoc macOS arm64201 needs: policy202 runs-on: macos-26203 timeout-minutes: 90204 environment:205 name: main206 permissions:207 artifact-metadata: write208 attestations: write209 contents: write210 id-token: write211 env:212 RELEASE_TAG: ${{ inputs.tag }}213 steps:214 - name: Check out exact tag215 uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1216 with:217 ref: ${{ inputs.tag }}218 fetch-depth: 0219220 - name: Require workflow execution from the exact tag221 run: |222 test "$GITHUB_REF" = "refs/tags/$RELEASE_TAG"223 test "$GITHUB_SHA" = "$(git rev-parse HEAD)"224225 - name: Verify native arm64 runner226 run: |227 test "$(uname -s)" = Darwin228 test "$(uname -m)" = arm64229 rustup target add aarch64-apple-darwin230231 - name: Install pinned Tauri CLI232 run: cargo install tauri-cli --version 2.11.3 --locked233234 # Portal has no Apple Developer ID. The packaging gate refuses to run in235 # ad-hoc mode if any Apple credential is present in the environment, so236 # this job deliberately provides none.237 - name: Build and verify ad-hoc release assets238 run: |239 release_dir="$RUNNER_TEMP/portal-release"240 mkdir -m 0700 "$release_dir"241 ./scripts/package-macos-client.sh \242 --release-tag "$RELEASE_TAG" \243 --signing adhoc \244 --output-dir "$release_dir"245246 - name: Attest exact Portal build provenance247 uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.1.0248 with:249 subject-path: '${{ runner.temp }}/portal-release/Portal-*'250251 - name: Require the exact Sigil candidate draft252 env:253 GH_TOKEN: ${{ github.token }}254 run: |255 release_json="$(gh release view "$RELEASE_TAG" --json isDraft,tagName,assets)"256 test "$(jq -r '.isDraft' <<<"$release_json")" = true257 test "$(jq -r '.tagName' <<<"$release_json")" = "$RELEASE_TAG"258 target_contract="$(<release/sigil-target-contract.txt)"259 test "$target_contract" = linux-glibc2.17-x86_64260 sigil_asset="sigil-$RELEASE_TAG-$target_contract.tar.gz"261 expected="$(printf '%s\n' "$sigil_asset" "$sigil_asset.sha256" | sort)"262 actual="$(jq -r '.assets[].name' <<<"$release_json" | sort)"263 test "$actual" = "$expected" || {264 echo "draft must contain only the two verified Sigil candidate assets before Portal upload" >&2265 exit 1266 }267268 - name: Upload exact Portal assets269 env:270 GH_TOKEN: ${{ github.token }}271 run: |272 version="${RELEASE_TAG#v}"273 release_dir="$RUNNER_TEMP/portal-release"274 dmg="Portal-${version}-arm64.dmg"275 checksum="${dmg}.sha256"276 manifest="Portal-${version}-arm64.json"277 gh release upload "$RELEASE_TAG" \278 "$release_dir/$dmg" \279 "$release_dir/$checksum" \280 "$release_dir/$manifest"281282 - name: Verify the combined draft remains complete and unpublished283 env:284 GH_TOKEN: ${{ github.token }}285 run: |286 release_json="$(gh release view "$RELEASE_TAG" --json isDraft,tagName,assets)"287 test "$(jq -r '.isDraft' <<<"$release_json")" = true288 test "$(jq -r '.tagName' <<<"$release_json")" = "$RELEASE_TAG"289 version="${RELEASE_TAG#v}"290 target_contract="$(<release/sigil-target-contract.txt)"291 test "$target_contract" = linux-glibc2.17-x86_64292 sigil_asset="sigil-$RELEASE_TAG-$target_contract.tar.gz"293 expected="$(printf '%s\n' \294 "$sigil_asset" \295 "$sigil_asset.sha256" \296 "Portal-${version}-arm64.dmg" \297 "Portal-${version}-arm64.dmg.sha256" \298 "Portal-${version}-arm64.json" | sort)"299 actual="$(jq -r '.assets[].name' <<<"$release_json" | sort)"300 test "$actual" = "$expected" || {301 echo "combined draft does not match the five-asset pre-signature contract" >&2302 exit 1303 }304 asset_names="$RUNNER_TEMP/portal-release-assets.json"305 jq '[.assets[].name | select(startswith("Portal-"))]' \306 <<<"$release_json" >"$asset_names"307 python3 scripts/verify-portal-release.py release-assets \308 --release-tag "$RELEASE_TAG" \309 --names-json "$asset_names"310