From f5b297cfdb889f61cee7ca8085cdc7324484bb6d Mon Sep 17 00:00:00 2001 From: Leonid Shevtsov Date: Thu, 6 Aug 2026 07:56:55 +0300 Subject: [PATCH] chore: publish mailtrap-local to NUR via goreleaser Add a nix publisher so each tag opens a PR in mailtrap/mailtrap-nur, and fail the release job if the expected Homebrew or NUR package PR is missing (goreleaser soft-fails those errors). Co-authored-by: Cursor --- .github/workflows/release.yml | 38 +++++++++++++++++++++++++++++++++-- .goreleaser.yaml | 34 +++++++++++++++++++++++++++---- README.md | 17 +++++++++++++--- 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 94125cc..b1f1cd5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,8 @@ name: release # 3. Publishes a GitHub release with .tar.gz archives + checksums. # 4. Pushes images to docker.io/mailtrap/mailtrap-local + ghcr.io/mailtrap/mailtrap-local. # 5. Opens a PR in mailtrap/homebrew-local updating Formula/mailtrap-local.rb. +# 6. Opens a PR in mailtrap/mailtrap-nur updating pkgs/mailtrap-local/default.nix. +# 7. Verifies Homebrew + NUR PRs exist (goreleaser soft-fails those errors). on: push: @@ -19,6 +21,7 @@ permissions: jobs: release: + environment: release runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -54,12 +57,43 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + # nix-hash required by goreleaser's `nix:` publisher (NUR PR). + - uses: cachix/install-nix-action@v31 + - name: Run goreleaser uses: goreleaser/goreleaser-action@v7 with: version: latest args: release --clean env: - # Default token covers this repo. Tap updates need HOMEBREW_TAP_TOKEN. + # Default token covers this repo. Cross-repo publishes (homebrew-local, mailtrap-nur, …) + # need GORELEASER_TOKEN with write to those repos. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + GORELEASER_TOKEN: ${{ secrets.GORELEASER_TOKEN }} + + # Goreleaser package-manager pipes (brew, nix, …) ContinueOnError=true, so + # PR open failures only log. Fail loud if expected cross-repo PRs missing. + # Head branch defaults to {{ .ProjectName }}-{{ .Version }} when + # pull_request.enabled is true. + - name: Verify Homebrew and NUR package PRs opened + env: + GH_TOKEN: ${{ secrets.GORELEASER_TOKEN }} + run: | + set -euo pipefail + version="${GITHUB_REF_NAME#v}" + head="mailtrap-local-${version}" + failed=0 + for repo in mailtrap/homebrew-local mailtrap/mailtrap-nur; do + url=$(gh pr list -R "$repo" \ + --head "$head" \ + --state all \ + --json url \ + --jq '.[0].url // empty') + if [ -z "$url" ]; then + echo "::error::Expected PR with head '${head}' not found in ${repo} (goreleaser may have soft-failed)" + failed=1 + else + echo "${repo} PR found: $url" + fi + done + exit "$failed" diff --git a/.goreleaser.yaml b/.goreleaser.yaml index d707200..65aab4d 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -3,12 +3,13 @@ # Channels published: # 1. GitHub Releases — .tar.gz binary archives (darwin/linux × amd64/arm64) # 2. Homebrew tap — PR updating Formula/mailtrap-local.rb in mailtrap/homebrew-local -# 3. Docker Hub — mailtrap/mailtrap-local:{version,latest} -# 4. GHCR — ghcr.io/mailtrap/mailtrap-local:{version,latest} +# 3. NUR — PR updating pkgs/mailtrap-local/default.nix in mailtrap/mailtrap-nur +# 4. Docker Hub — mailtrap/mailtrap-local:{version,latest} +# 5. GHCR — ghcr.io/mailtrap/mailtrap-local:{version,latest} # # The releaser needs: # - GITHUB_TOKEN with write to mailtrap/mailtrap-local -# - HOMEBREW_TAP_TOKEN for the homebrew-local formula PR +# - GORELEASER_TOKEN for cross-repo publishes (homebrew-local, mailtrap-nur, …) # - DOCKERHUB_USERNAME + DOCKERHUB_TOKEN for the docker push # - ghcr.io auth via GITHUB_TOKEN (free, no extra secret) # @@ -112,6 +113,7 @@ release: --- **Channels:** - Homebrew: `brew install mailtrap/local/mailtrap-local` + - Nix: `nix run github:mailtrap/mailtrap-nur#mailtrap-local` - Docker: `docker run -p 3535:3535 -p 3550:3550 mailtrap/mailtrap-local:{{ .Tag }}` - Binary: download the platform tarball above and `tar xzf`. @@ -130,7 +132,7 @@ brews: owner: mailtrap name: homebrew-local # Cross-repo token; GITHUB_TOKEN cannot write the tap. - token: "{{ .Env.HOMEBREW_TAP_TOKEN }}" + token: "{{ .Env.GORELEASER_TOKEN }}" pull_request: enabled: true directory: Formula @@ -153,6 +155,30 @@ brews: test: | assert_match version.to_s, shell_output("#{bin}/mailtrap-local --version") +# --------------------------------------------------------------------- +# NUR — on every tagged release, open a PR that updates +# pkgs/mailtrap-local/default.nix in mailtrap/mailtrap-nur (same pattern +# as the Homebrew tap). Needs nix-hash on PATH (see release.yml). +# --------------------------------------------------------------------- +nix: + - name: mailtrap-local + ids: [mailtrap-local] + repository: + owner: mailtrap + name: mailtrap-nur + token: "{{ .Env.GORELEASER_TOKEN }}" + pull_request: + enabled: true + path: pkgs/mailtrap-local/default.nix + homepage: "https://github.com/mailtrap/mailtrap-local" + description: "Local email sandbox + catcher: SMTP + Web UI + JSON API in one binary" + license: mit + main_program: mailtrap-local + # Default install copies the binary; add sendmail-replacement symlink + # (same as Homebrew Formula). + extra_install: | + ln -s mailtrap-local $out/bin/mailtrap-sendmail + # --------------------------------------------------------------------- # Docker images — built once per arch, then combined into a manifest # list so `docker pull` picks the right binary automatically. diff --git a/README.md b/README.md index 04c93a6..7d8a0e3 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Browse caught messages, preview rendered HTML, and inspect client compatibility | Storage | SQLite via [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite) (pure Go — no CGo) | | Web UI | React 19 + tailwindcss + Radix UI + Vite, embedded into the binary at build time via `//go:embed` | | Realtime | Plain WebSocket (`/cable`) | -| Distribution | `brew install`, `docker run`, single static binary (macOS + Linux; Windows not yet) | +| Distribution | `brew install`, `nix run`, `docker run`, single static binary (macOS + Linux; Windows not yet) | ## Quick start @@ -53,6 +53,17 @@ brew services start mailtrap-local Then open **http://127.0.0.1:3550** and point your app's SMTP client at **127.0.0.1:3535**. +### Nix + +```sh +nix run github:mailtrap/mailtrap-nur#mailtrap-local +# or +nix profile add github:mailtrap/mailtrap-nur#mailtrap-local +# Nix older than 2.25: nix profile install … +``` + +Package lives in [`mailtrap/mailtrap-nur`](https://github.com/mailtrap/mailtrap-nur); goreleaser opens a PR there on every tag (same flow as the Homebrew tap). + ### Docker ```sh @@ -144,11 +155,11 @@ mailtrap-local/ ├── scripts/build.sh # frontend build + go build → bin/mailtrap-local ├── Dockerfile # multi-stage source build (Node → Go → distroless) ├── Dockerfile.goreleaser # release-time wrapper (goreleaser-built binary → distroless) -├── .goreleaser.yaml # release pipeline: binaries + Homebrew tap + Docker Hub + GHCR +├── .goreleaser.yaml # release pipeline: binaries + Homebrew + NUR + Docker Hub + GHCR └── .github/workflows/ # ci.yml + release.yml ``` -The Homebrew formula lives in a separate tap repo (`mailtrap/homebrew-local`); `goreleaser` opens a formula PR there on every tag. +The Homebrew formula lives in `mailtrap/homebrew-local`; the Nix package lives in `mailtrap/mailtrap-nur`. `goreleaser` opens a PR in each on every tag. ## API