Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/packaging/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ ARG TARGETARCH

# Install build dependencies
# - gcc: required for cgo (CGO_ENABLED=1 in scripts/constants.sh)
# - gettext: envsubst for nfpm config template expansion
# - gnupg2: GPG signing of RPM packages
# - git: version detection in build scripts
RUN dnf install -y \
gcc \
gettext \
gnupg2 \
git \
&& dnf clean all
Expand Down
68 changes: 68 additions & 0 deletions .github/packaging/Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ vars:
arm64) echo "aarch64" ;;
*) echo "${arch}" ;;
esac
# Map uname -m to DEB arch names (x86_64 -> amd64).
PACKAGING_HOST_DEB_ARCH:
sh: |
arch=$(uname -m)
case "${arch}" in
x86_64) echo "amd64" ;;
aarch64|arm64) echo "arm64" ;;
*) echo "${arch}" ;;
esac
# Git commit hash, resolved on host (container may not have .git for worktrees).
PACKAGING_GIT_COMMIT:
sh: git rev-parse HEAD
Expand All @@ -30,6 +39,7 @@ vars:
sh: echo "${PACKAGING_TAG:-v0.0.0}"
PACKAGING_DOCKER_IMAGE: avalanchego-rpm-builder
PACKAGING_OUTPUT_DIR: '{{.REPO_ROOT}}/build/rpm'
PACKAGING_DEB_OUTPUT_DIR: '{{.REPO_ROOT}}/build/deb'

tasks:
default:
Expand Down Expand Up @@ -114,3 +124,61 @@ tasks:
GIT_COMMIT: '{{.PACKAGING_GIT_COMMIT}}'
cmds:
- cmd: '{{.REPO_ROOT}}/.github/packaging/scripts/validate-rpm.sh'

# ── DEB packaging tasks ──────────────────────────────────────────

build-debs:
desc: Builds DEBs for both avalanchego and subnet-evm
cmds:
- task: build-avalanchego-deb
- task: build-subnet-evm-deb

build-avalanchego-deb:
desc: Builds DEB for avalanchego
vars:
DEB_ARCH: '{{.DEB_ARCH | default .PACKAGING_HOST_DEB_ARCH}}'
DEB_TAG: '{{.PACKAGING_TAG}}'
cmds:
- cmd: mkdir -p {{.PACKAGING_DEB_OUTPUT_DIR}}
- cmd: >-
PACKAGE=avalanchego
VERSION={{trimPrefix "v" .DEB_TAG}}
TAG={{.DEB_TAG}}
PACKAGE_ARCH={{.DEB_ARCH}}
OUTPUT_DIR={{.PACKAGING_DEB_OUTPUT_DIR}}
AVALANCHEGO_COMMIT={{.PACKAGING_GIT_COMMIT}}
{{if .DEB_GPG_KEY_FILE}}DEB_GPG_KEY_FILE={{.DEB_GPG_KEY_FILE}}{{end}}
{{if .NFPM_DEB_PASSPHRASE}}NFPM_DEB_PASSPHRASE={{.NFPM_DEB_PASSPHRASE}}{{end}}
{{.REPO_ROOT}}/.github/packaging/scripts/build-deb.sh

build-subnet-evm-deb:
desc: Builds DEB for subnet-evm
vars:
DEB_ARCH: '{{.DEB_ARCH | default .PACKAGING_HOST_DEB_ARCH}}'
DEB_TAG: '{{.PACKAGING_TAG}}'
cmds:
- cmd: mkdir -p {{.PACKAGING_DEB_OUTPUT_DIR}}
- cmd: >-
PACKAGE=subnet-evm
VERSION={{trimPrefix "v" .DEB_TAG}}
TAG={{.DEB_TAG}}
PACKAGE_ARCH={{.DEB_ARCH}}
OUTPUT_DIR={{.PACKAGING_DEB_OUTPUT_DIR}}
AVALANCHEGO_COMMIT={{.PACKAGING_GIT_COMMIT}}
{{if .DEB_GPG_KEY_FILE}}DEB_GPG_KEY_FILE={{.DEB_GPG_KEY_FILE}}{{end}}
{{if .NFPM_DEB_PASSPHRASE}}NFPM_DEB_PASSPHRASE={{.NFPM_DEB_PASSPHRASE}}{{end}}
{{.REPO_ROOT}}/.github/packaging/scripts/build-deb.sh

test-build-debs:
desc: Builds and validates DEBs end-to-end
cmds:
- task: build-debs
- task: validate-debs

