release: reject dirty sources and build artifacts

Fixes #3583
This commit is contained in:
Kristoffer Dalby committed 2026-10-29 15:39:30 +01:00
1 parent c285eff776
commit beb1214926
4 files changed
+143 -1

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
name: Release checks
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
defaults:
run:
shell: nix develop --fallback --command bash -e {0}
jobs:
release-check:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- uses: NixOS/nix-installer-action@62c1943b776c509394b550f3f983adc14e9212d6 # main
- uses: Mic92/hestia@dfed9ced335d28978ba74e513939a10db1f71025 # v3.1.0
- name: Validate release configuration
run: goreleaser check
- name: Test release guards
run: bash tools/release-check-test.sh
- name: Build and verify Headscale release binary
run: goreleaser build --snapshot --clean --single-target
+3 -1
View File
@@ -2,7 +2,6 @@
version: 2
before:
hooks:
- go mod tidy
- go mod vendor
release:
@@ -27,6 +26,9 @@ builds:
- linux_arm64
flags:
- -mod=readonly
hooks:
pre: bash tools/release-check.sh
post: bash tools/release-check.sh "{{ .Path }}"
archives:
- id: golang-cross
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
set -euo pipefail
root=$(cd "$(dirname "$0")/.." && pwd)
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
export GOWORK=off CGO_ENABLED=0 LC_ALL=C
unset GOFLAGS GOOS GOARCH
mkdir -p "$tmp/headscale/cmd/headscale" "$tmp/headscale/tools"
cp "$root/.goreleaser.yml" "$tmp/headscale/"
cp "$root/tools/release-check.sh" "$tmp/headscale/tools/"
cd "$tmp/headscale"
cat > go.mod <<'MOD'
module example.com/headscale
go 1.24.0
// Tidy removes this block, reproducing release-time go.mod edits.
require (
)
MOD
printf 'package main\nfunc main() {}\n' > cmd/headscale/main.go
printf 'dist/\nvendor/\n' > .gitignore
git init -q
git config user.name 'Release check test'
git config user.email 'release-check@example.invalid'
git remote add origin https://github.com/juanfont/headscale.git
git add .
git commit -qm 'Create release fixture'
git tag v0.0.1
check="$PWD/tools/release-check.sh"
expect_failure() {
local label=$1 message=$2
shift 2
if "$@" > "$tmp/failure.log" 2>&1; then
printf 'FAIL: %s unexpectedly succeeded\n' "$label" >&2
exit 1
fi
if ! grep -Fq "$message" "$tmp/failure.log"; then
cat "$tmp/failure.log" >&2
exit 1
fi
printf 'PASS: %s\n' "$label"
}
# Use the real release hooks and every configured target, without publishing.
if ! goreleaser build --snapshot --clean > "$tmp/build.log" 2>&1; then
cat "$tmp/build.log" >&2
exit 1
fi
binaries=(dist/headscale_*/headscale)
bash "$check" "${binaries[@]}"
printf 'PASS: clean release builds, including cross-compiled binaries\n'
git config status.showUntrackedFiles no
printf '// dirty\n' >> cmd/headscale/main.go
expect_failure 'unstaged change' 'working tree is dirty' bash "$check"
go build -o dist/dirty ./cmd/headscale
git add cmd/headscale/main.go
expect_failure 'staged change' 'working tree is dirty' bash "$check"
git restore --staged --worktree cmd/headscale/main.go
expect_failure 'dirty binary after source cleanup' 'lacks clean VCS metadata' bash "$check" dist/dirty
touch untracked
expect_failure 'untracked file despite Git display settings' 'working tree is dirty' bash "$check"
rm untracked
go build -buildvcs=false -o dist/unstamped ./cmd/headscale
expect_failure 'missing VCS metadata' 'lacks clean VCS metadata' bash "$check" dist/unstamped
expect_failure 'missing binary' 'no such file or directory' bash "$check" dist/missing
git commit --allow-empty -qm 'Advance fixture HEAD'
expect_failure 'binary from another commit' 'was not built from HEAD' bash "$check" "${binaries[0]}"
# Reintroducing a mutating preparation hook must abort the actual build pipeline.
sed -i '/ - go mod vendor/a\ - go mod tidy' .goreleaser.yml
git add .goreleaser.yml
git commit -qm 'Reproduce release-time tidy'
expect_failure 'preparation dirties source' 'working tree is dirty' goreleaser build --snapshot --clean --single-target
cd "$tmp"
expect_failure 'Git failure' 'not a git repository' bash "$check"
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
status=$(git status --porcelain --untracked-files=all)
if [[ -n "$status" ]]; then
printf 'release: working tree is dirty\n%s\n' "$status" >&2
exit 1
fi
for binary in "$@"; do
info=$(go version -m "$binary")
if ! grep -Eq '[[:space:]]vcs.modified=false$' <<< "$info"; then
printf 'release: %s lacks clean VCS metadata\n%s\n' "$binary" "$info" >&2
exit 1
fi
revision=$(git rev-parse HEAD)
if ! grep -Eq "[[:space:]]vcs.revision=${revision}$" <<< "$info"; then
printf 'release: %s was not built from HEAD\n%s\n' "$binary" "$info" >&2
exit 1
fi
done