diff --git a/.github/workflows/docker-reproducible.yml b/.github/workflows/docker-reproducible.yml index f3479e9468d..fc78af3fc02 100644 --- a/.github/workflows/docker-reproducible.yml +++ b/.github/workflows/docker-reproducible.yml @@ -136,6 +136,23 @@ jobs: IMAGE_TAG="${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${VERSION}-${{ matrix.arch }}" docker push "$IMAGE_TAG" + - name: Extract artifacts for release + run: | + # Extract binary and .deb from verified image + docker create --name extract-${{ matrix.arch }} lighthouse-verify-2-${{ matrix.arch }} + docker cp extract-${{ matrix.arch }}:/lighthouse ./lighthouse-${{ matrix.arch }} + docker cp extract-${{ matrix.arch }}:/lighthouse.deb ./lighthouse-${{ matrix.arch }}.deb + docker rm extract-${{ matrix.arch }} + + - name: Upload reproducible artifacts + uses: actions/upload-artifact@v4 + with: + name: reproducible-artifacts-${{ matrix.arch }} + path: | + lighthouse-${{ matrix.arch }} + lighthouse-${{ matrix.arch }}.deb + retention-days: 1 + - name: Clean up local images run: | docker rmi lighthouse-verify-2-${{ matrix.arch }} || true diff --git a/.github/workflows/release-reproducible.yml b/.github/workflows/release-reproducible.yml new file mode 100644 index 00000000000..7375c7cada9 --- /dev/null +++ b/.github/workflows/release-reproducible.yml @@ -0,0 +1,126 @@ +# This workflow signs and publishes reproducible artifacts +# It triggers when either Release Suite or docker-reproducible completes +# But only proceeds when BOTH have completed successfully for the same tag + +name: release-reproducible + +on: + workflow_run: + workflows: [Release Suite, docker-reproducible] + types: [completed] + +jobs: + check-both-workflows: + name: verify both workflows completed + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + outputs: + should_proceed: ${{ steps.check.outputs.should_proceed }} + version: ${{ steps.check.outputs.version }} + steps: + - name: Check if both workflows completed successfully + id: check + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Get the tag/branch from the triggering workflow + TAG="${{ github.event.workflow_run.head_branch }}" + + # Only proceed for version tags + if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]]; then + echo "Not a version tag, skipping" + echo "should_proceed=false" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "Checking workflows for tag: $TAG" + echo "version=$TAG" >> $GITHUB_OUTPUT + + # Check Release Suite status + RELEASE_SUCCESS=$(gh api /repos/${{ github.repository }}/actions/workflows/release.yml/runs \ + --jq ".workflow_runs[] | select(.head_branch == \"$TAG\" and .conclusion == \"success\") | .id" \ + | head -1) + + # Check docker-reproducible status + DOCKER_SUCCESS=$(gh api /repos/${{ github.repository }}/actions/workflows/docker-reproducible.yml/runs \ + --jq ".workflow_runs[] | select(.head_branch == \"$TAG\" and .conclusion == \"success\") | .id" \ + | head -1) + + if [[ -n "$RELEASE_SUCCESS" ]] && [[ -n "$DOCKER_SUCCESS" ]]; then + echo "Both workflows completed successfully for $TAG" + echo " - Release Suite: run $RELEASE_SUCCESS" + echo " - docker-reproducible: run $DOCKER_SUCCESS" + echo "should_proceed=true" >> $GITHUB_OUTPUT + else + echo "Waiting for both workflows to complete for $TAG" + [[ -z "$RELEASE_SUCCESS" ]] && echo " - Release Suite: not completed" + [[ -z "$DOCKER_SUCCESS" ]] && echo " - docker-reproducible: not completed" + echo "should_proceed=false" >> $GITHUB_OUTPUT + fi + + sign-and-publish: + name: sign and publish reproducible artifacts + needs: check-both-workflows + if: needs.check-both-workflows.outputs.should_proceed == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + strategy: + matrix: + arch: [amd64, arm64] + include: + - arch: amd64 + rust_target: x86_64-unknown-linux-gnu + - arch: arm64 + rust_target: aarch64-unknown-linux-gnu + steps: + - name: Download reproducible artifacts + uses: dawidd6/action-download-artifact@v6 + with: + workflow: docker-reproducible.yml + name: reproducible-artifacts-${{ matrix.arch }} + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ needs.check-both-workflows.outputs.version }} + + - name: Prepare artifacts for signing + run: | + VERSION=${{ needs.check-both-workflows.outputs.version }} + ARCH_SHORT=$(echo "${{ matrix.rust_target }}" | cut -d'-' -f1) + + # Rename binary and create tarball + mv lighthouse-${{ matrix.arch }} lighthouse-reproducible-${VERSION}-${{ matrix.rust_target }} + tar -czf lighthouse-reproducible-${VERSION}-${{ matrix.rust_target }}.tar.gz \ + lighthouse-reproducible-${VERSION}-${{ matrix.rust_target }} --remove-files + + # Rename Debian package + mv lighthouse-${{ matrix.arch }}.deb lighthouse-${VERSION}-${ARCH_SHORT}-reproducible.deb + + - name: Sign artifacts with GPG + env: + GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + run: | + export GPG_TTY=$(tty) + echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --batch --import + + VERSION=${{ needs.check-both-workflows.outputs.version }} + ARCH_SHORT=$(echo "${{ matrix.rust_target }}" | cut -d'-' -f1) + + # Sign binary tarball + echo "$GPG_PASSPHRASE" | gpg --passphrase-fd 0 --pinentry-mode loopback --batch -ab \ + lighthouse-reproducible-${VERSION}-${{ matrix.rust_target }}.tar.gz + + # Sign Debian package + echo "$GPG_PASSPHRASE" | gpg --passphrase-fd 0 --pinentry-mode loopback --batch -ab \ + lighthouse-${VERSION}-${ARCH_SHORT}-reproducible.deb + + - name: Upload reproducible artifacts to release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION=${{ needs.check-both-workflows.outputs.version }} + + # Upload all signed artifacts and their signatures + gh release upload ${VERSION} \ + lighthouse-reproducible-*.tar.gz* \ + lighthouse-*-reproducible.deb* diff --git a/Dockerfile.reproducible b/Dockerfile.reproducible index 903515373f8..7a210759461 100644 --- a/Dockerfile.reproducible +++ b/Dockerfile.reproducible @@ -11,11 +11,18 @@ ARG RUST_TARGET="x86_64-unknown-linux-gnu" COPY ./ /app WORKDIR /app -# Build the project with the reproducible settings -RUN make build-reproducible +# Build the project with reproducible settings and create Debian package +RUN make build-reproducible RUST_TARGET=${RUST_TARGET} && \ + make deb-cargo RUST_TARGET=${RUST_TARGET} -# Move the binary to a standard location -RUN mv /app/target/${RUST_TARGET}/release/lighthouse /lighthouse +# Move artifacts to standard locations for easier extraction +RUN mv /app/target/${RUST_TARGET}/release/lighthouse /lighthouse && \ + mv /app/target/${RUST_TARGET}/debian/*.deb /lighthouse.deb + +# Artifacts stage for extracting files in CI/CD workflows +FROM scratch AS artifacts +COPY --from=builder /lighthouse /lighthouse +COPY --from=builder /lighthouse.deb /lighthouse.deb # Create a minimal final image with just the binary FROM gcr.io/distroless/cc-debian12:nonroot-6755e21ccd99ddead6edc8106ba03888cbeed41a diff --git a/Makefile b/Makefile index a6891b682f7..fa628043220 100644 --- a/Makefile +++ b/Makefile @@ -332,3 +332,81 @@ clean: cargo clean make -C $(EF_TESTS) clean make -C $(STATE_TRANSITION_VECTORS) clean + +.PHONY: deb-cargo +deb-cargo: build-reproducible ## Build .deb package using cargo-deb with reproducible settings + @echo "Building .deb package with cargo-deb..." + cargo install cargo-deb@3.6.0 --locked + cd lighthouse && \ + SOURCE_DATE_EPOCH=$(SOURCE_DATE) \ + CARGO_INCREMENTAL=$(CARGO_INCREMENTAL_VAL) \ + LC_ALL=$(LOCALE_VAL) \ + TZ=$(TZ_VAL) \ + cargo deb --target $(RUST_TARGET) --no-build --no-dbgsym --no-strip + + @echo "Package built successfully!" + @find target/$(RUST_TARGET)/debian -name "*.deb" -exec ls -la {} \; + + +.PHONY: deb-cargo-x86_64 +deb-cargo-x86_64: ## Build .deb for specific architectures + $(MAKE) deb-cargo RUST_TARGET=x86_64-unknown-linux-gnu + +.PHONY: deb-cargo-aarch64 +deb-cargo-aarch64: + $(MAKE) deb-cargo RUST_TARGET=aarch64-unknown-linux-gnu + +.PHONY: deb-cargo-all +deb-cargo-all: deb-cargo-x86_64 deb-cargo-aarch64 + +.PHONY: install-deb-local +install-deb-local: ## Install .deb package locally for testing + @PACKAGE=$$(find target/$(RUST_TARGET)/debian -name "*.deb" | head -1); \ + if [ -n "$$PACKAGE" ]; then \ + echo "Installing lighthouse package: $$PACKAGE"; \ + sudo dpkg -i "$$PACKAGE"; \ + echo "Fixing dependencies if needed..."; \ + sudo apt-get install -f; \ + echo "Package installed successfully!"; \ + echo ""; \ + echo "The lighthouse service is now available but not started."; \ + echo "Your systemd service file handles user creation declaratively."; \ + echo ""; \ + echo "To check service status: systemctl status lighthouse"; \ + else \ + echo "No .deb package found. Run 'make deb-cargo' first."; \ + fi + + +.PHONY: remove-deb-local +remove-deb-local: ## Remove installed lighthouse package + @echo "Removing lighthouse package..." + sudo dpkg -r lighthouse || true + sudo systemctl daemon-reload || true + +.PHONY: clean-deb +clean-deb: ## Clean up debian packaging artifacts + @echo "Cleaning up debian packaging artifacts..." + rm -f lighthouse_*.deb lighthouse-deb-*.deb *-diff.txt + rm -rf target/*/debian/ + + +.PHONY: help-deb +help-deb: ## Show help for debian packaging + @echo "Clean Debian Packaging Targets:" + @echo " deb-cargo - Build .deb package with cargo-deb" + @echo " deb-cargo-x86_64 - Build x86_64 .deb package" + @echo " deb-cargo-aarch64 - Build aarch64 .deb package" + @echo " deb-cargo-all - Build all architectures" + @echo " test-deb-reproducible - Test reproducibility" + @echo " install-deb-local - Install .deb package locally" + @echo " remove-deb-local - Remove installed package" + @echo " clean-deb - Clean up packaging artifacts" + @echo "" + @echo "Prerequisites:" + @echo " - lighthouse/lighthouse.service file" + @echo " - README.md file in current directory" + @echo "" + @echo "Quick start:" + @echo " make deb-cargo - Build .deb for current RUST_TARGET" + @echo " make install-deb-local - Test the package" diff --git a/lighthouse/Cargo.toml b/lighthouse/Cargo.toml index ebe00c9be59..2669bd2e0b3 100644 --- a/lighthouse/Cargo.toml +++ b/lighthouse/Cargo.toml @@ -96,3 +96,26 @@ zeroize = { workspace = true } [[test]] name = "lighthouse_tests" path = "tests/main.rs" + +[package.metadata.deb] +maintainer = "Sigma Prime " +extended-description = """\ +Lighthouse is a Rust implementation of the Ethereum beacon chain, \ +built by Sigma Prime. It implements the official Ethereum 2.0 specification.""" + +depends = "$auto, systemd" +section = "net" +priority = "optional" +maintainer-scripts = "debian/" # This tells cargo-deb where to find scripts + +# System integration +systemd-units = { enable = false, start = false } + +# Assets to include in the package +assets = [ + ["target/release/lighthouse", "usr/bin/", "755"], + ["debian/lighthouse.service", "lib/systemd/system/", "644"], + ["../README.md", "usr/share/doc/lighthouse/", "644"], +] + +default-features = false diff --git a/lighthouse/debian/lighthouse.service b/lighthouse/debian/lighthouse.service new file mode 100644 index 00000000000..185d202de57 --- /dev/null +++ b/lighthouse/debian/lighthouse.service @@ -0,0 +1,16 @@ +[Unit] +Description=Lighthouse Ethereum Beacon Node +After=network.target +Wants=network.target + +[Service] +Type=exec +DynamicUser=yes +StateDirectory=lighthouse +ExecStart=/usr/bin/lighthouse bn \ + --execution-endpoint http://localhost:8551 \ + --execution-jwt-secret-key 0000000000000000000000000000000000000000000000000000000000000000 + --datadir %S/lighthouse + +[Install] +WantedBy=default.target diff --git a/lighthouse/debian/prerm b/lighthouse/debian/prerm new file mode 100644 index 00000000000..f8e1bcfd14a --- /dev/null +++ b/lighthouse/debian/prerm @@ -0,0 +1,12 @@ +#!/bin/sh +set -e + +if [ "$1" = "remove" ]; then + # Stop service if running + systemctl stop lighthouse || true + systemctl disable lighthouse || true +fi + +#DEBHELPER# + +exit 0