validate-debs:
desc: Validates built DEBs by installing and smoke testing in a fresh container
env:
TAG: '{{.PACKAGING_TAG}}'
GIT_COMMIT: '{{.PACKAGING_GIT_COMMIT}}'
cmds:
- cmd: '{{.REPO_ROOT}}/.github/packaging/scripts/validate-deb.sh'
18 changes: 18 additions & 0 deletions .github/packaging/nfpm/avalanchego-deb.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: avalanchego
arch: "${PACKAGE_ARCH}"
version: "${VERSION}"
maintainer: "Ava Labs <security@avalabs.org>"
description: "AvalancheGo node — the official Avalanche protocol implementation"
homepage: "https://github.com/ava-labs/avalanchego"
license: "BSD-3-Clause"
contents:
- src: "${AVALANCHEGO_BINARY}"
dst: /usr/local/bin/avalanchego
expand: true
file_info:
mode: 0755
changelog: "${NFPM_CHANGELOG}"
deb:
compression: gzip
# DEB signing is performed post-build with dpkg-sig (not nfpm inline)
# because nfpm's Go openpgp signatures are incompatible with dpkg-sig --verify.
5 changes: 2 additions & 3 deletions .github/packaging/nfpm/avalanchego.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ contents:
expand: true
file_info:
mode: 0755
# changelog and key_file paths are set to well-known locations by build-rpm.sh
changelog: "/build/build/nfpm-changelog.yml"
changelog: "${NFPM_CHANGELOG}"
rpm:
compression: zstd
signature:
key_file: "/build/build/gpg/signing-key.asc"
key_file: "${NFPM_SIGNING_KEY}"
19 changes: 19 additions & 0 deletions .github/packaging/nfpm/subnet-evm-deb.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: subnet-evm
arch: "${PACKAGE_ARCH}"
version: "${VERSION}"
maintainer: "Ava Labs <security@avalabs.org>"
description: "Subnet-EVM plugin for AvalancheGo"
homepage: "https://github.com/ava-labs/avalanchego"
license: "BSD-3-Clause"
contents:
- src: "${SUBNET_EVM_BINARY}"
# SUBNET_EVM_VM_ID is sourced from graft/subnet-evm/scripts/constants.sh
dst: /usr/local/lib/avalanchego/plugins/${SUBNET_EVM_VM_ID}
expand: true
file_info:
mode: 0755
changelog: "${NFPM_CHANGELOG}"
deb:
compression: gzip
# DEB signing is performed post-build with dpkg-sig (not nfpm inline)
# because nfpm's Go openpgp signatures are incompatible with dpkg-sig --verify.
5 changes: 2 additions & 3 deletions .github/packaging/nfpm/subnet-evm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ contents:
expand: true
file_info:
mode: 0755
# changelog and key_file paths are set to well-known locations by build-rpm.sh
changelog: "/build/build/nfpm-changelog.yml"
changelog: "${NFPM_CHANGELOG}"
rpm:
compression: zstd
signature:
key_file: "/build/build/gpg/signing-key.asc"
key_file: "${NFPM_SIGNING_KEY}"
183 changes: 183 additions & 0 deletions .github/packaging/scripts/build-deb.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#!/usr/bin/env bash

# Build and sign a DEB package on the host runner.
#
# Required env vars:
# PACKAGE - "avalanchego" or "subnet-evm"
# VERSION - Semantic version without "v" prefix (e.g., "1.14.1")
# TAG - Git tag (e.g., "v1.14.1")
# PACKAGE_ARCH - DEB architecture name ("amd64" or "arm64")
# OUTPUT_DIR - Directory for the output DEB
#
# Optional env vars:
# DEB_GPG_KEY_FILE - Path to GPG private key for signing
# NFPM_DEB_PASSPHRASE - Passphrase for the GPG key (cached in gpg-agent for dpkg-sig)
# AVALANCHEGO_COMMIT - Git commit hash (auto-detected if not set)

set -euo pipefail

: "${PACKAGE:?PACKAGE must be set (avalanchego or subnet-evm)}"
: "${VERSION:?VERSION must be set}"
: "${TAG:?TAG must be set}"
: "${PACKAGE_ARCH:?PACKAGE_ARCH must be set}"
: "${OUTPUT_DIR:?OUTPUT_DIR must be set}"

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
PACKAGING_DIR="${REPO_ROOT}/.github/packaging"

# Well-known paths referenced by nfpm configs
export NFPM_CHANGELOG="${REPO_ROOT}/build/nfpm-changelog.yml"
export NFPM_SIGNING_KEY="${REPO_ROOT}/build/gpg/signing-key.asc"

