diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 4862c97..96efa42 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -21,6 +21,8 @@ jobs: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true permissions: contents: write + outputs: + tag: ${{ steps.version.outputs.tag }} steps: - uses: actions/checkout@v6 with: @@ -48,3 +50,100 @@ jobs: git config user.email "github-actions[bot]@users.noreply.github.com" git tag ${{ steps.version.outputs.tag }} git push origin ${{ steps.version.outputs.tag }} + + build: + name: Build (${{ matrix.os }}/${{ matrix.arch }}) + runs-on: ubuntu-latest + needs: bump + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + strategy: + matrix: + os: [linux, darwin, windows] + arch: [amd64, arm64] + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ needs.bump.outputs.tag }} + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: '1.24' + - name: Build + run: | + GOOS=${{ matrix.os }} + GOARCH=${{ matrix.arch }} + BINARY_NAME=aperiodic + if [ "$GOOS" = "windows" ]; then BINARY_NAME=aperiodic.exe; fi + mkdir -p dist + GOARCH=$GOARCH GOOS=$GOOS go build -o dist/aperiodic-$GOOS-$GOARCH${BINARY_NAME#aperiodic} ./cmd/aperiodic + - name: Upload artifact + uses: actions/upload-artifact@v6 + with: + name: aperiodic-${{ matrix.os }}-${{ matrix.arch }} + path: dist/ + + release: + name: Publish Release + runs-on: ubuntu-latest + needs: [bump, build] + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + permissions: + contents: write + steps: + - name: Download all artifacts + uses: actions/download-artifact@v8 + with: + path: artifacts + - name: Prepare assets + run: | + mkdir -p release + cp artifacts/aperiodic-*/* release/ + ls -l release/ + # Immutable releases reject asset uploads after publication, so the assets + # have to be attached while the release is still a draft. + - name: Create draft release with assets + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ needs.bump.outputs.tag }} + run: | + gh release create "$TAG" \ + --repo "$GITHUB_REPOSITORY" \ + --draft \ + --title "$TAG" \ + --generate-notes \ + release/* + - name: Verify assets are attached + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ needs.bump.outputs.tag }} + run: | + EXPECTED=$(find release/ -type f | wc -l | tr -d " ") + ACTUAL=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" \ + --json assets --jq '.assets | length') + echo "Expected $EXPECTED assets, found $ACTUAL" + if [ "$EXPECTED" -ne "$ACTUAL" ]; then + echo "::error::Draft release is missing assets; refusing to publish." + exit 1 + fi + - name: Publish release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ needs.bump.outputs.tag }} + run: | + gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --draft=false + + notify-failure: + name: 🚨 PagerDuty Alert + needs: [bump, build, release] + if: >- + always() && + contains(join(needs.*.result, ','), 'failure') + runs-on: ubuntu-latest + steps: + - name: Send PagerDuty alert + uses: Entle/action-pagerduty-alert@1.0.5 + with: + pagerduty-integration-key: '${{ secrets.PAGERDUTY_INTEGRATION_KEY }}' + pagerduty-dedup-key: aperiodic-cli-release-failure diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95d9741..e6149ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,28 +57,3 @@ jobs: with: name: aperiodic-${{ matrix.os }}-${{ matrix.arch }} path: dist/ - - release: - name: Release - runs-on: ubuntu-latest - needs: build - if: startsWith(github.ref, 'refs/tags/v') - env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - permissions: - contents: write - steps: - - name: Download all artifacts - uses: actions/download-artifact@v8 - with: - path: artifacts - - name: Prepare assets - run: | - mkdir -p release - cp artifacts/aperiodic-*/* release/ - - name: Create Release - uses: softprops/action-gh-release@v2 - with: - files: release/* - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/install.sh b/install.sh index c89f4ed..a5aca56 100644 --- a/install.sh +++ b/install.sh @@ -36,15 +36,32 @@ else URL="https://github.com/$REPO/releases/download/$VERSION/aperiodic-$OS-$ARCH$EXT" fi -INSTALL_DIR="/usr/local/bin" +INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}" echo "Detected OS: $OS, Architecture: $ARCH" echo "Downloading Aperiodic CLI from $URL..." TMP_FILE=$(mktemp) -if ! curl -L -o "$TMP_FILE" "$URL"; then - echo "Error: Failed to download binary. Please check the repository and version." - rm -f "$TMP_FILE" +cleanup() { rm -f "$TMP_FILE"; } +trap cleanup EXIT + +# -f makes curl exit non-zero on 404 instead of writing the error body to disk. +if ! curl -fL -o "$TMP_FILE" "$URL"; then + echo "Error: Failed to download binary from $URL" >&2 + echo "The release may not exist, or it may not have assets for $OS-$ARCH." >&2 + echo "See https://github.com/$REPO/releases for available versions." >&2 + exit 1 +fi + +# Guard against a truncated or non-binary download slipping through. +if [ ! -s "$TMP_FILE" ]; then + echo "Error: Downloaded file is empty. Aborting." >&2 + exit 1 +fi + +if head -c 1024 "$TMP_FILE" | LC_ALL=C grep -qi '&2 + echo "URL: $URL" >&2 exit 1 fi @@ -55,5 +72,6 @@ if [ -w "$INSTALL_DIR" ]; then else sudo mv "$TMP_FILE" "$INSTALL_DIR/$BINARY_NAME" fi +trap - EXIT echo "Installation complete. Run 'aperiodic' to get started."