From 78481a410099af779993aff76ca20f524f7735ae Mon Sep 17 00:00:00 2001 From: MoeMahhouk Date: Wed, 18 Jun 2025 19:51:37 +0000 Subject: [PATCH 1/3] feat: add reproducible debian packaging with goreleaser & CI workflow --- .github/workflows/checks.yml | 4 +- .github/workflows/release.yaml | 196 +++++++++++--------- .goreleaser.yaml | 224 ++++++++++++++++++++--- Makefile | 148 ++++++++++++++- README.md | 24 +++ packaging/go-template-httpserver.default | 8 + packaging/go-template-httpserver.service | 30 +++ packaging/postinstall.sh | 16 ++ packaging/postremove.sh | 5 + packaging/preremove.sh | 10 + 10 files changed, 552 insertions(+), 113 deletions(-) create mode 100644 packaging/go-template-httpserver.default create mode 100644 packaging/go-template-httpserver.service create mode 100755 packaging/postinstall.sh create mode 100755 packaging/postremove.sh create mode 100755 packaging/preremove.sh diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 4aea049..48c0b0b 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -64,10 +64,10 @@ jobs: run: go install mvdan.cc/gofumpt@v0.4.0 - name: Install staticcheck - run: go install honnef.co/go/tools/cmd/staticcheck@2025.1.1 + run: go install honnef.co/go/tools/cmd/staticcheck@latest - name: Install golangci-lint - run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.8 + run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest # - name: Install NilAway # run: go install go.uber.org/nilaway/cmd/nilaway@v0.0.0-20240821220108-c91e71c080b7 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b11d87e..dc0e2a2 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,12 +4,20 @@ on: push: tags: - 'v*' + workflow_dispatch: + inputs: + snapshot: + description: 'Create snapshot release' + required: false + default: false + type: boolean permissions: contents: write + packages: write jobs: - build_and_release: + release: runs-on: ubuntu-latest steps: - name: Checkout @@ -20,97 +28,115 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ^v1.24 - - - name: Build - run: make build + go-version: '1.24' + cache: true + + - name: Set SOURCE_DATE_EPOCH for reproducible builds + run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> $GITHUB_ENV + + - name: Set up packaging dependencies + run: | + # Install nfpm for packaging + echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | sudo tee /etc/apt/sources.list.d/goreleaser.list + sudo apt-get update + sudo apt-get install -y nfpm + + - name: Import GPG key (if available) + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v6 + if: env.GPG_PRIVATE_KEY != '' + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + env: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - # https://goreleaser.com/cmd/goreleaser_release/ - - name: Run GoReleaser + - name: Run GoReleaser (Release) uses: goreleaser/goreleaser-action@v6 + if: startsWith(github.ref, 'refs/tags/') && !inputs.snapshot with: distribution: goreleaser version: "~> v2" - args: release --config .goreleaser.yaml + args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SOURCE_DATE_EPOCH: ${{ env.SOURCE_DATE_EPOCH }} + GPG_KEY_PATH: ${{ steps.import_gpg.outputs.keyid && format('/tmp/gpg-{0}.key', steps.import_gpg.outputs.keyid) || '' }} + NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} -# docker-image: -# name: Publish Docker Image -# runs-on: ubuntu-latest - -# steps: -# - name: Checkout sources -# uses: actions/checkout@v2 - -# - name: Get tag version -# run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - -# - name: Print version -# run: | -# echo $RELEASE_VERSION -# echo ${{ env.RELEASE_VERSION }} - -# - name: Set up QEMU -# uses: docker/setup-qemu-action@v3 - -# - name: Set up Docker Buildx -# uses: docker/setup-buildx-action@v3 - -# - name: Extract metadata (tags, labels) for Docker -# id: meta -# uses: docker/metadata-action@v5 -# with: -# images: flashbots/go-template -# tags: | -# type=sha -# type=pep440,pattern={{version}} -# type=pep440,pattern={{major}}.{{minor}} -# type=raw,value=latest,enable=${{ !contains(env.RELEASE_VERSION, '-') }} - -# - name: Login to DockerHub -# uses: docker/login-action@v3 -# with: -# username: ${{ secrets.DOCKERHUB_USERNAME }} -# password: ${{ secrets.DOCKERHUB_TOKEN }} - -# - name: Go Build Cache for Docker -# uses: actions/cache@v3 -# with: -# path: go-build-cache -# key: ${{ runner.os }}-go-build-cache-${{ hashFiles('**/go.sum') }} - -# - name: inject go-build-cache into docker -# uses: reproducible-containers/buildkit-cache-dance@v2.1.2 -# with: -# cache-source: go-build-cache - -# - name: Build and push -# uses: docker/build-push-action@v5 -# with: -# context: . -# build-args: | -# VERSION=${{ env.RELEASE_VERSION }} -# push: true -# tags: ${{ steps.meta.outputs.tags }} -# labels: ${{ steps.meta.outputs.labels }} -# platforms: linux/amd64,linux/arm64 -# cache-from: type=gha -# cache-to: type=gha,mode=max + - name: Run GoReleaser (Snapshot) + uses: goreleaser/goreleaser-action@v6 + if: inputs.snapshot || (!startsWith(github.ref, 'refs/tags/')) + with: + distribution: goreleaser + version: "~> v2" + args: release --snapshot --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SOURCE_DATE_EPOCH: ${{ env.SOURCE_DATE_EPOCH }} + GPG_KEY_PATH: ${{ steps.import_gpg.outputs.keyid && format('/tmp/gpg-{0}.key', steps.import_gpg.outputs.keyid) || '' }} + NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} -# github-release: -# runs-on: ubuntu-latest -# steps: -# - name: Checkout sources -# uses: actions/checkout@v2 + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: packages + path: | + dist/*.deb + dist/*.rpm + dist/*.tar.gz + dist/checksums.txt + retention-days: 30 + + - name: Test package installation + run: | + # Test Debian package installation - use AMD64 package for GitHub Actions runner + DEB_FILE=$(find dist -name "*httpserver*_linux_amd64.deb" | head -1) + if [ -n "$DEB_FILE" ]; then + echo "Testing package installation: $DEB_FILE" + sudo dpkg -i "$DEB_FILE" || true + sudo apt-get -f install -y + + # Test if binary is installed and working + /usr/bin/go-template-httpserver --help + + # Test systemd service + sudo systemctl daemon-reload + sudo systemctl is-enabled go-template-httpserver + + echo "✅ Package installation test passed" + else + echo "❌ No AMD64 .deb file found for testing" + exit 1 + fi + + reproducibility-test: + runs-on: ubuntu-latest + needs: release + if: always() + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 -# - name: Create release -# id: create_release -# uses: actions/create-release@v1 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token -# with: -# tag_name: ${{ github.ref }} -# release_name: ${{ github.ref }} -# draft: false -# prerelease: false + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + cache: true + + - name: Set SOURCE_DATE_EPOCH for reproducible builds + run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> $GITHUB_ENV + + - name: Test reproducible builds + run: | + # Install GoReleaser + go install github.com/goreleaser/goreleaser/v2@latest + + # Run reproducibility test + make package-test-reproducible + + echo "✅ Reproducibility test passed" + env: + SOURCE_DATE_EPOCH: ${{ env.SOURCE_DATE_EPOCH }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 14fd69d..97020e6 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,39 +1,213 @@ +# GoReleaser configuration for reproducible Debian packaging version: 2 -# https://goreleaser.com/customization/builds/go/ -builds: - - id: go-template - main: ./cmd/cli/ - binary: cli +# Global environment for reproducible builds +env: + - CGO_ENABLED=0 + - SOURCE_DATE_EPOCH={{ .CommitTimestamp }} - env: - # Force build to be all Go. - - CGO_ENABLED=0 +before: + hooks: + - go mod tidy + - go generate ./... + +builds: + - id: httpserver + binary: go-template-httpserver + main: ./cmd/httpserver/main.go + goos: + - linux + goarch: + - amd64 + - arm64 + # Reproducible build flags flags: - # Remove all file system paths from the executable. - -trimpath + - -buildvcs=false ldflags: - # Sets the value of the symbol. + - -s -w - -X github.com/flashbots/go-template/common.Version={{.Version}} + # Ensure reproducible builds by removing build path and timestamp info + - -buildid="" + env: + - CGO_ENABLED=0 + - GOFLAGS=-mod=readonly + - SOURCE_DATE_EPOCH={{ .CommitTimestamp }} + + - id: cli + binary: go-template-cli + main: ./cmd/cli/main.go goos: - linux - - darwin goarch: - amd64 - arm64 - - riscv64 - ignore: - - goos: darwin - goarch: riscv64 + flags: + - -trimpath + - -buildvcs=false + ldflags: + - -s -w + - -X github.com/flashbots/go-template/common.Version={{.Version}} + - -buildid="" + env: + - CGO_ENABLED=0 + - GOFLAGS=-mod=readonly + - SOURCE_DATE_EPOCH={{ .CommitTimestamp }} + +archives: + - id: default + formats: + - tar.gz + name_template: >- + {{ .ProjectName }}_ + {{- .Version }}_ + {{- .Os }}_ + {{- .Arch }} + {{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }} + # Set consistent file modification times for reproducibility + wrap_in_directory: true + +nfpms: + - id: httpserver + package_name: go-template-httpserver + file_name_template: >- + {{ .PackageName }}_{{ .Version }}_{{ .Os }}_ + {{- .Arch }} + {{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }} + + ids: + - httpserver + + vendor: Flashbots + homepage: https://github.com/flashbots/go-template + maintainer: Flashbots + description: | + Go Template HTTP Server + A template HTTP server for Go projects with metrics, health checks, and graceful shutdown. + license: MIT + + formats: + - deb + - rpm + + # For reproducible builds, set consistent metadata + version_metadata: "" + + # Ensure consistent file permissions + umask: 0o022 + + bindir: /usr/bin + + # Dependencies for the HTTP server + dependencies: + - adduser + - lsb-base (>= 3.0-6) + + contents: + # GoReleaser automatically includes binaries from the referenced build + + # Systemd service file + - src: ./packaging/go-template-httpserver.service + dst: /lib/systemd/system/go-template-httpserver.service + type: config + file_info: + mode: 0644 + mtime: "{{ .CommitDate }}" + + # Default configuration + - src: ./packaging/go-template-httpserver.default + dst: /etc/default/go-template-httpserver + type: config + file_info: + mode: 0644 + mtime: "{{ .CommitDate }}" + + # Create log directory with correct ownership + - dst: /var/log/go-template + type: dir + file_info: + mode: 0755 + owner: go-template + group: go-template + + scripts: + postinstall: ./packaging/postinstall.sh + preremove: ./packaging/preremove.sh + postremove: ./packaging/postremove.sh + + # Set consistent modification time for reproducibility + mtime: "{{ .CommitDate }}" + + - id: cli + package_name: go-template-cli + file_name_template: >- + {{ .PackageName }}_{{ .Version }}_{{ .Os }}_ + {{- .Arch }} + {{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }} + + ids: + - cli + + vendor: Flashbots + homepage: https://github.com/flashbots/go-template + maintainer: Flashbots + description: | + Go Template CLI + A command-line interface for the Go template project. + license: MIT + + formats: + - deb + - rpm + + version_metadata: "" + umask: 0o022 + bindir: /usr/bin + + # Set consistent modification time for reproducibility + mtime: "{{ .CommitDate }}" + +# Reproducible source archives +source: + enabled: true + name_template: "{{ .ProjectName }}-{{ .Version }}" + format: tar.gz + +# Checksums for package verification +checksum: + name_template: "checksums.txt" + algorithm: sha256 + +# Reproducible changelog +changelog: + use: github + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + - "merge conflict" + - Merge pull request + - Merge remote-tracking branch + - Merge branch + groups: + - title: Features + regexp: "^.*feat[(\\w)]*:+.*$" + order: 0 + - title: "Bug fixes" + regexp: "^.*fix[(\\w)]*:+.*$" + order: 1 + - title: Others + order: 999 -# https://goreleaser.com/customization/release/ +# Release configuration release: - draft: true - extra_files: - - glob: ./dist/* - header: | - # 🚀 Features - # 🎄 Enhancements - # 🐞 Notable bug fixes - # 🔖 Version updates - # 🎠 Community \ No newline at end of file + # GoReleaser will auto-detect from git remote origin + mode: append + + # Create release draft + draft: false + + # Mark as prerelease if version contains prerelease identifiers + prerelease: auto + \ No newline at end of file diff --git a/Makefile b/Makefile index 53b0931..4acd10e 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ v: ## Show the version ##@ Build .PHONY: clean -clean: ## Clean the build directory +clean: package-clean ## Clean the build directory rm -rf build/ .PHONY: build-cli @@ -96,3 +96,149 @@ docker-httpserver: ## Build the HTTP server Docker image --file httpserver.dockerfile \ --tag your-project \ . + +##@ Packaging + +.PHONY: package-build +package-build: ## Build packages (without releasing) + @echo "Building packages..." + @goreleaser build --snapshot --clean + @echo "✅ Packages built in dist/" + +.PHONY: package-local +package-local: ## Build packages locally for testing + @echo "Creating local release packages..." + @goreleaser release --snapshot --clean + @echo "✅ Release packages created in dist/" + @echo "📦 Created packages:" + @find dist/ -name "*.deb" -o -name "*.rpm" -o -name "*.tar.gz" | sort + +.PHONY: package-test-reproducible +package-test-reproducible: ## Test reproducible builds + @echo "🔄 Testing reproducible builds..." + @mkdir -p ./test-reproducible + @echo " Building first version (with packages)..." + @if goreleaser release --snapshot --clean >/dev/null 2>&1; then \ + echo " ✅ First build completed"; \ + cp -r ./dist ./test-reproducible/build1; \ + else \ + echo "❌ First build failed"; \ + echo "Running with verbose output:"; \ + goreleaser release --snapshot --clean; \ + rm -rf ./test-reproducible; \ + exit 1; \ + fi + @sleep 2 + @echo " Building second version (with packages)..." + @if goreleaser release --snapshot --clean >/dev/null 2>&1; then \ + echo " ✅ Second build completed"; \ + cp -r ./dist ./test-reproducible/build2; \ + else \ + echo "❌ Second build failed"; \ + echo "Running with verbose output:"; \ + goreleaser release --snapshot --clean; \ + rm -rf ./test-reproducible; \ + exit 1; \ + fi + @echo " Comparing packages and binaries..." + @BUILD1_DEBS=$$(find ./test-reproducible/build1 -name "*.deb" | wc -l); \ + BUILD2_DEBS=$$(find ./test-reproducible/build2 -name "*.deb" | wc -l); \ + BUILD1_BINS=$$(find ./test-reproducible/build1 -type f -name "go-template-*" | wc -l); \ + BUILD2_BINS=$$(find ./test-reproducible/build2 -type f -name "go-template-*" | wc -l); \ + echo " Found $$BUILD1_DEBS .deb packages and $$BUILD1_BINS binaries in first build"; \ + echo " Found $$BUILD2_DEBS .deb packages and $$BUILD2_BINS binaries in second build"; \ + if [ "$$BUILD1_DEBS" -eq 0 ] && [ "$$BUILD1_BINS" -eq 0 ]; then \ + echo "❌ No build artifacts found in first build"; \ + find ./test-reproducible/build1 -type f | head -10; \ + rm -rf ./test-reproducible; \ + exit 1; \ + fi + @echo " Comparing binary checksums..." + @find ./test-reproducible/build1 -type f -name "go-template-*" -exec sha256sum {} \; | sed 's|./test-reproducible/build1/||' | sort > ./test-reproducible/checksums1_bins.txt + @find ./test-reproducible/build2 -type f -name "go-template-*" -exec sha256sum {} \; | sed 's|./test-reproducible/build2/||' | sort > ./test-reproducible/checksums2_bins.txt + @echo " Comparing package checksums..." + @find ./test-reproducible/build1 -name "*.deb" -exec sha256sum {} \; | sed 's|./test-reproducible/build1/||' | sort > ./test-reproducible/checksums1_debs.txt + @find ./test-reproducible/build2 -name "*.deb" -exec sha256sum {} \; | sed 's|./test-reproducible/build2/||' | sort > ./test-reproducible/checksums2_debs.txt + @if diff ./test-reproducible/checksums1_bins.txt ./test-reproducible/checksums2_bins.txt >/dev/null 2>&1; then \ + BINS_MATCH=true; \ + else \ + BINS_MATCH=false; \ + fi; \ + if diff ./test-reproducible/checksums1_debs.txt ./test-reproducible/checksums2_debs.txt >/dev/null 2>&1; then \ + DEBS_MATCH=true; \ + else \ + DEBS_MATCH=false; \ + fi; \ + if [ "$$BINS_MATCH" = "true" ] && [ "$$DEBS_MATCH" = "true" ]; then \ + echo "✅ Both binaries and packages are reproducible!"; \ + else \ + echo "❌ Builds are NOT reproducible!"; \ + if [ "$$BINS_MATCH" = "false" ]; then \ + echo "Binary differences:"; \ + diff ./test-reproducible/checksums1_bins.txt ./test-reproducible/checksums2_bins.txt || true; \ + fi; \ + if [ "$$DEBS_MATCH" = "false" ]; then \ + echo "Package differences:"; \ + diff ./test-reproducible/checksums1_debs.txt ./test-reproducible/checksums2_debs.txt || true; \ + fi; \ + rm -rf ./test-reproducible; \ + exit 1; \ + fi + @rm -rf ./test-reproducible + @echo "🎉 Reproducibility test passed" + +.PHONY: package-install-local +package-install-local: package-local ## Install locally built package + @echo "Installing local package..." + @DEB_FILE=$$(find ./dist -name "*httpserver*.deb" | head -1); \ + if [ -n "$$DEB_FILE" ]; then \ + echo "Installing $$DEB_FILE"; \ + sudo dpkg -i "$$DEB_FILE" || sudo apt-get -f install -y; \ + echo "✅ Package installed successfully"; \ + echo "To start service: sudo systemctl start go-template-httpserver"; \ + echo "To check status: sudo systemctl status go-template-httpserver"; \ + else \ + echo "❌ No .deb file found in ./dist/"; \ + exit 1; \ + fi + +.PHONY: package-uninstall +package-uninstall: ## Uninstall locally installed package + @echo "Uninstalling go-template packages..." + @if dpkg -l | grep -q go-template-httpserver; then \ + sudo systemctl stop go-template-httpserver || true; \ + sudo dpkg -r go-template-httpserver; \ + echo "✅ HTTP server package removed"; \ + fi + @if dpkg -l | grep -q go-template-cli; then \ + sudo dpkg -r go-template-cli; \ + echo "✅ CLI package removed"; \ + fi + +.PHONY: package-info +package-info: ## Show information about built packages + @echo "📦 Package Information" + @echo "=====================" + @for pkg in $$(find dist/ -name "*.deb" 2>/dev/null); do \ + echo "Package: $$pkg"; \ + echo "Size: $$(du -h "$$pkg" | cut -f1)"; \ + echo "Contents:"; \ + dpkg-deb --contents "$$pkg" | head -10; \ + echo "---"; \ + done + +.PHONY: package-clean +package-clean: ## Clean packaging artifacts + @echo "Cleaning packaging artifacts..." + @rm -rf dist/ + @echo "✅ Packaging artifacts cleaned" + +.PHONY: package-release +package-release: ## Create a release (requires git tag) + @if [ "$$(git describe --exact-match --tags HEAD 2>/dev/null)" = "" ]; then \ + echo "❌ No git tag found. Create a tag first: git tag v1.0.0"; \ + exit 1; \ + fi + @echo "🚀 Creating release for tag: $$(git describe --tags)" + @goreleaser release --clean + @echo "✅ Release created successfully" diff --git a/README.md b/README.md index c3b11a6..73d9918 100644 --- a/README.md +++ b/README.md @@ -72,3 +72,27 @@ RUN_DB_TESTS=1 make test # stop the database docker rm -f postgres-test ``` + +## Reproducible Debian Packaging + +This project supports reproducible Debian package builds using GoReleaser. + +**Prerequisites:** +- Go 1.24+ +- GoReleaser + +**Build packages:** + +```bash +# Create local packages +make package-local + +# Test reproducibility +make package-test-reproducible +``` + +**Package contents:** +- `go-template-httpserver`: HTTP server with systemd service +- `go-template-cli`: Command-line interface + +The packages include systemd integration and automatic user/directory setup. \ No newline at end of file diff --git a/packaging/go-template-httpserver.default b/packaging/go-template-httpserver.default new file mode 100644 index 0000000..8bec797 --- /dev/null +++ b/packaging/go-template-httpserver.default @@ -0,0 +1,8 @@ +# Default configuration for go-template-httpserver +LISTEN_ADDR="0.0.0.0:8080" +METRICS_ADDR="0.0.0.0:8090" +LOG_JSON=false +LOG_DEBUG=false +LOG_SERVICE="go-template-httpserver" +PPROF=false +DRAIN_SECONDS=45 diff --git a/packaging/go-template-httpserver.service b/packaging/go-template-httpserver.service new file mode 100644 index 0000000..f438f64 --- /dev/null +++ b/packaging/go-template-httpserver.service @@ -0,0 +1,30 @@ +[Unit] +Description=Go Template HTTP Server +Documentation=https://github.com/flashbots/go-template +After=network.target +Wants=network.target + +[Service] +Type=simple +User=nobody +Group=nogroup +ExecStart=/usr/bin/go-template-httpserver +ExecReload=/bin/kill -HUP $MAINPID +KillMode=process +Restart=on-failure +RestartSec=5s + +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/var/log/go-template + +EnvironmentFile=-/etc/default/go-template-httpserver + +StandardOutput=journal +StandardError=journal +SyslogIdentifier=go-template-httpserver + +[Install] +WantedBy=multi-user.target diff --git a/packaging/postinstall.sh b/packaging/postinstall.sh new file mode 100755 index 0000000..e1cabb1 --- /dev/null +++ b/packaging/postinstall.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e + +if ! id "go-template" &>/dev/null; then + useradd --system --no-create-home --shell /bin/false go-template +fi + +mkdir -p /var/log/go-template +chown go-template:go-template /var/log/go-template +chmod 755 /var/log/go-template + +systemctl daemon-reload +systemctl enable go-template-httpserver.service + +echo "go-template-httpserver installed successfully!" +echo "To start: sudo systemctl start go-template-httpserver" diff --git a/packaging/postremove.sh b/packaging/postremove.sh new file mode 100755 index 0000000..f66021f --- /dev/null +++ b/packaging/postremove.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -e + +systemctl daemon-reload +echo "go-template-httpserver removed." diff --git a/packaging/preremove.sh b/packaging/preremove.sh new file mode 100755 index 0000000..01cd9bd --- /dev/null +++ b/packaging/preremove.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +if systemctl is-active --quiet go-template-httpserver; then + systemctl stop go-template-httpserver +fi + +if systemctl is-enabled --quiet go-template-httpserver; then + systemctl disable go-template-httpserver +fi From 3494cbbf751ef26b95cd19599618a4e9c557c1a2 Mon Sep 17 00:00:00 2001 From: MoeMahhouk Date: Thu, 19 Jun 2025 09:18:25 +0000 Subject: [PATCH 2/3] refinements and addressing feedback --- .github/workflows/checks.yml | 4 +- .github/workflows/release.yaml | 7 ---- .goreleaser.yaml | 2 +- Makefile | 73 +--------------------------------- scripts/test-reproducible.sh | 49 +++++++++++++++++++++++ 5 files changed, 54 insertions(+), 81 deletions(-) create mode 100755 scripts/test-reproducible.sh diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 48c0b0b..3b5f695 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -64,10 +64,10 @@ jobs: run: go install mvdan.cc/gofumpt@v0.4.0 - name: Install staticcheck - run: go install honnef.co/go/tools/cmd/staticcheck@latest + run: go install honnef.co/go/tools/cmd/staticcheck@v0.6.1 - name: Install golangci-lint - run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.8 # - name: Install NilAway # run: go install go.uber.org/nilaway/cmd/nilaway@v0.0.0-20240821220108-c91e71c080b7 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index dc0e2a2..c070528 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,13 +4,6 @@ on: push: tags: - 'v*' - workflow_dispatch: - inputs: - snapshot: - description: 'Create snapshot release' - required: false - default: false - type: boolean permissions: contents: write diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 97020e6..201cb07 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -206,7 +206,7 @@ release: mode: append # Create release draft - draft: false + draft: true # Mark as prerelease if version contains prerelease identifiers prerelease: auto diff --git a/Makefile b/Makefile index 4acd10e..1614bc4 100644 --- a/Makefile +++ b/Makefile @@ -115,77 +115,8 @@ package-local: ## Build packages locally for testing .PHONY: package-test-reproducible package-test-reproducible: ## Test reproducible builds - @echo "🔄 Testing reproducible builds..." - @mkdir -p ./test-reproducible - @echo " Building first version (with packages)..." - @if goreleaser release --snapshot --clean >/dev/null 2>&1; then \ - echo " ✅ First build completed"; \ - cp -r ./dist ./test-reproducible/build1; \ - else \ - echo "❌ First build failed"; \ - echo "Running with verbose output:"; \ - goreleaser release --snapshot --clean; \ - rm -rf ./test-reproducible; \ - exit 1; \ - fi - @sleep 2 - @echo " Building second version (with packages)..." - @if goreleaser release --snapshot --clean >/dev/null 2>&1; then \ - echo " ✅ Second build completed"; \ - cp -r ./dist ./test-reproducible/build2; \ - else \ - echo "❌ Second build failed"; \ - echo "Running with verbose output:"; \ - goreleaser release --snapshot --clean; \ - rm -rf ./test-reproducible; \ - exit 1; \ - fi - @echo " Comparing packages and binaries..." - @BUILD1_DEBS=$$(find ./test-reproducible/build1 -name "*.deb" | wc -l); \ - BUILD2_DEBS=$$(find ./test-reproducible/build2 -name "*.deb" | wc -l); \ - BUILD1_BINS=$$(find ./test-reproducible/build1 -type f -name "go-template-*" | wc -l); \ - BUILD2_BINS=$$(find ./test-reproducible/build2 -type f -name "go-template-*" | wc -l); \ - echo " Found $$BUILD1_DEBS .deb packages and $$BUILD1_BINS binaries in first build"; \ - echo " Found $$BUILD2_DEBS .deb packages and $$BUILD2_BINS binaries in second build"; \ - if [ "$$BUILD1_DEBS" -eq 0 ] && [ "$$BUILD1_BINS" -eq 0 ]; then \ - echo "❌ No build artifacts found in first build"; \ - find ./test-reproducible/build1 -type f | head -10; \ - rm -rf ./test-reproducible; \ - exit 1; \ - fi - @echo " Comparing binary checksums..." - @find ./test-reproducible/build1 -type f -name "go-template-*" -exec sha256sum {} \; | sed 's|./test-reproducible/build1/||' | sort > ./test-reproducible/checksums1_bins.txt - @find ./test-reproducible/build2 -type f -name "go-template-*" -exec sha256sum {} \; | sed 's|./test-reproducible/build2/||' | sort > ./test-reproducible/checksums2_bins.txt - @echo " Comparing package checksums..." - @find ./test-reproducible/build1 -name "*.deb" -exec sha256sum {} \; | sed 's|./test-reproducible/build1/||' | sort > ./test-reproducible/checksums1_debs.txt - @find ./test-reproducible/build2 -name "*.deb" -exec sha256sum {} \; | sed 's|./test-reproducible/build2/||' | sort > ./test-reproducible/checksums2_debs.txt - @if diff ./test-reproducible/checksums1_bins.txt ./test-reproducible/checksums2_bins.txt >/dev/null 2>&1; then \ - BINS_MATCH=true; \ - else \ - BINS_MATCH=false; \ - fi; \ - if diff ./test-reproducible/checksums1_debs.txt ./test-reproducible/checksums2_debs.txt >/dev/null 2>&1; then \ - DEBS_MATCH=true; \ - else \ - DEBS_MATCH=false; \ - fi; \ - if [ "$$BINS_MATCH" = "true" ] && [ "$$DEBS_MATCH" = "true" ]; then \ - echo "✅ Both binaries and packages are reproducible!"; \ - else \ - echo "❌ Builds are NOT reproducible!"; \ - if [ "$$BINS_MATCH" = "false" ]; then \ - echo "Binary differences:"; \ - diff ./test-reproducible/checksums1_bins.txt ./test-reproducible/checksums2_bins.txt || true; \ - fi; \ - if [ "$$DEBS_MATCH" = "false" ]; then \ - echo "Package differences:"; \ - diff ./test-reproducible/checksums1_debs.txt ./test-reproducible/checksums2_debs.txt || true; \ - fi; \ - rm -rf ./test-reproducible; \ - exit 1; \ - fi - @rm -rf ./test-reproducible - @echo "🎉 Reproducibility test passed" + @chmod +x scripts/test-reproducible.sh + @./scripts/test-reproducible.sh .PHONY: package-install-local package-install-local: package-local ## Install locally built package diff --git a/scripts/test-reproducible.sh b/scripts/test-reproducible.sh new file mode 100755 index 0000000..c8dd959 --- /dev/null +++ b/scripts/test-reproducible.sh @@ -0,0 +1,49 @@ +#!/bin/bash +set -e + +echo "🔄 Testing reproducible builds..." + +# Create test directory +mkdir -p ./test-reproducible + +# Build first version +echo " Building first version..." +SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) goreleaser release --snapshot --clean >/dev/null 2>&1 || { + echo "❌ First build failed" + goreleaser release --snapshot --clean + rm -rf ./test-reproducible + exit 1 +} +cp -r ./dist ./test-reproducible/build1 + +# Wait to ensure different build times +sleep 2 + +# Build second version +echo " Building second version..." +SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) goreleaser release --snapshot --clean >/dev/null 2>&1 || { + echo "❌ Second build failed" + goreleaser release --snapshot --clean + rm -rf ./test-reproducible + exit 1 +} +cp -r ./dist ./test-reproducible/build2 + +# Compare builds +echo " Comparing builds..." +find ./test-reproducible/build1 -name "*.deb" -exec sha256sum {} \; | sed 's|./test-reproducible/build1/||' | sort > ./test-reproducible/checksums1.txt +find ./test-reproducible/build2 -name "*.deb" -exec sha256sum {} \; | sed 's|./test-reproducible/build2/||' | sort > ./test-reproducible/checksums2.txt + +if diff ./test-reproducible/checksums1.txt ./test-reproducible/checksums2.txt >/dev/null 2>&1; then + echo "✅ Builds are reproducible!" +else + echo "❌ Builds are NOT reproducible!" + echo "Differences:" + diff ./test-reproducible/checksums1.txt ./test-reproducible/checksums2.txt || true + rm -rf ./test-reproducible + exit 1 +fi + +# Cleanup +rm -rf ./test-reproducible +echo "🎉 Reproducibility test passed" From 760f937fa149b0c50afa2b29e3083485c1ef6a1a Mon Sep 17 00:00:00 2001 From: MoeMahhouk Date: Thu, 19 Jun 2025 12:31:56 +0000 Subject: [PATCH 3/3] make reproducibility mandatory --- .github/workflows/checks.yml | 30 ++++++++++++++ .github/workflows/release.yaml | 76 ++++++++++++++-------------------- 2 files changed, 61 insertions(+), 45 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 3b5f695..a9ae2f6 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -80,3 +80,33 @@ jobs: go mod tidy git update-index -q --really-refresh git diff-index HEAD + + reproducibility-test: + name: Test Reproducible Builds + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + cache: true + + - name: Set SOURCE_DATE_EPOCH for reproducible builds + run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> $GITHUB_ENV + + - name: Test reproducible builds + run: | + # Install GoReleaser + go install github.com/goreleaser/goreleaser/v2@latest + + # Run reproducibility test + make package-test-reproducible + + echo "✅ Reproducibility test passed" + env: + SOURCE_DATE_EPOCH: ${{ env.SOURCE_DATE_EPOCH }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index c070528..c4e69c7 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,8 +10,38 @@ permissions: packages: write jobs: + reproducibility-test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + cache: true + + - name: Set SOURCE_DATE_EPOCH for reproducible builds + run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> $GITHUB_ENV + + - name: Test reproducible builds + run: | + # Install GoReleaser + go install github.com/goreleaser/goreleaser/v2@latest + + # Run reproducibility test + make package-test-reproducible + + echo "✅ Reproducibility test passed" + env: + SOURCE_DATE_EPOCH: ${{ env.SOURCE_DATE_EPOCH }} + release: runs-on: ubuntu-latest + needs: reproducibility-test # Only run if reproducibility test passes steps: - name: Checkout uses: actions/checkout@v4 @@ -46,7 +76,7 @@ jobs: - name: Run GoReleaser (Release) uses: goreleaser/goreleaser-action@v6 - if: startsWith(github.ref, 'refs/tags/') && !inputs.snapshot + if: startsWith(github.ref, 'refs/tags/') with: distribution: goreleaser version: "~> v2" @@ -57,19 +87,6 @@ jobs: GPG_KEY_PATH: ${{ steps.import_gpg.outputs.keyid && format('/tmp/gpg-{0}.key', steps.import_gpg.outputs.keyid) || '' }} NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - - name: Run GoReleaser (Snapshot) - uses: goreleaser/goreleaser-action@v6 - if: inputs.snapshot || (!startsWith(github.ref, 'refs/tags/')) - with: - distribution: goreleaser - version: "~> v2" - args: release --snapshot --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SOURCE_DATE_EPOCH: ${{ env.SOURCE_DATE_EPOCH }} - GPG_KEY_PATH: ${{ steps.import_gpg.outputs.keyid && format('/tmp/gpg-{0}.key', steps.import_gpg.outputs.keyid) || '' }} - NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - - name: Upload artifacts uses: actions/upload-artifact@v4 with: @@ -102,34 +119,3 @@ jobs: echo "❌ No AMD64 .deb file found for testing" exit 1 fi - - reproducibility-test: - runs-on: ubuntu-latest - needs: release - if: always() - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.24' - cache: true - - - name: Set SOURCE_DATE_EPOCH for reproducible builds - run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> $GITHUB_ENV - - - name: Test reproducible builds - run: | - # Install GoReleaser - go install github.com/goreleaser/goreleaser/v2@latest - - # Run reproducibility test - make package-test-reproducible - - echo "✅ Reproducibility test passed" - env: - SOURCE_DATE_EPOCH: ${{ env.SOURCE_DATE_EPOCH }}