feat(linux): add checksum-verifying release installer #11
Workflow file for this run
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: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| pull_request: | |
| paths: | |
| - ".github/workflows/release.yml" | |
| - ".github/scripts/**" | |
| - "apps/headless/Dockerfile.linux" | |
| - "apps/headless/build.sh" | |
| - "apps/headless/build-linux.sh" | |
| - "apps/headless/install-linux.sh" | |
| - "apps/headless/install.sh" | |
| - "apps/headless/Tests/linux-installer.sh" | |
| - "apps/headless/Package.swift" | |
| - "apps/headless/Sources/HeadlessProtocol/ProductVersion.swift" | |
| - "apps/headless/VERSION" | |
| - "apps/headless/VersionSupport/**" | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: Build and verify release packages without publishing | |
| required: true | |
| default: true | |
| type: boolean | |
| permissions: | |
| contents: read | |
| jobs: | |
| version: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| publish: ${{ steps.version.outputs.publish }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - id: version | |
| shell: bash | |
| env: | |
| DRY_RUN: ${{ inputs.dry_run }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then | |
| version="${GITHUB_REF_NAME#v}" | |
| if ! printf '%s\n' "$version" | grep -Eq -f apps/headless/VersionSupport/semver-pattern.txt; then | |
| echo "Release tag must be a semantic version prefixed with v: $GITHUB_REF_NAME" >&2 | |
| exit 64 | |
| fi | |
| expected="$(tr -d '[:space:]' < apps/headless/VERSION)" | |
| if [[ "$version" != "$expected" ]]; then | |
| echo "Release tag $version does not match apps/headless/VERSION $expected" >&2 | |
| exit 64 | |
| fi | |
| echo "publish=true" >> "$GITHUB_OUTPUT" | |
| else | |
| if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" && "$DRY_RUN" != "true" ]]; then | |
| echo "manual release runs must use dry_run=true; publish by pushing a v* tag" >&2 | |
| exit 64 | |
| fi | |
| version="0.0.${GITHUB_RUN_NUMBER}" | |
| echo "publish=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| macos: | |
| needs: version | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install FFmpeg | |
| run: brew install ffmpeg | |
| - name: Build | |
| env: | |
| HEADLESS_VERSION: ${{ needs.version.outputs.version }} | |
| run: ./apps/headless/build.sh | |
| - name: Unit tests | |
| env: | |
| HEADLESS_VERSION: ${{ needs.version.outputs.version }} | |
| run: ./apps/headless/test.sh | |
| - name: E2E | |
| run: zsh ./apps/headless/Tests/macos-e2e.sh | |
| - name: Package | |
| env: | |
| VERSION: ${{ needs.version.outputs.version }} | |
| run: | | |
| set -eu | |
| cd apps/headless | |
| ditto -c -k --keepParent Headless.app "Headless-${VERSION}-macos.zip" | |
| test -s "Headless-${VERSION}-macos.zip" | |
| unzip -t "Headless-${VERSION}-macos.zip" | |
| unzip -Z1 "Headless-${VERSION}-macos.zip" | grep -qx 'Headless.app/Contents/MacOS/Headless' | |
| unzip -Z1 "Headless-${VERSION}-macos.zip" | grep -qx 'Headless.app/Contents/Resources/bin/headless' | |
| unzip -Z1 "Headless-${VERSION}-macos.zip" | grep -qx 'Headless.app/Contents/Resources/bin/headless-mcp' | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: macos | |
| path: apps/headless/Headless-${{ needs.version.outputs.version }}-macos.zip | |
| linux-amd64: | |
| needs: version | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Build | |
| env: | |
| HEADLESS_LINUX_PLATFORM: linux/amd64 | |
| HEADLESS_VERSION: ${{ needs.version.outputs.version }} | |
| run: ./apps/headless/build-linux.sh | |
| - name: E2E | |
| run: ./apps/headless/Tests/linux-docker.sh | |
| - name: Package | |
| env: | |
| VERSION: ${{ needs.version.outputs.version }} | |
| run: | | |
| set -eu | |
| cp apps/headless/build/headless-linux-amd64.tar.gz \ | |
| "apps/headless/build/headless-${VERSION}-linux-amd64.tar.gz" | |
| archive="apps/headless/build/headless-${VERSION}-linux-amd64.tar.gz" | |
| test -s "$archive" | |
| tar -tzf "$archive" > archive-contents.txt | |
| for expected in headless headless-host headless-mcp install-linux.sh Headless_HeadlessProtocol.resources/AgentRuntime.js; do | |
| grep -qx "$expected" archive-contents.txt | |
| done | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: linux-amd64 | |
| path: apps/headless/build/headless-${{ needs.version.outputs.version }}-linux-amd64.tar.gz | |
| linux-arm64: | |
| needs: version | |
| runs-on: ubuntu-24.04-arm | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Build | |
| env: | |
| HEADLESS_LINUX_PLATFORM: linux/arm64 | |
| HEADLESS_VERSION: ${{ needs.version.outputs.version }} | |
| run: ./apps/headless/build-linux.sh | |
| - name: E2E | |
| run: ./apps/headless/Tests/linux-docker.sh | |
| - name: Package | |
| env: | |
| VERSION: ${{ needs.version.outputs.version }} | |
| run: | | |
| set -eu | |
| cp apps/headless/build/headless-linux-arm64.tar.gz \ | |
| "apps/headless/build/headless-${VERSION}-linux-arm64.tar.gz" | |
| archive="apps/headless/build/headless-${VERSION}-linux-arm64.tar.gz" | |
| test -s "$archive" | |
| tar -tzf "$archive" > archive-contents.txt | |
| for expected in headless headless-host headless-mcp install-linux.sh Headless_HeadlessProtocol.resources/AgentRuntime.js; do | |
| grep -qx "$expected" archive-contents.txt | |
| done | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: linux-arm64 | |
| path: apps/headless/build/headless-${{ needs.version.outputs.version }}-linux-arm64.tar.gz | |
| publish: | |
| needs: [version, macos, linux-amd64, linux-arm64] | |
| if: >- | |
| github.event_name == 'push' && | |
| startsWith(github.ref, 'refs/tags/v') && | |
| needs.version.outputs.publish == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| path: dist | |
| merge-multiple: true | |
| - name: Add Linux bootstrap installer | |
| run: cp apps/headless/install.sh dist/install.sh | |
| - name: Generate and verify checksums | |
| run: ./.github/scripts/release-checksums.sh "${{ needs.version.outputs.version }}" dist | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| name: Headless ${{ needs.version.outputs.version }} | |
| generate_release_notes: true | |
| body: | | |
| ## Downloads | |
| | File | Platform | | |
| | --- | --- | | |
| | `Headless-${{ needs.version.outputs.version }}-macos.zip` | macOS (ad-hoc signed `.app`) | | |
| | `headless-${{ needs.version.outputs.version }}-linux-amd64.tar.gz` | Linux x86_64 | | |
| | `headless-${{ needs.version.outputs.version }}-linux-arm64.tar.gz` | Linux arm64 | | |
| | `install.sh` | Verified Linux bootstrap installer | | |
| ### Notes | |
| - **macOS:** Unzip and run `Headless.app`. Gatekeeper may warn (ad-hoc signature; notarization not included yet). CLI: `Headless.app/Contents/Resources/bin/headless`. | |
| - **Linux:** `curl -fsSL https://github.com/LockInTime/headless/releases/latest/download/install.sh | sh`. The installer verifies the selected tarball against `SHA256SUMS`. Chromium and FFmpeg remain system dependencies. | |
| files: | | |
| dist/Headless-${{ needs.version.outputs.version }}-macos.zip | |
| dist/headless-${{ needs.version.outputs.version }}-linux-amd64.tar.gz | |
| dist/headless-${{ needs.version.outputs.version }}-linux-arm64.tar.gz | |
| dist/SHA256SUMS | |
| dist/install.sh | |
| fail_on_unmatched_files: true |