gopin is a CLI tool to pin versions of go install commands in your files for reproducible builds and enhanced security.
Using @latest in go install commands is convenient but problematic:
- No reproducibility: Different runs may install different versions
- Security risk: A malicious version could be installed unknowingly
- CI/CD instability: Team members might use different tool versions
- Debugging difficulty: Hard to reproduce past builds
gopin solves these problems by automatically updating all go install commands to the latest specific semantic versions:
- Pin
@latest: Convert@latestto specific versions (e.g.,@v2.6.2) - Update outdated versions: Update already-pinned versions to the latest (e.g.,
@v1.0.0→@v2.6.2) - Add missing versions: Add version specifiers to commands without them
go install github.com/nnnkkk7/gopin/cmd/gopin@latestgopin versionbrew install nnnkkk7/tap/gopinDownload the pre-built binary for your platform from Releases:
# macOS (Apple Silicon)
curl -L https://github.com/nnnkkk7/gopin/releases/latest/download/gopin_Darwin_arm64.tar.gz | tar xz
sudo mv gopin /usr/local/bin/
# macOS (Intel)
curl -L https://github.com/nnnkkk7/gopin/releases/latest/download/gopin_Darwin_x86_64.tar.gz | tar xz
sudo mv gopin /usr/local/bin/
# Linux (amd64)
curl -L https://github.com/nnnkkk7/gopin/releases/latest/download/gopin_Linux_x86_64.tar.gz | tar xz
sudo mv gopin /usr/local/bin/
# Linux (arm64)
curl -L https://github.com/nnnkkk7/gopin/releases/latest/download/gopin_Linux_arm64.tar.gz | tar xz
sudo mv gopin /usr/local/bin/On macOS, you may see a security warning: "gopin cannot be opened because Apple cannot check it for malicious software"
This happens because the binary is not code-signed. To resolve this:
xattr -d com.apple.quarantine $(which gopin)- Try to run
gopinand the warning will appear - Open System Settings → Privacy & Security
- Scroll down and click "Open Anyway" next to the gopin warning
- Confirm by clicking "Open"
go install github.com/nnnkkk7/gopin/cmd/gopin@latest# Pin all @latest versions in default target files
gopin run
# Preview changes without applying
gopin run --dry-run
# Check for unpinned versions (useful for CI)
gopin check
# List all go install commands
gopin listUpdate all go install commands to latest versions.
# Update all go install to latest
gopin run
# Update specific files
gopin run Makefile .github/workflows/*.yml
# Preview changes without applying
gopin run --dry-run
# Show diff output
gopin run --diff
# Update only specific modules
gopin run --include "golangci-lint.*"
# Exclude specific modules
gopin run --exclude "internal/.*"Note: To keep a specific version, add it to ignore_modules in .gopin.yaml:
ignore_modules:
- name: "github.com/special/tool"
reason: "Must stay at v1.50.0"Check for unpinned go install commands. Exits with code 1 if unpinned versions are found.
# Check for unpinned versions
gopin check
# Fix unpinned versions
gopin check --fixList go install commands in files.
# List all go install commands
gopin list
# List only unpinned commands
gopin list --unpinnedCreate a configuration file.
gopin initCreate .gopin.yaml in your repository root:
version: 1
# Target file patterns
files:
- pattern: ".github/**/*.yml"
- pattern: ".github/**/*.yaml"
- pattern: "Makefile"
- pattern: "makefile"
- pattern: "GNUmakefile"
- pattern: "*.mk"
# Modules to ignore (optional)
ignore_modules:
- name: "github.com/internal/.*"
reason: "internal tools"If no configuration file is found, gopin uses these default patterns:
.github/**/*.yml(all YAML files in .github directory, recursively).github/**/*.yamlMakefilemakefileGNUmakefile*.mk
Before:
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latestAfter:
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6.2Before:
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.0.0After:
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6.2Before:
go install github.com/air-verse/airAfter:
go install github.com/air-verse/air@v1.61.7name: gopin
on:
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23'
- run: go install github.com/nnnkkk7/gopin/cmd/gopin@latest
- run: gopin checkname: gopin
on:
pull_request:
permissions:
contents: write
jobs:
pin:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- uses: actions/setup-go@v5
with:
go-version: '1.23'
- run: go install github.com/nnnkkk7/gopin/cmd/gopin@latest
- run: gopin run
- name: Commit changes
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
git diff --cached --quiet || git commit -m "chore: pin go install versions"
git push- Scan files: Find files matching the configured patterns
- Detect patterns: Find
go install <module>@<version>using regex - Resolve versions: Query
proxy.golang.org(orgo list) for latest version - Rewrite files: Update all versions to the latest (e.g.,
@latest→@v2.6.2,@v1.0.0→@v2.6.2)
gopin uses the Go module proxy (proxy.golang.org) to resolve module versions:
https://proxy.golang.org/<module>/@latest
For private modules or environments without proxy access, gopin falls back to go list -m -json <module>@latest.
| Tool | Scope | Pros | Cons |
|---|---|---|---|
| gopin | go install in any file | Automated, works with existing code | New tool to learn |
| Go 1.24 tool directive | go.mod only | Official feature | Limited to go.mod |
| aqua | Multi-language | Unified version management | Additional setup |
| Manual | Any | Simple | Time-consuming |
go build -o gopin cmd/gopin/main.go# Run all tests
go test ./...
# Run tests with coverage
go test -cover ./...
# Run integration tests
./gopin run --dry-run testdata/Makefilegopin/
├── cmd/gopin/ # Main entry point
├── pkg/
│ ├── detector/ # Pattern detection
│ ├── resolver/ # Version resolution
│ ├── rewriter/ # File rewriting
│ ├── config/ # Configuration
│ └── cli/ # CLI commands
├── testdata/ # Test fixtures
└── README.md
Contributions are welcome! Please feel free to submit a Pull Request.