echo "=== Building ${PACKAGE} DEB for ${PACKAGE_ARCH} (tag: ${TAG}) ==="

# ── Step 1: Build binary ──────────────────────────────────────────

# shellcheck disable=SC1091
source "${REPO_ROOT}/scripts/constants.sh"
# shellcheck disable=SC1091
source "${REPO_ROOT}/scripts/git_commit.sh"

# shellcheck disable=SC2154
echo "Git commit: ${git_commit}"

# Disable Go's automatic VCS stamping — the commit hash is passed
# explicitly via AVALANCHEGO_COMMIT and -ldflags instead.
export GOFLAGS="${GOFLAGS:-} -buildvcs=false"

case "${PACKAGE}" in
avalanchego)
echo "Building avalanchego..."
"${REPO_ROOT}/scripts/build.sh"
# shellcheck disable=SC2154
BINARY_PATH="${avalanchego_path}"
;;
subnet-evm)
echo "Building subnet-evm..."
# Source VM ID from constants.sh (canonical definition)
SUBNET_EVM_VM_ID=$(
grep '^DEFAULT_VM_ID=' "${REPO_ROOT}/graft/subnet-evm/scripts/constants.sh" \
| cut -d'"' -f2
)
export SUBNET_EVM_VM_ID
echo "Subnet-EVM VM ID: ${SUBNET_EVM_VM_ID}"

SUBNET_EVM_BINARY="${REPO_ROOT}/build/subnet-evm"
# Build from subnet-evm directory — build.sh uses relative glob "plugin/"*.go
(cd "${REPO_ROOT}/graft/subnet-evm" && ./scripts/build.sh "${SUBNET_EVM_BINARY}")
BINARY_PATH="${SUBNET_EVM_BINARY}"
;;
*)
echo "Unknown package: ${PACKAGE}" >&2
exit 1
;;
esac

echo "Binary built at: ${BINARY_PATH}"

# ── Step 2: Generate changelog ────────────────────────────────────

mkdir -p "$(dirname "${NFPM_CHANGELOG}")"
cat > "${NFPM_CHANGELOG}" <<EOF
---
- semver: ${VERSION}
date: $(date -u +%Y-%m-%dT%H:%M:%SZ)
packager: Ava Labs <security@avalabs.org>
changes:
- note: "See https://github.com/ava-labs/avalanchego/releases/tag/v${VERSION}"
EOF

# ── Step 3: Set up GPG signing ────────────────────────────────────

GPG_WORKDIR="${REPO_ROOT}/build/gpg"
mkdir -p "${GPG_WORKDIR}"
GPG_PUBLIC_KEY="${OUTPUT_DIR}/DEB-GPG-KEY-avalanchego"

# Configure gpg-agent for non-interactive signing. dpkg-sig delegates to gpg,
# which needs the passphrase available via gpg-agent. allow-preset-passphrase
# lets us cache the passphrase so dpkg-sig can sign without prompting.
GPG_AGENT_CONF="${HOME}/.gnupg/gpg-agent.conf"
mkdir -p "$(dirname "${GPG_AGENT_CONF}")"
if ! grep -q allow-preset-passphrase "${GPG_AGENT_CONF}" 2>/dev/null; then
echo "allow-preset-passphrase" >> "${GPG_AGENT_CONF}"
gpgconf --kill gpg-agent 2>/dev/null || true
fi

if [[ -n "${DEB_GPG_KEY_FILE:-}" ]]; then
echo "Using provided GPG key for signing"
gpg --batch --import "${DEB_GPG_KEY_FILE}"
# Copy to well-known path for nfpm config
cp "${DEB_GPG_KEY_FILE}" "${NFPM_SIGNING_KEY}"

# Cache the passphrase in gpg-agent so dpkg-sig can sign non-interactively.
if [[ -n "${NFPM_DEB_PASSPHRASE:-}" ]]; then
GPG_PRESET_PASS="$(gpgconf --list-dirs libexecdir)/gpg-preset-passphrase"
KEYGRIPS=$(gpg --batch --with-colons --with-keygrip --list-secret-keys "security@avalabs.org" \
| awk -F: '$1 == "grp" { print $10 }')
for kg in ${KEYGRIPS}; do
echo "${NFPM_DEB_PASSPHRASE}" | "${GPG_PRESET_PASS}" --preset "${kg}"
done
echo "GPG passphrase cached in gpg-agent"
fi
elif [[ -f "${NFPM_SIGNING_KEY}" ]]; then
# Reuse ephemeral key from a previous build (e.g., avalanchego built before subnet-evm)
echo "Reusing existing ephemeral GPG key"
gpg --batch --import "${NFPM_SIGNING_KEY}"
else
echo "Generating ephemeral GPG key for signing"

