Skip to content

Release

Release #65

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
tag:
description: "Tag to release (e.g. v2.10.3). Must already exist."
required: true
type: string
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
permissions:
contents: write
packages: write
jobs:
# Run tests once before building
test:
runs-on: ubuntu-latest
name: Test
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Test with race detection
run: go test -race ./...
build-standard:
needs: test
strategy:
matrix:
include:
- goos: linux
goarch: amd64
binary: onwatch-linux-amd64
- goos: linux
goarch: arm64
binary: onwatch-linux-arm64
- goos: windows
goarch: amd64
binary: onwatch-windows-amd64.exe
runs-on: ubuntu-latest
name: Build ${{ matrix.goos }}/${{ matrix.goarch }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Read version
id: version
run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT"
- name: Build
env:
CGO_ENABLED: "0"
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: |
go build -ldflags="-s -w -X main.version=${{ steps.version.outputs.version }}" \
-o ${{ matrix.binary }} .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.binary }}
path: ${{ matrix.binary }}
build-macos-amd64:
needs: test
runs-on: macos-15
name: Build macOS amd64
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Read version
id: version
run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT"
- name: Build macOS artifact
run: |
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build \
-tags menubar,desktop,production \
-ldflags="-s -w -X main.version=${{ steps.version.outputs.version }}" \
-o onwatch-darwin-amd64 .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: onwatch-darwin-amd64
path: onwatch-darwin-amd64
build-macos-arm64:
needs: test
runs-on: macos-15
name: Build macOS arm64
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Read version
id: version
run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT"
- name: Build macOS artifact
run: |
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build \
-tags menubar,desktop,production \
-ldflags="-s -w -X main.version=${{ steps.version.outputs.version }}" \
-o onwatch-darwin-arm64 .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: onwatch-darwin-arm64
path: onwatch-darwin-arm64
# Create GitHub release with all binaries
release:
needs: [build-standard, build-macos-amd64, build-macos-arm64]
runs-on: ubuntu-latest
name: Release
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: List binaries
run: ls -lh dist/
- name: Copy additional release files
run: |
cp install.bat dist/
cp Dockerfile dist/
cp docker-compose.yml dist/
cp .env.docker.example dist/
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Delete existing release if present (allows re-running workflow)
gh release delete "${{ inputs.tag }}" --yes 2>/dev/null || true
gh release create "${{ inputs.tag }}" dist/* \
--title "onWatch ${{ inputs.tag }}" \
--generate-notes
# Build and push Docker image (after release is created)
docker:
needs: release
runs-on: ubuntu-latest
name: Docker
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}
persist-credentials: false
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Read version
id: version
run: |
if [ ! -f VERSION ] || [ ! -s VERSION ]; then
echo "ERROR: VERSION file is missing or empty"
exit 1
fi
echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT"
# Build Alpine variant first (issue #34)
- name: Extract metadata for Alpine variant
id: meta-alpine
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}-alpine,value=${{ inputs.tag }}
type=semver,pattern={{major}}.{{minor}}-alpine,value=${{ inputs.tag }}
type=raw,value=alpine
- name: Build and push Docker image (Alpine with shell)
uses: docker/build-push-action@v6
with:
context: .
target: runtime-shell
platforms: linux/arm64,linux/amd64
push: true
tags: ${{ steps.meta-alpine.outputs.tags }}
labels: ${{ steps.meta-alpine.outputs.labels }}
build-args: |
VERSION=${{ steps.version.outputs.version }}
BUILD_TIME=${{ github.event.head_commit.timestamp }}
cache-from: type=gha,scope=alpine
cache-to: type=gha,mode=max,scope=alpine
# Build distroless last so its `latest` tag is the final push
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}},value=${{ inputs.tag }}
type=semver,pattern={{major}}.{{minor}},value=${{ inputs.tag }}
type=semver,pattern={{major}},value=${{ inputs.tag }}
type=raw,value=latest
- name: Build and push Docker image (distroless)
uses: docker/build-push-action@v6
with:
context: .
target: runtime
platforms: linux/arm64,linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ steps.version.outputs.version }}
BUILD_TIME=${{ github.event.head_commit.timestamp }}
cache-from: type=gha,scope=distroless
cache-to: type=gha,mode=max,scope=distroless
# Update Homebrew formula with new version and checksums
homebrew:
needs: [release, docker]
runs-on: ubuntu-latest
name: Update Homebrew Formula
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}
- name: Read version
id: version
run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT"
- name: Download release binaries
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p /tmp/bins
TAG="${{ inputs.tag }}"
for bin in darwin-arm64 darwin-amd64 linux-arm64 linux-amd64; do
gh release download "$TAG" -p "onwatch-${bin}" -D /tmp/bins/
done
- name: Compute checksums and update formula
env:
TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
run: |
VERSION="${{ steps.version.outputs.version }}"
SHA_DARWIN_ARM64=$(sha256sum /tmp/bins/onwatch-darwin-arm64 | cut -d' ' -f1)
SHA_DARWIN_AMD64=$(sha256sum /tmp/bins/onwatch-darwin-amd64 | cut -d' ' -f1)
SHA_LINUX_ARM64=$(sha256sum /tmp/bins/onwatch-linux-arm64 | cut -d' ' -f1)
SHA_LINUX_AMD64=$(sha256sum /tmp/bins/onwatch-linux-amd64 | cut -d' ' -f1)
git clone "https://x-access-token:${TAP_TOKEN}@github.com/onllm-dev/homebrew-tap.git" /tmp/tap
cd /tmp/tap
mkdir -p Formula
cat > Formula/onwatch.rb << 'FORMULA'
class Onwatch < Formula
desc "CLI tool for tracking AI API quotas across multiple providers"
homepage "https://github.com/onllm-dev/onwatch"
version "VERSION_PLACEHOLDER"
license "GPL-3.0-only"
on_macos do
if Hardware::CPU.arm?
url "https://github.com/onllm-dev/onwatch/releases/download/vVERSION_PLACEHOLDER/onwatch-darwin-arm64"
sha256 "SHA_DARWIN_ARM64_PLACEHOLDER"
else
url "https://github.com/onllm-dev/onwatch/releases/download/vVERSION_PLACEHOLDER/onwatch-darwin-amd64"
sha256 "SHA_DARWIN_AMD64_PLACEHOLDER"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/onllm-dev/onwatch/releases/download/vVERSION_PLACEHOLDER/onwatch-linux-arm64"
sha256 "SHA_LINUX_ARM64_PLACEHOLDER"
else
url "https://github.com/onllm-dev/onwatch/releases/download/vVERSION_PLACEHOLDER/onwatch-linux-amd64"
sha256 "SHA_LINUX_AMD64_PLACEHOLDER"
end
end
def install
bin.install Dir["onwatch-*"].first => "onwatch"
end
def caveats
<<~EOS
To configure onWatch, run the interactive setup wizard:
onwatch setup
This will guide you through configuring API keys, dashboard
credentials, and polling settings. Configuration is stored
in ~/.onwatch/.env
After setup, start onWatch with:
onwatch
EOS
end
test do
assert_match version.to_s, shell_output("#{bin}/onwatch --version")
end
end
FORMULA
# Replace placeholders with actual values
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" Formula/onwatch.rb
sed -i "s/SHA_DARWIN_ARM64_PLACEHOLDER/${SHA_DARWIN_ARM64}/g" Formula/onwatch.rb
sed -i "s/SHA_DARWIN_AMD64_PLACEHOLDER/${SHA_DARWIN_AMD64}/g" Formula/onwatch.rb
sed -i "s/SHA_LINUX_ARM64_PLACEHOLDER/${SHA_LINUX_ARM64}/g" Formula/onwatch.rb
sed -i "s/SHA_LINUX_AMD64_PLACEHOLDER/${SHA_LINUX_AMD64}/g" Formula/onwatch.rb
# Remove leading whitespace from heredoc (cat << 'FORMULA' adds indentation)
sed -i 's/^ //' Formula/onwatch.rb
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/onwatch.rb
git commit -m "onwatch ${VERSION}"
git push