From 5f17b398b254b442e160ad89703aa04227e1d13f Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 23 Jan 2026 16:50:29 -0800 Subject: [PATCH 1/2] feat(release): add release automation with goreleaser and release-please - Add .goreleaser.yaml for multi-platform builds (linux/darwin/windows) - Add release-please config for automated semantic versioning - Add release workflow triggered on version tags - Add release-please workflow for automated release PRs - Add version command with ldflags injection for build info - Add release-dry-run job to CI for goreleaser validation - Add empty CHANGELOG.md (managed by release-please) Includes: - Cosign keyless signing for checksums and SBOMs - SBOM generation via Syft - Homebrew tap publishing (meigma/homebrew-tap) - Scoop bucket publishing (meigma/scoop-bucket) Required secrets: RELEASE_PAT, HOMEBREW_TAP_TOKEN, SCOOP_BUCKET_TOKEN Co-Authored-By: Claude Opus 4.5 --- .github/workflows/ci.yml | 24 +++++ .github/workflows/release-please.yml | 19 ++++ .github/workflows/release.yml | 52 ++++++++++ .goreleaser.yaml | 150 +++++++++++++++++++++++++++ .release-please-manifest.json | 1 + CHANGELOG.md | 1 + cmd/version.go | 28 +++++ go.mod | 2 +- go.sum | 10 ++ release-please-config.json | 25 +++++ 10 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release-please.yml create mode 100644 .github/workflows/release.yml create mode 100644 .goreleaser.yaml create mode 100644 .release-please-manifest.json create mode 100644 CHANGELOG.md create mode 100644 cmd/version.go create mode 100644 release-please-config.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d0bad5..88752ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,3 +52,27 @@ jobs: - name: Build run: go build -o blob . + + release-dry-run: + name: Release (Dry Run) + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + with: + go-version: "1.25" + + - name: Install Syft + uses: anchore/sbom-action/download-syft@0b82b0b1a22399a1c542d4d656f70cd903571b5c # v0.21.1 + + - name: Run GoReleaser (snapshot) + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 + with: + distribution: goreleaser + version: "~> v2" + args: release --snapshot --clean --skip=sign diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..ff2a1a3 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,19 @@ +name: Release Please + +on: + push: + branches: + - master + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + name: Release Please + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + token: ${{ secrets.RELEASE_PAT }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e1ad5d6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +name: Release + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + tag: + description: "Tag to release (e.g., v1.0.0)" + required: true + type: string + +permissions: + contents: write + id-token: write # Required for keyless Cosign signing + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + fetch-depth: 0 + ref: ${{ inputs.tag || github.ref }} + + - name: Set up Go + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + with: + go-version: "1.25" + + - name: Install Syft + uses: anchore/sbom-action/download-syft@0b82b0b1a22399a1c542d4d656f70cd903571b5c # v0.21.1 + + - name: Install Cosign + uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 + + - name: Initialize Cosign cache + run: cosign initialize + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 + with: + distribution: goreleaser + version: "~> v2" + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + SCOOP_BUCKET_TOKEN: ${{ secrets.SCOOP_BUCKET_TOKEN }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..4d7f259 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,150 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +version: 2 + +project_name: blob + +before: + hooks: + - go mod tidy + +builds: + - id: blob + main: . + binary: blob + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + ignore: + - goos: windows + goarch: arm64 + ldflags: + - -s -w + - -X github.com/meigma/blob-cli/cmd.version={{.Version}} + - -X github.com/meigma/blob-cli/cmd.commit={{.ShortCommit}} + - -X github.com/meigma/blob-cli/cmd.date={{.Date}} + +archives: + - id: default + formats: + - tar.gz + format_overrides: + - goos: windows + formats: + - zip + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + files: + - LICENSE* + - README* + +checksum: + name_template: "checksums.txt" + algorithm: sha256 + +sboms: + - artifacts: archive + documents: + - "${artifact}.sbom.spdx.json" + cmd: syft + args: + - "${artifact}" + - "--output" + - "spdx-json=${document}" + +signs: + - id: checksum + cmd: flock + signature: "${artifact}.bundle" + args: + - "--exclusive" + - "/tmp/cosign.lock" + - "cosign" + - "sign-blob" + - "--bundle=${signature}" + - "${artifact}" + - "--yes" + artifacts: checksum + + - id: sbom + cmd: flock + signature: "${artifact}.bundle" + args: + - "--exclusive" + - "/tmp/cosign.lock" + - "cosign" + - "sign-blob" + - "--bundle=${signature}" + - "${artifact}" + - "--yes" + artifacts: sbom + +# Changelog is managed by release-please +changelog: + disable: true + +brews: + - repository: + owner: meigma + name: homebrew-tap + token: "{{ .Env.HOMEBREW_TAP_TOKEN }}" + directory: Formula + homepage: "https://github.com/meigma/blob-cli" + description: "A CLI for working with blob archives in OCI registries" + license: "MIT" + skip_upload: auto + commit_msg_template: "brew: update {{ .ProjectName }} to {{ .Tag }}" + test: | + system "#{bin}/blob", "version" + install: | + bin.install "blob" + +scoops: + - repository: + owner: meigma + name: scoop-bucket + token: "{{ .Env.SCOOP_BUCKET_TOKEN }}" + homepage: "https://github.com/meigma/blob-cli" + description: "A CLI for working with blob archives in OCI registries" + license: "MIT" + skip_upload: auto + commit_msg_template: "scoop: update {{ .ProjectName }} to {{ .Tag }}" + +release: + github: + owner: meigma + name: blob-cli + # Append to the release created by release-please + mode: append + draft: false + prerelease: auto + name_template: "v{{.Version}}" + footer: | + ## Verification + + Verify the checksums signature: + ```bash + cosign verify-blob \ + --bundle checksums.txt.bundle \ + --certificate-identity-regexp="https://github.com/meigma/blob-cli" \ + --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ + checksums.txt + ``` + + Verify an SBOM signature: + ```bash + cosign verify-blob \ + --bundle blob_{{ .Version }}_linux_amd64.tar.gz.sbom.spdx.json.bundle \ + --certificate-identity-regexp="https://github.com/meigma/blob-cli" \ + --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ + blob_{{ .Version }}_linux_amd64.tar.gz.sbom.spdx.json + ``` + + Verify artifact integrity: + ```bash + sha256sum -c checksums.txt --ignore-missing + ``` diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..b719d43 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1 @@ +{".":"0.0.0"} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..825c32f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..91f381d --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,28 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// Build information set via ldflags. +var ( + version = "dev" + commit = "none" + date = "unknown" +) + +func init() { + rootCmd.AddCommand(versionCmd) +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print version information", + Run: func(_ *cobra.Command, _ []string) { + fmt.Printf("blob %s\n", version) + fmt.Printf(" commit: %s\n", commit) + fmt.Printf(" built: %s\n", date) + }, +} diff --git a/go.mod b/go.mod index 385837d..69d3937 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/meigma/blob-cli go 1.25.5 require ( + github.com/alecthomas/chroma/v2 v2.23.1 github.com/charmbracelet/bubbles v0.21.0 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/lipgloss v1.1.0 @@ -23,7 +24,6 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/agnivade/levenshtein v1.2.1 // indirect - github.com/alecthomas/chroma/v2 v2.23.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect diff --git a/go.sum b/go.sum index e4feb00..3f182b7 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,12 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY= github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= @@ -84,6 +88,8 @@ github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= @@ -108,6 +114,8 @@ github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7 github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= @@ -305,6 +313,8 @@ github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/in-toto/attestation v1.1.2 h1:MBFn6lsMq6dptQZJBhalXTcWMb/aJy3V+GX3VYj/V1E= diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..6c414df --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "go", + "include-v-in-tag": true, + "include-component-in-tag": false, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "changelog-sections": [ + { "type": "feat", "section": "Features" }, + { "type": "fix", "section": "Bug Fixes" }, + { "type": "perf", "section": "Performance" }, + { "type": "refactor", "section": "Code Refactoring" }, + { "type": "docs", "section": "Documentation", "hidden": true }, + { "type": "style", "section": "Styles", "hidden": true }, + { "type": "chore", "section": "Miscellaneous", "hidden": true }, + { "type": "test", "section": "Tests", "hidden": true }, + { "type": "build", "section": "Build System", "hidden": true }, + { "type": "ci", "section": "Continuous Integration", "hidden": true } + ], + "packages": { + ".": { + "package-name": "blob" + } + } +} From 65d46b59aa1fc8a35781d541dbdf89947a6fddf4 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 23 Jan 2026 16:56:40 -0800 Subject: [PATCH 2/2] fix(ci): address Kusari security findings - Pin googleapis/release-please-action to commit hash (16a9c90) - Add persist-credentials: false to checkout actions in release workflows - Disable Go module caching in release pipelines to prevent cache poisoning Co-Authored-By: Claude Opus 4.5 --- .github/workflows/ci.yml | 2 ++ .github/workflows/release-please.yml | 2 +- .github/workflows/release.yml | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88752ed..71834ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,11 +61,13 @@ jobs: uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 + persist-credentials: false - name: Set up Go uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: "1.25" + cache: false - name: Install Syft uses: anchore/sbom-action/download-syft@0b82b0b1a22399a1c542d4d656f70cd903571b5c # v0.21.1 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index ff2a1a3..64f92db 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -14,6 +14,6 @@ jobs: name: Release Please runs-on: ubuntu-latest steps: - - uses: googleapis/release-please-action@v4 + - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4 with: token: ${{ secrets.RELEASE_PAT }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e1ad5d6..1b25cdd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,11 +25,13 @@ jobs: with: fetch-depth: 0 ref: ${{ inputs.tag || github.ref }} + persist-credentials: false - name: Set up Go uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: "1.25" + cache: false - name: Install Syft uses: anchore/sbom-action/download-syft@0b82b0b1a22399a1c542d4d656f70cd903571b5c # v0.21.1