gpg --batch --gen-key <<GPGEOF
%no-protection
Key-Type: RSA
Key-Length: 4096
Subkey-Type: RSA
Subkey-Length: 4096
Name-Real: AvalancheGo DEB Signing (ephemeral)
Name-Email: security@avalabs.org
Expire-Date: 1d
%commit
GPGEOF

# Export private key to well-known path for nfpm
gpg --batch --armor --export-secret-keys "security@avalabs.org" > "${NFPM_SIGNING_KEY}"
fi

# Export public key for verification
gpg --batch --armor --export "security@avalabs.org" > "${GPG_PUBLIC_KEY}"
echo "GPG public key exported to: ${GPG_PUBLIC_KEY}"

# ── Step 4: Package with nfpm ─────────────────────────────────────

DEB_FILENAME="${PACKAGE}-${TAG}-${PACKAGE_ARCH}.deb"
DEB_PATH="${OUTPUT_DIR}/${DEB_FILENAME}"
mkdir -p "${OUTPUT_DIR}"

# Set binary path env var for nfpm config (contents use expand: true)
case "${PACKAGE}" in
avalanchego) export AVALANCHEGO_BINARY="${BINARY_PATH}" ;;
subnet-evm) export SUBNET_EVM_BINARY="${BINARY_PATH}" ;;
esac

export VERSION PACKAGE_ARCH

# nfpm does not expand env vars in top-level fields (changelog, signature.key_file).
# Preprocess the config template with envsubst so all ${VAR} references resolve.
NFPM_CONFIG_RESOLVED="${REPO_ROOT}/build/${PACKAGE}-deb-resolved.yml"
envsubst < "${PACKAGING_DIR}/nfpm/${PACKAGE}-deb.yml" > "${NFPM_CONFIG_RESOLVED}"

echo "Packaging ${DEB_FILENAME}..."
nfpm package \
--config "${NFPM_CONFIG_RESOLVED}" \
--packager deb \
--target "${DEB_PATH}"

# ── Step 5: Sign with dpkg-sig ───────────────────────────────────
# nfpm's Go openpgp signatures are incompatible with dpkg-sig --verify,
# so we sign post-build with dpkg-sig itself for verifiable signatures.

GPG_FINGERPRINT=$(gpg --batch --with-colons --list-secret-keys "security@avalabs.org" 2>/dev/null \
| awk -F: '$1 == "fpr" { print $10; exit }')
echo "Signing ${DEB_FILENAME} with GPG fingerprint ${GPG_FINGERPRINT}..."
dpkg-sig --sign builder -k "${GPG_FINGERPRINT}" "${DEB_PATH}"

echo "DEB built and signed: ${DEB_PATH}"
11 changes: 8 additions & 3 deletions .github/packaging/scripts/build-rpm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ REPO_ROOT="/build"
PACKAGING_DIR="${REPO_ROOT}/.github/packaging"

# Well-known paths referenced by nfpm configs
NFPM_CHANGELOG="${REPO_ROOT}/build/nfpm-changelog.yml"
NFPM_SIGNING_KEY="${REPO_ROOT}/build/gpg/signing-key.asc"
export NFPM_CHANGELOG="${REPO_ROOT}/build/nfpm-changelog.yml"
export NFPM_SIGNING_KEY="${REPO_ROOT}/build/gpg/signing-key.asc"

echo "=== Building ${PACKAGE} RPM for ${RPM_ARCH} (tag: ${TAG}) ==="

Expand Down Expand Up @@ -147,9 +147,14 @@ esac

export VERSION RPM_ARCH

# nfpm does not expand env vars in top-level fields (changelog, signature.key_file).
# Preprocess the config template with envsubst so all ${VAR} references resolve.
NFPM_CONFIG_RESOLVED="${REPO_ROOT}/build/${PACKAGE}-rpm-resolved.yml"
envsubst < "${PACKAGING_DIR}/nfpm/${PACKAGE}.yml" > "${NFPM_CONFIG_RESOLVED}"

echo "Packaging ${RPM_FILENAME}..."
nfpm package \
--config "${PACKAGING_DIR}/nfpm/${PACKAGE}.yml" \
--config "${NFPM_CONFIG_RESOLVED}" \
--packager rpm \
--target "${RPM_PATH}"

Expand Down
Loading
Loading