Hosted release #1
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: Hosted release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| source_ref: | |
| description: Exact branch, tag, or commit to build | |
| required: true | |
| default: main | |
| type: string | |
| publish: | |
| description: Publish the artifacts as GitHub Latest (main only) | |
| required: true | |
| default: false | |
| type: boolean | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: hosted-release-${{ inputs.source_ref }} | |
| cancel-in-progress: false | |
| env: | |
| NODE_VERSION: "22" | |
| PUPPETEER_SKIP_DOWNLOAD: "1" | |
| jobs: | |
| preflight: | |
| name: Pin source identity | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| outputs: | |
| commit: ${{ steps.source.outputs.commit }} | |
| version: ${{ steps.source.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | |
| with: | |
| ref: ${{ inputs.source_ref }} | |
| fetch-depth: 0 | |
| - id: source | |
| name: Validate source and publication intent | |
| env: | |
| PUBLISH: ${{ inputs.publish }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| commit="$(git rev-parse HEAD)" | |
| version="$(node -p 'require("./package.json").version')" | |
| [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] | |
| git diff --exit-code --check | |
| git fetch origin main --no-tags | |
| [[ "$commit" == "$(git rev-parse origin/main)" ]] || { | |
| echo "Hosted release builds are restricted to the exact origin/main commit" >&2; exit 1; | |
| } | |
| if [[ "$PUBLISH" == "true" ]]; then | |
| if git ls-remote --exit-code origin "refs/tags/v$version" >/dev/null 2>&1; then | |
| echo "Tag v$version already exists; refusing to replace a release" >&2; exit 1 | |
| fi | |
| fi | |
| echo "commit=$commit" >> "$GITHUB_OUTPUT" | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| printf 'Building 1Helm %s from %s\n' "$version" "$commit" | |
| ci: | |
| name: Full CI | |
| needs: preflight | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | |
| with: | |
| ref: ${{ needs.preflight.outputs.commit }} | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: npm | |
| - run: npm ci | |
| - run: npm run ci | |
| linux: | |
| name: Linux package | |
| needs: preflight | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 60 | |
| steps: | |
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | |
| with: | |
| ref: ${{ needs.preflight.outputs.commit }} | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: npm | |
| - name: Install build dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y podman | |
| npm ci | |
| - name: Build sealed channel image and Linux package | |
| run: | | |
| npm run package:channel-image | |
| npm run package:linux | |
| sha256sum -c "dist/1Helm-${{ needs.preflight.outputs.version }}-linux-node.tgz.sha256" | |
| - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: linux-${{ needs.preflight.outputs.commit }} | |
| path: | | |
| dist/1Helm-${{ needs.preflight.outputs.version }}-linux-node.tgz | |
| dist/1Helm-${{ needs.preflight.outputs.version }}-linux-node.tgz.sha256 | |
| if-no-files-found: error | |
| retention-days: 14 | |
| mac: | |
| name: Signed and notarized Apple Silicon package | |
| needs: preflight | |
| runs-on: macos-15 | |
| timeout-minutes: 90 | |
| environment: release | |
| steps: | |
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | |
| with: | |
| ref: ${{ needs.preflight.outputs.commit }} | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Create temporary signing keychain | |
| id: signing | |
| shell: bash | |
| env: | |
| APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| APPLE_API_PRIVATE_KEY: ${{ secrets.APPLE_API_PRIVATE_KEY }} | |
| APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} | |
| APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }} | |
| run: | | |
| set -euo pipefail | |
| keychain="$RUNNER_TEMP/1helm-signing.keychain-db" | |
| keychain_password="$(openssl rand -hex 24)" | |
| certificate="$RUNNER_TEMP/developer-id.p12" | |
| api_key="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8" | |
| printf '%s' "$APPLE_CERTIFICATE_P12_BASE64" | base64 --decode > "$certificate" | |
| printf '%s' "$APPLE_API_PRIVATE_KEY" > "$api_key" | |
| chmod 600 "$certificate" "$api_key" | |
| security create-keychain -p "$keychain_password" "$keychain" | |
| security set-keychain-settings -lut 7200 "$keychain" | |
| security unlock-keychain -p "$keychain_password" "$keychain" | |
| security import "$certificate" -k "$keychain" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security | |
| security set-key-partition-list -S apple-tool:,apple: -s -k "$keychain_password" "$keychain" | |
| security list-keychains -d user -s "$keychain" | |
| profile="1helm-actions-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" | |
| xcrun notarytool store-credentials "$profile" --key "$api_key" --key-id "$APPLE_API_KEY_ID" --issuer "$APPLE_API_ISSUER_ID" --keychain "$keychain" | |
| rm -f "$certificate" "$api_key" | |
| security find-identity -v -p codesigning "$keychain" | grep -F "Developer ID Application" | |
| echo "keychain=$keychain" >> "$GITHUB_OUTPUT" | |
| echo "profile=$profile" >> "$GITHUB_OUTPUT" | |
| - name: Build, sign, notarize, staple, and verify | |
| env: | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| APPLE_NOTARY_PROFILE: ${{ steps.signing.outputs.profile }} | |
| run: npm run package:dmg:release | |
| - name: Record digests | |
| run: | | |
| cd dist | |
| shasum -a 256 "1Helm-${{ needs.preflight.outputs.version }}-arm64.dmg" "1Helm-${{ needs.preflight.outputs.version }}-mac-arm64.zip" > mac-sha256.txt | |
| - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: mac-${{ needs.preflight.outputs.commit }} | |
| path: | | |
| dist/1Helm-${{ needs.preflight.outputs.version }}-arm64.dmg | |
| dist/1Helm-${{ needs.preflight.outputs.version }}-mac-arm64.zip | |
| dist/mac-sha256.txt | |
| if-no-files-found: error | |
| retention-days: 14 | |
| - name: Destroy temporary signing material | |
| if: always() | |
| shell: bash | |
| run: | | |
| security delete-keychain "${{ steps.signing.outputs.keychain }}" 2>/dev/null || true | |
| rm -f "$RUNNER_TEMP/developer-id.p12" "$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8" | |
| acceptance: | |
| name: Artifact identity gate | |
| needs: [preflight, ci, linux, mac] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| pattern: "*-${{ needs.preflight.outputs.commit }}" | |
| path: dist/release | |
| merge-multiple: true | |
| - name: Verify exact package identity | |
| env: | |
| VERSION: ${{ needs.preflight.outputs.version }} | |
| COMMIT: ${{ needs.preflight.outputs.commit }} | |
| run: | | |
| set -euo pipefail | |
| test -s "dist/release/1Helm-$VERSION-linux-node.tgz" | |
| test -s "dist/release/1Helm-$VERSION-arm64.dmg" | |
| test -s "dist/release/1Helm-$VERSION-mac-arm64.zip" | |
| tar -xOf "dist/release/1Helm-$VERSION-linux-node.tgz" "1Helm-$VERSION/resources/source-build.json" > source-build.json | |
| test "$(jq -r .version source-build.json)" = "$VERSION" | |
| test "$(jq -r .commit source-build.json)" = "$COMMIT" | |
| sha256sum "dist/release/1Helm-$VERSION-linux-node.tgz" "dist/release/1Helm-$VERSION-arm64.dmg" "dist/release/1Helm-$VERSION-mac-arm64.zip" | |
| publish: | |
| name: Publish GitHub Latest | |
| if: ${{ inputs.publish }} | |
| needs: [preflight, acceptance] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| environment: release | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | |
| with: | |
| ref: ${{ needs.preflight.outputs.commit }} | |
| - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| pattern: "*-${{ needs.preflight.outputs.commit }}" | |
| path: dist/release | |
| merge-multiple: true | |
| - name: Create immutable release and Stable manifest | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| VERSION: ${{ needs.preflight.outputs.version }} | |
| COMMIT: ${{ needs.preflight.outputs.commit }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| tag="v$VERSION" | |
| cleanup() { gh release delete "$tag" --repo "$GITHUB_REPOSITORY" --cleanup-tag --yes >/dev/null 2>&1 || true; } | |
| trap cleanup ERR | |
| gh release create "$tag" --repo "$GITHUB_REPOSITORY" --target "$COMMIT" --title "1Helm $VERSION" --generate-notes --draft | |
| gh release upload "$tag" --repo "$GITHUB_REPOSITORY" \ | |
| "dist/release/1Helm-$VERSION-linux-node.tgz" \ | |
| "dist/release/1Helm-$VERSION-arm64.dmg" \ | |
| "dist/release/1Helm-$VERSION-mac-arm64.zip" | |
| node scripts/create-stable-manifest.mjs "$VERSION" "$COMMIT" "$GITHUB_RUN_ID" dist/release | |
| gh release upload "$tag" --repo "$GITHUB_REPOSITORY" "dist/release/1Helm-$VERSION-stable.json" | |
| gh release edit "$tag" --repo "$GITHUB_REPOSITORY" --target "$COMMIT" --draft=false --latest | |
| trap - ERR | |
| test "$(gh api "repos/$GITHUB_REPOSITORY/releases/latest" --jq .tag_name)" = "$tag" | |
| - name: Verify public ranged downloads | |
| env: | |
| VERSION: ${{ needs.preflight.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| for name in "1Helm-$VERSION-linux-node.tgz" "1Helm-$VERSION-arm64.dmg" "1Helm-$VERSION-mac-arm64.zip" "1Helm-$VERSION-stable.json"; do | |
| code="$(curl -fsSL --retry 4 -r 0-0 -o /dev/null -w '%{http_code}' "https://github.com/$GITHUB_REPOSITORY/releases/download/v$VERSION/$name")" | |
| test "$code" = 206 | |
| done |