match a download to its job by infohash, not title substring #102
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: Build & Test | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| jobs: | |
| test: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-go@v5 | |
| with: {go-version: '1.25.x', cache: false, check-latest: true} | |
| - name: gofmt | |
| run: | | |
| unformatted=$(gofmt -l .) | |
| if [ -n "$unformatted" ]; then | |
| echo "::error::These files are not gofmt'd. Run 'gofmt -w .' locally:" | |
| echo "$unformatted" | |
| exit 1 | |
| fi | |
| - name: go build | |
| run: go build -o gamarr ./cmd/gamarr/ | |
| - name: go test (race) | |
| run: go test ./... -v -count=1 -race | |
| - name: go vet | |
| run: go vet ./... | |
| - name: staticcheck | |
| run: | | |
| go install honnef.co/go/tools/cmd/staticcheck@latest | |
| staticcheck ./... | |
| - name: govulncheck | |
| run: | | |
| go install golang.org/x/vuln/cmd/govulncheck@latest | |
| govulncheck ./... | |
| - name: smoke test — binary boots and responds | |
| run: | | |
| mkdir -p /tmp/smoke-data | |
| GAMARR_PORT=15001 \ | |
| DATA_DIR=/tmp/smoke-data \ | |
| ./gamarr > /tmp/smoke.log 2>&1 & | |
| SMOKE_PID=$! | |
| for i in $(seq 1 30); do | |
| if curl -sf -o /dev/null http://127.0.0.1:15001/api/health; then | |
| echo "binary up after ${i}*500ms" | |
| break | |
| fi | |
| sleep 0.5 | |
| done | |
| if ! curl -sf -o /dev/null http://127.0.0.1:15001/api/health; then | |
| echo "::error::binary failed to respond within 15s" | |
| cat /tmp/smoke.log | |
| kill $SMOKE_PID 2>/dev/null | |
| exit 1 | |
| fi | |
| # Confirm the new Torznab + OpenAPI surfaces are reachable. | |
| curl -sf http://127.0.0.1:15001/api/openapi.json > /dev/null \ | |
| || { echo "::error::/api/openapi.json failed"; cat /tmp/smoke.log; kill $SMOKE_PID 2>/dev/null; exit 1; } | |
| curl -sf "http://127.0.0.1:15001/torznab/api?t=caps" > /dev/null \ | |
| || { echo "::error::/torznab/api?t=caps failed"; cat /tmp/smoke.log; kill $SMOKE_PID 2>/dev/null; exit 1; } | |
| kill $SMOKE_PID 2>/dev/null | |
| if grep -qiE "panic|fatal" /tmp/smoke.log; then | |
| echo "::error::panic/fatal in boot log:" | |
| cat /tmp/smoke.log | |
| exit 1 | |
| fi | |
| docker: | |
| runs-on: ubuntu-latest | |
| permissions: {contents: read, packages: write} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # QEMU is needed only for the arm64 runtime stage (apk add / adduser) -- | |
| # the Go compile cross-compiles on the native runner. See the Dockerfile. | |
| - uses: docker/setup-qemu-action@v3 | |
| - uses: docker/setup-buildx-action@v3 | |
| # The plain path a user gets with `docker build`, no buildx involved. | |
| - name: docker build (native) | |
| run: docker build -t gamarr:ci . | |
| # Release publishes linux/amd64 + linux/arm64. Build both here so an | |
| # arm64-breaking change fails on the PR instead of at `git tag`. | |
| # cache-to seeds the gha cache that the release job restores from. | |
| # | |
| # Exactly one of the two build steps below runs per event: PRs validate | |
| # the build, pushes to main publish it as :edge. They are separate steps | |
| # rather than one step with a conditional `outputs:` because `--push` and | |
| # `--output type=cacheonly` are mutually exclusive on the same build. | |
| - name: docker build (multi-arch) | |
| if: github.ref != 'refs/heads/main' | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| platforms: linux/amd64,linux/arm64 | |
| outputs: type=cacheonly | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| # Fork PRs get a read-only GITHUB_TOKEN, so login is gated on main too -- | |
| # not just for correctness, but because a failed login would fail the job. | |
| - name: Lowercase owner | |
| if: github.ref == 'refs/heads/main' | |
| id: owner | |
| run: echo "name=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT | |
| - uses: docker/login-action@v3 | |
| if: github.ref == 'refs/heads/main' | |
| with: {registry: ghcr.io, username: "${{ github.actor }}", password: "${{ secrets.GITHUB_TOKEN }}"} | |
| # :edge tracks main. Releases move :latest and add an immutable :vX.Y.Z; | |
| # this gives people who want unreleased fixes a prebuilt image instead of | |
| # a local `docker build`, on both architectures. | |
| - name: docker build (multi-arch) and publish :edge | |
| if: github.ref == 'refs/heads/main' | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| push: true | |
| platforms: linux/amd64,linux/arm64 | |
| # Stamped so `/api/health` on an edge image names the commit it came | |
| # from -- "1.3.0" on a moving tag is not a bug report anyone can act on. | |
| build-args: VERSION=edge-${{ github.sha }} | |
| tags: ghcr.io/${{ steps.owner.outputs.name }}/gamarr:edge | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| # A green multi-arch build does NOT prove the binary is arm64: if the | |
| # TARGETARCH plumbing regressed, the arm64 image would still build, with an | |
| # amd64 binary inside, and only fail at `exec` on a real arm64 host. So | |
| # look at the binary itself. | |
| # | |
| # This exports the built filesystem straight to disk rather than loading an | |
| # image and copying out of a container. `--load` + `docker cp` would route | |
| # the answer through the daemon's image store, and on a containerd-store | |
| # daemon that resolves to the HOST platform -- which silently turns this | |
| # into an amd64-vs-amd64 comparison that can never fail. | |
| - name: arm64 image contains an arm64 binary | |
| run: | | |
| docker buildx build --platform linux/arm64 \ | |
| --cache-from type=gha \ | |
| --output type=local,dest=/tmp/arm64-rootfs . | |
| file /tmp/arm64-rootfs/usr/local/bin/gamarr | |
| file /tmp/arm64-rootfs/usr/local/bin/gamarr | grep -q 'ARM aarch64' || { | |
| echo "::error::/usr/local/bin/gamarr in the linux/arm64 image is not an aarch64 binary" | |
| exit 1 | |
| } | |
| e2e: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-go@v5 | |
| with: {go-version: '1.25.x', cache: false, check-latest: true} | |
| - name: build binary | |
| run: go build -o /tmp/gamarr ./cmd/gamarr/ | |
| - uses: actions/setup-python@v5 | |
| with: {python-version: '3.12'} | |
| - name: install e2e deps | |
| run: | | |
| pip install -r e2e/requirements.txt | |
| playwright install --with-deps chromium | |
| - name: e2e user journey (hermetic — stubbed services, real browser) | |
| env: | |
| GAMARR_E2E_BIN: /tmp/gamarr | |
| run: pytest e2e/ -v | |
| - name: server log on failure | |
| if: failure() | |
| run: cat /tmp/pytest-of-*/pytest-*/data*/gamarr.log 2>/dev/null || true |