Add GoReleaser configuration, CI workflows, and contributing guidelines; update .gitignore and Makefile

This commit is contained in:
JY Tan
2025-12-18 16:45:12 -08:00
parent accce04769
commit 55230dd774
9 changed files with 689 additions and 3 deletions

80
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,80 @@
name: Build and test
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Download dependencies
run: go mod download
- name: Build
run: make build-ci
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Download dependencies
run: go mod download
- name: Lint
uses: golangci/golangci-lint-action@v6
with:
install-mode: goinstall
version: v1.64.8
test:
name: Test
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Download dependencies
run: go mod download
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y bubblewrap socat
- name: Test
run: make test-ci

57
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,57 @@
name: Release
on:
push:
tags:
- "v*"
run-name: "Release ${{ github.ref_name }}"
permissions:
contents: read
jobs:
goreleaser:
permissions:
contents: write
id-token: write # Required for SLSA
runs-on: ubuntu-latest
outputs:
hashes: ${{ steps.hash.outputs.hashes }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate hashes for provenance
id: hash
run: |
cd dist
echo "hashes=$(sha256sum * | grep -v checksums.txt | base64 -w0)" >> "$GITHUB_OUTPUT"
provenance:
needs: [goreleaser]
permissions:
actions: read
id-token: write
contents: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
with:
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
upload-assets: true

5
.gitignore vendored
View File

@@ -1,5 +1,7 @@
# Binary (only at root, not cmd/fence or pkg/fence) # Binary (only at root, not cmd/fence or pkg/fence)
/fence /fence
/fence_unix
/fence_darwin
# OS files # OS files
.DS_Store .DS_Store
@@ -15,3 +17,6 @@ Thumbs.db
*.test *.test
coverage.out coverage.out
# GoReleaser
/dist/

41
.golangci.yml Normal file
View File

@@ -0,0 +1,41 @@
run:
timeout: 5m
modules-download-mode: readonly
linters-settings:
gci:
sections:
- standard
- default
- prefix(github.com/Use-Tusk/fence)
gofmt:
simplify: true
goimports:
local-prefixes: github.com/Use-Tusk/fence
gocritic:
disabled-checks:
- singleCaseSwitch
revive:
rules:
- name: exported
disabled: true
linters:
enable-all: false
disable-all: true
enable:
- staticcheck
- errcheck
- gosimple
- govet
- unused
- ineffassign
- gosec
- gocritic
- revive
- gofumpt
- misspell
issues:
exclude-use-default: false

83
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,83 @@
version: 2
before:
hooks:
- go mod tidy
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- darwin
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X main.version={{.Version}}
- -X main.buildTime={{.Date}}
- -X main.gitCommit={{.Commit}}
binary: fence
main: ./cmd/fence
archives:
- formats: [tar.gz]
name_template: >-
{{ .ProjectName }}_
{{- .Version }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
files:
- README.md
- LICENSE
- ARCHITECTURE.md
- CONTRIBUTING.md
checksum:
name_template: "checksums.txt"
changelog:
sort: asc
use: github
format: "{{ .SHA }}: {{ .Message }}{{ with .AuthorUsername }} (@{{ . }}){{ end }}"
filters:
exclude:
- "^test:"
- "^test\\("
- "^chore: update$"
- "^chore: docs$"
- "^docs: update$"
- "^chore: typo$"
- "^chore\\(deps\\): "
- "^(build|ci): "
- "merge conflict"
- Merge pull request
- Merge remote-tracking branch
- Merge branch
- go mod tidy
groups:
- title: "New Features"
regexp: '^.*?feat(\(.+\))??!?:.+$'
order: 100
- title: "Security updates"
regexp: '^.*?sec(\(.+\))??!?:.+$'
order: 150
- title: "Bug fixes"
regexp: '^.*?(fix|refactor)(\(.+\))??!?:.+$'
order: 200
- title: "Documentation updates"
regexp: ^.*?docs?(\(.+\))??!?:.+$
order: 400
- title: Other work
order: 9999
release:
github:
owner: Use-Tusk
name: fence
draft: false
prerelease: auto

165
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,165 @@
# Contributing
Thanks for helping improve `fence`!
If you have any questions, feel free to open an issue.
## Quick start
- Requirements:
- Go 1.25+
- macOS or Linux
- Clone and prepare:
```bash
git clone https://github.com/Use-Tusk/fence
cd fence
make setup # Install deps and lint tools
make build # Build the binary
./fence --help
```
## Dev workflow
Common targets:
| Command | Description |
|---------|-------------|
| `make build` | Build the binary (`./fence`) |
| `make run` | Build and run |
| `make test` | Run tests |
| `make test-ci` | Run tests with coverage |
| `make deps` | Download/tidy modules |
| `make fmt` | Format code with gofumpt |
| `make lint` | Run golangci-lint |
| `make build-ci` | Build with version info (used in CI) |
| `make help` | Show all available targets |
## Code structure
See [ARCHITECTURE.md](ARCHITECTURE.md) for the full project structure and component details.
## Style and conventions
- Keep edits focused and covered by tests where possible.
- Update [ARCHITECTURE.md](ARCHITECTURE.md) when adding features or changing behavior.
- Prefer small, reviewable PRs with a clear rationale.
- Run `make fmt` and `make lint` before committing.
## Testing
```bash
# Run all tests
make test
# Run with verbose output
go test -v ./...
# Run with coverage
make test-ci
```
### Testing on macOS
```bash
# Test blocked network request
./fence curl https://example.com
# Test with allowed domain
echo '{"network":{"allowedDomains":["example.com"]}}' > /tmp/test.json
./fence -s /tmp/test.json curl https://example.com
# Test monitor mode
./fence -m -c "touch /etc/test"
```
### Testing on Linux
Requires `bubblewrap` and `socat`:
```bash
# Ubuntu/Debian
sudo apt install bubblewrap socat
# Test in Colima or VM
./fence curl https://example.com
```
## Troubleshooting
**"command not found" after go install:**
- Add `$GOPATH/bin` to your PATH
- Or use `go env GOPATH` to find the path
**Module issues:**
```bash
go mod tidy # Clean up dependencies
```
**Build cache issues:**
```bash
go clean -cache
go clean -modcache
```
**macOS sandbox issues:**
- Check `log stream --predicate 'eventMessage ENDSWITH "_SBX"'` for violations
- Ensure you're not running as root
**Linux bwrap issues:**
- May need `sudo` or `kernel.unprivileged_userns_clone=1`
- Check that socat and bwrap are installed
## For Maintainers
### Releasing
Releases are automated using [GoReleaser](https://goreleaser.com/) via GitHub Actions.
#### Creating a release
1. Tag the commit with a semantic version:
```bash
git tag v1.0.0
git push origin v1.0.0
```
2. GitHub Actions will automatically:
- Build binaries for all supported platforms
- Create archives with README, LICENSE, and ARCHITECTURE.md
- Generate checksums
- Create a GitHub release with changelog
- Upload all artifacts
#### Supported platforms
The release workflow builds for:
- **Linux**: amd64, arm64
- **macOS (darwin)**: amd64, arm64
#### Building locally for distribution
```bash
# Build for current platform
make build
# Cross-compile
make build-linux
make build-darwin
# With version info (mimics CI builds)
make build-ci
```
To test the GoReleaser configuration locally:
```bash
goreleaser release --snapshot --clean
```

103
Makefile Normal file
View File

@@ -0,0 +1,103 @@
GOCMD=go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
GOMOD=$(GOCMD) mod
BINARY_NAME=fence
BINARY_UNIX=$(BINARY_NAME)_unix
.PHONY: all build build-ci build-linux test test-ci clean deps install-lint-tools setup setup-ci run fmt lint release release-minor help
all: build
build:
@echo "🔨 Building $(BINARY_NAME)..."
$(GOBUILD) -o $(BINARY_NAME) -v ./cmd/fence
build-ci:
@echo "🏗️ CI: Building $(BINARY_NAME) with version info..."
$(eval VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev"))
$(eval BUILD_TIME := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ'))
$(eval GIT_COMMIT := $(shell git rev-parse HEAD 2>/dev/null || echo "unknown"))
$(GOBUILD) -ldflags "-s -w -X main.version=$(VERSION) -X main.buildTime=$(BUILD_TIME) -X main.gitCommit=$(GIT_COMMIT)" -o $(BINARY_NAME) -v ./cmd/fence
test:
@echo "🧪 Running tests..."
$(GOTEST) -v ./...
test-ci:
@echo "🧪 CI: Running tests with coverage..."
$(GOTEST) -v -race -coverprofile=coverage.out ./...
clean:
@echo "🧹 Cleaning..."
$(GOCLEAN)
rm -f $(BINARY_NAME)
rm -f $(BINARY_UNIX)
rm -f coverage.out
deps:
@echo "📦 Downloading dependencies..."
$(GOMOD) download
$(GOMOD) tidy
build-linux:
@echo "🐧 Building for Linux..."
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_UNIX) -v ./cmd/fence
build-darwin:
@echo "🍎 Building for macOS..."
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GOBUILD) -o $(BINARY_NAME)_darwin -v ./cmd/fence
install-lint-tools:
@echo "📦 Installing linting tools..."
go install mvdan.cc/gofumpt@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
@echo "✅ Linting tools installed"
setup: deps install-lint-tools
@echo "✅ Development environment ready"
setup-ci: deps install-lint-tools
@echo "✅ CI environment ready"
run: build
./$(BINARY_NAME)
fmt:
@echo "📝 Formatting code..."
gofumpt -w .
lint:
@echo "🔍 Linting code..."
golangci-lint run --allow-parallel-runners
release:
@echo "🚀 Creating patch release..."
./scripts/release.sh patch
release-minor:
@echo "🚀 Creating minor release..."
./scripts/release.sh minor
help:
@echo "Available targets:"
@echo " all - build (default)"
@echo " build - Build the binary"
@echo " build-ci - Build for CI with version info"
@echo " build-linux - Build for Linux"
@echo " build-darwin - Build for macOS"
@echo " test - Run tests"
@echo " test-ci - Run tests for CI with coverage"
@echo " clean - Clean build artifacts"
@echo " deps - Download dependencies"
@echo " install-lint-tools - Install linting tools"
@echo " setup - Setup development environment"
@echo " setup-ci - Setup CI environment"
@echo " run - Build and run"
@echo " fmt - Format code"
@echo " lint - Lint code"
@echo " release - Create patch release (v0.0.X)"
@echo " release-minor - Create minor release (v0.X.0)"
@echo " help - Show this help"

View File

@@ -139,7 +139,7 @@ func parseViolation(line string) string {
} }
// Filter out noisy violations // Filter out noisy violations
if isNoisyViolation(operation, details) { if isNoisyViolation(details) {
return "" return ""
} }
@@ -170,7 +170,7 @@ func shouldShowViolation(operation string) bool {
} }
// isNoisyViolation returns true if this violation is system noise that should be filtered. // isNoisyViolation returns true if this violation is system noise that should be filtered.
func isNoisyViolation(operation, details string) bool { func isNoisyViolation(details string) bool {
// Filter out TTY/terminal writes (very noisy from any process that prints output) // Filter out TTY/terminal writes (very noisy from any process that prints output)
if strings.HasPrefix(details, "/dev/tty") || if strings.HasPrefix(details, "/dev/tty") ||
strings.HasPrefix(details, "/dev/pts") { strings.HasPrefix(details, "/dev/pts") {
@@ -195,4 +195,3 @@ func isNoisyViolation(operation, details string) bool {
func GetSessionSuffix() string { func GetSessionSuffix() string {
return sessionSuffix // defined in macos.go return sessionSuffix // defined in macos.go
} }

153
scripts/release.sh Executable file
View File

@@ -0,0 +1,153 @@
#!/usr/bin/env bash
set -euo pipefail
# Usage: ./scripts/release.sh [patch|minor]
# Default: patch
BUMP_TYPE="${1:-patch}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
info() { echo -e "${GREEN}[INFO]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
# Validate bump type
if [[ "$BUMP_TYPE" != "patch" && "$BUMP_TYPE" != "minor" ]]; then
error "Invalid bump type: $BUMP_TYPE. Use 'patch' or 'minor' (or no argument for minor)."
fi
info "Bump type: $BUMP_TYPE"
# =============================================================================
# Preflight checks
# =============================================================================
info "Running preflight checks..."
# Check we're in a git repository
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
error "Not in a git repository"
fi
# Check we're on the default branch (main)
DEFAULT_BRANCH="main"
CURRENT_BRANCH=$(git branch --show-current)
if [[ "$CURRENT_BRANCH" != "$DEFAULT_BRANCH" ]]; then
error "Not on default branch. Current: $CURRENT_BRANCH, Expected: $DEFAULT_BRANCH"
fi
# Check for uncommitted changes
if ! git diff --quiet || ! git diff --staged --quiet; then
error "Working directory has uncommitted changes. Commit or stash them first."
fi
# Check for untracked files (warning only)
UNTRACKED=$(git ls-files --others --exclude-standard)
if [[ -n "$UNTRACKED" ]]; then
warn "Untracked files present (continuing anyway):"
echo "$UNTRACKED" | head -5
fi
# Fetch latest from remote
info "Fetching latest from origin..."
git fetch origin "$DEFAULT_BRANCH" --tags
# Check if local branch is up to date with remote
LOCAL_COMMIT=$(git rev-parse HEAD)
REMOTE_COMMIT=$(git rev-parse "origin/$DEFAULT_BRANCH")
if [[ "$LOCAL_COMMIT" != "$REMOTE_COMMIT" ]]; then
error "Local branch is not up to date with origin/$DEFAULT_BRANCH. Run 'git pull' first."
fi
# Check if there are commits since last tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [[ -n "$LAST_TAG" ]]; then
COMMITS_SINCE_TAG=$(git rev-list "$LAST_TAG"..HEAD --count)
if [[ "$COMMITS_SINCE_TAG" -eq 0 ]]; then
error "No commits since last tag ($LAST_TAG). Nothing to release."
fi
info "Commits since $LAST_TAG: $COMMITS_SINCE_TAG"
fi
# Check that tests pass
info "Running tests..."
if ! make test-ci; then
error "Tests failed. Fix them before releasing."
fi
# Check that lint passes
info "Running linter..."
if ! make lint; then
error "Lint failed. Fix issues before releasing."
fi
info "✓ All preflight checks passed"
# =============================================================================
# Calculate new version
# =============================================================================
if [[ -z "$LAST_TAG" ]]; then
# No existing tags, start at v0.1.0
NEW_VERSION="v0.1.0"
info "No existing tags found. Starting at $NEW_VERSION"
else
# Parse current version (strip 'v' prefix)
VERSION="${LAST_TAG#v}"
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
# Validate parsed version
if [[ -z "$MAJOR" || -z "$MINOR" || -z "$PATCH" ]]; then
error "Failed to parse version from tag: $LAST_TAG"
fi
# Increment based on bump type
case "$BUMP_TYPE" in
patch)
PATCH=$((PATCH + 1))
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
esac
NEW_VERSION="v${MAJOR}.${MINOR}.${PATCH}"
info "Version bump: $LAST_TAG$NEW_VERSION"
fi
# =============================================================================
# Confirm and create tag
# =============================================================================
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Ready to release: $NEW_VERSION"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
read -p "Create and push tag $NEW_VERSION? [y/N] " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
info "Aborted."
exit 0
fi
# Create annotated tag
info "Creating tag $NEW_VERSION..."
git tag -a "$NEW_VERSION" -m "Release $NEW_VERSION"
# Push tag to origin
info "Pushing tag to origin..."
git push origin "$NEW_VERSION"
echo ""
info "✓ Released $NEW_VERSION"
info "GitHub Actions will now build and publish the release."
info "Watch progress at: https://github.com/Use-Tusk/fence/actions"