diff --git a/.github/workflows/package-linux.yml b/.github/workflows/package-linux.yml new file mode 100644 index 0000000..62c22de --- /dev/null +++ b/.github/workflows/package-linux.yml @@ -0,0 +1,196 @@ +name: Linux Package +on: + push: + tags: + - "v*" + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + +jobs: + determine-versioning: + runs-on: ubuntu-latest + outputs: + linux_package_version: ${{ steps.linux_version.outputs.linux_package_version }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Derive Linux package version + id: linux_version + shell: bash + run: | + : ${GITHUB_OUTPUT:=/tmp/github_output} + + BASE_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') + if [[ "$GITHUB_REF" == refs/tags/* ]]; then + VERSION="${GITHUB_REF_NAME#v}" + else + BRANCH=$(echo "$GITHUB_REF_NAME" | sed 's|[^A-Za-z0-9]|.|g; s/\.\.+/./g; s/^\.//; s/\.$//') + HEIGHT=$(git rev-list --count HEAD) + HASH=$(git rev-parse --short HEAD) + if [[ -z "$BRANCH" ]]; then + BRANCH="ref" + fi + VERSION="${BASE_VERSION}+${BRANCH}.${HEIGHT}.${HASH}" + fi + + echo "linux_package_version=${VERSION}" >> "$GITHUB_OUTPUT" + + build: + name: Build Linux artifacts (${{ matrix.artifact_arch }}) + runs-on: ${{ matrix.os }} + needs: determine-versioning + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + artifact_arch: x86_64 + deb_arch: amd64 + - os: ubuntu-24.04-arm + artifact_arch: aarch64 + deb_arch: arm64 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set SOURCE_DATE_EPOCH from git + run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> "$GITHUB_ENV" + + - name: Install system dependencies + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libdbus-1-dev llvm + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache Cargo registry + build + if: ${{ env.ACT != 'true' }} + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: linux-release-${{ runner.os }}-${{ matrix.artifact_arch }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + linux-release-${{ runner.os }}-${{ matrix.artifact_arch }}- + + - name: Install cargo-deb + run: cargo install cargo-deb --version 3.6.3 --locked + + - name: Build release binaries + run: cargo build --release + + - name: Build systemd tarball + env: + STRIP: llvm-strip + run: | + packaging/systemd/build-tarball.sh \ + --version "${{ needs.determine-versioning.outputs.linux_package_version }}" \ + --arch "${{ matrix.artifact_arch }}" \ + --no-build + + - name: Build Debian package + run: | + packaging/debian/build-deb.sh \ + --version "${{ needs.determine-versioning.outputs.linux_package_version }}" \ + --no-build + + - name: Resolve Linux asset paths + id: linux-assets + shell: bash + run: | + : ${GITHUB_OUTPUT:=/tmp/github_output} + + TARBALL="deploy/fips-${{ needs.determine-versioning.outputs.linux_package_version }}-linux-${{ matrix.artifact_arch }}.tar.gz" + if [[ ! -f "$TARBALL" ]]; then + echo "Missing tarball: $TARBALL" >&2 + exit 1 + fi + + DEB_FILE=$(find deploy -maxdepth 1 -type f -name "fips_*_${{ matrix.deb_arch }}.deb" | sort | head -n 1) + if [[ -z "$DEB_FILE" ]]; then + echo "Missing Debian package for ${{ matrix.deb_arch }}" >&2 + exit 1 + fi + + echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT" + echo "deb=$DEB_FILE" >> "$GITHUB_OUTPUT" + + - name: SHA-256 hashes + run: | + echo "==> Linux release assets:" + sha256sum \ + "${{ steps.linux-assets.outputs.tarball }}" \ + "${{ steps.linux-assets.outputs.deb }}" + + - name: Upload artifact (GitHub only) + if: ${{ env.ACT != 'true' }} + uses: actions/upload-artifact@v4 + with: + name: fips_${{ needs.determine-versioning.outputs.linux_package_version }}_${{ matrix.artifact_arch }}_linux + path: | + ${{ steps.linux-assets.outputs.tarball }} + ${{ steps.linux-assets.outputs.deb }} + retention-days: 30 + + - name: Build Summary + run: | + echo "Build Summary for linux/${{ matrix.artifact_arch }}:" + echo " Tarball: ${{ steps.linux-assets.outputs.tarball }}" + echo " Debian: ${{ steps.linux-assets.outputs.deb }}" + + release: + name: Publish Linux assets to GitHub Release + runs-on: ubuntu-latest + needs: build + if: startsWith(github.ref, 'refs/tags/') + permissions: + contents: write + + steps: + - name: Download Linux artifacts + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: Generate Linux release checksums + run: | + cd dist + find . -maxdepth 1 -type f \( -name '*.deb' -o -name '*.tar.gz' \) -printf '%P\n' \ + | LC_ALL=C sort \ + | xargs sha256sum \ + > checksums-linux.txt + + - name: Wait for tag release + env: + GH_TOKEN: ${{ github.token }} + run: | + for attempt in $(seq 1 20); do + if gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then + exit 0 + fi + echo "Release ${GITHUB_REF_NAME} not available yet; waiting..." + sleep 15 + done + + echo "Timed out waiting for release ${GITHUB_REF_NAME}" >&2 + exit 1 + + - name: Upload Linux assets + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release upload "${GITHUB_REF_NAME}" \ + dist/*.deb \ + dist/*.tar.gz \ + dist/checksums-linux.txt \ + --clobber \ + --repo "${GITHUB_REPOSITORY}" diff --git a/packaging/debian/build-deb.sh b/packaging/debian/build-deb.sh index 7ba3f7e..d703044 100755 --- a/packaging/debian/build-deb.sh +++ b/packaging/debian/build-deb.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Build a .deb package for FIPS using cargo-deb. # -# Usage: ./build-deb.sh +# Usage: ./build-deb.sh [--target ] [--version ] [--no-build] # # Prerequisites: cargo-deb (install with: cargo install cargo-deb) # Output: deploy/fips__.deb @@ -11,6 +11,48 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="${SCRIPT_DIR}/../.." +usage() { + cat <<'EOF' +Usage: packaging/debian/build-deb.sh [options] + +Options: + --target Rust target triple to build/package + --version Override Debian package version + --no-build Package existing binaries without running cargo build + -h, --help Show this help +EOF +} + +TARGET_TRIPLE="" +VERSION_OVERRIDE="" +NO_BUILD=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --target) + TARGET_TRIPLE="${2:?missing value for --target}" + shift 2 + ;; + --version) + VERSION_OVERRIDE="${2:?missing value for --version}" + shift 2 + ;; + --no-build) + NO_BUILD=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + cd "${PROJECT_ROOT}" # Ensure cargo-deb is available @@ -26,14 +68,27 @@ fi # Build the .deb package echo "Building .deb package..." -cargo deb +OUTPUT_DIR="$(mktemp -d)" +trap 'rm -rf "${OUTPUT_DIR}"' EXIT + +cargo_args=(deb --output "${OUTPUT_DIR}") +if [[ -n "${TARGET_TRIPLE}" ]]; then + cargo_args+=(--target "${TARGET_TRIPLE}") +fi +if [[ -n "${VERSION_OVERRIDE}" ]]; then + cargo_args+=(--deb-version "${VERSION_OVERRIDE}") +fi +if [[ "${NO_BUILD}" -eq 1 ]]; then + cargo_args+=(--no-build) +fi +cargo "${cargo_args[@]}" # Move output to deploy/ mkdir -p deploy -DEB_FILE=$(find target/debian -name '*.deb' -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2) +DEB_FILE=$(find "${OUTPUT_DIR}" -maxdepth 1 -name '*.deb' -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2) if [ -z "${DEB_FILE}" ]; then - echo "Error: No .deb file found in target/debian/" >&2 + echo "Error: No .deb file found in ${OUTPUT_DIR}" >&2 exit 1 fi diff --git a/packaging/systemd/build-tarball.sh b/packaging/systemd/build-tarball.sh index d6c0e61..7887735 100755 --- a/packaging/systemd/build-tarball.sh +++ b/packaging/systemd/build-tarball.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Build FIPS release binaries and create an install tarball. # -# Usage: ./packaging/build-tarball.sh +# Usage: ./packaging/build-tarball.sh [--target ] [--version ] [--arch ] [--no-build] # Output: deploy/fips--linux-.tar.gz set -euo pipefail @@ -10,29 +10,108 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PACKAGING_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" PROJECT_ROOT="$(cd "${PACKAGING_DIR}/.." && pwd)" -# Extract version from Cargo.toml -VERSION=$(grep '^version' "${PROJECT_ROOT}/Cargo.toml" | head -1 | sed 's/.*"\(.*\)"/\1/') -ARCH=$(uname -m) +usage() { + cat <<'EOF' +Usage: packaging/systemd/build-tarball.sh [options] + +Options: + --target Rust target triple to build/package + --version Override artifact version + --arch Override artifact architecture name + --no-build Package existing binaries without running cargo build + -h, --help Show this help +EOF +} + +target_to_arch() { + local target="$1" + printf '%s\n' "${target%%-*}" +} + +VERSION_OVERRIDE="" +TARGET_TRIPLE="" +ARCH_OVERRIDE="" +NO_BUILD=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --target) + TARGET_TRIPLE="${2:?missing value for --target}" + shift 2 + ;; + --version) + VERSION_OVERRIDE="${2:?missing value for --version}" + shift 2 + ;; + --arch) + ARCH_OVERRIDE="${2:?missing value for --arch}" + shift 2 + ;; + --no-build) + NO_BUILD=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +VERSION="${VERSION_OVERRIDE:-$(grep '^version' "${PROJECT_ROOT}/Cargo.toml" | head -1 | sed 's/.*"\(.*\)"/\1/')}" +if [[ -n "${ARCH_OVERRIDE}" ]]; then + ARCH="${ARCH_OVERRIDE}" +elif [[ -n "${TARGET_TRIPLE}" ]]; then + ARCH="$(target_to_arch "${TARGET_TRIPLE}")" +else + ARCH="$(uname -m)" +fi TARBALL_NAME="fips-${VERSION}-linux-${ARCH}" DEPLOY_DIR="${PROJECT_ROOT}/deploy" STAGING_DIR="${DEPLOY_DIR}/${TARBALL_NAME}" +STRIP_BIN="${STRIP:-strip}" + +if [[ -n "${TARGET_TRIPLE}" ]]; then + BINARY_DIR="${PROJECT_ROOT}/target/${TARGET_TRIPLE}/release" +else + BINARY_DIR="${PROJECT_ROOT}/target/release" +fi echo "Building FIPS v${VERSION} for ${ARCH}..." # Build release binaries (tui is a default feature, includes fipstop) -cargo build --release --manifest-path="${PROJECT_ROOT}/Cargo.toml" +if [[ "${NO_BUILD}" -eq 0 ]]; then + cargo_args=(build --release --manifest-path="${PROJECT_ROOT}/Cargo.toml") + if [[ -n "${TARGET_TRIPLE}" ]]; then + cargo_args+=(--target "${TARGET_TRIPLE}") + fi + cargo "${cargo_args[@]}" +fi # Create staging directory rm -rf "${STAGING_DIR}" mkdir -p "${STAGING_DIR}" # Copy binaries -cp "${PROJECT_ROOT}/target/release/fips" "${STAGING_DIR}/" -cp "${PROJECT_ROOT}/target/release/fipsctl" "${STAGING_DIR}/" -cp "${PROJECT_ROOT}/target/release/fipstop" "${STAGING_DIR}/" +for bin in fips fipsctl fipstop; do + if [[ ! -f "${BINARY_DIR}/${bin}" ]]; then + echo "Missing binary: ${BINARY_DIR}/${bin}" >&2 + exit 1 + fi + cp "${BINARY_DIR}/${bin}" "${STAGING_DIR}/" +done # Strip binaries to reduce size -strip "${STAGING_DIR}/fips" "${STAGING_DIR}/fipsctl" "${STAGING_DIR}/fipstop" +if ! command -v "${STRIP_BIN}" &>/dev/null; then + echo "Strip tool not found: ${STRIP_BIN}" >&2 + exit 1 +fi +"${STRIP_BIN}" "${STAGING_DIR}/fips" "${STAGING_DIR}/fipsctl" "${STAGING_DIR}/fipstop" # Copy packaging files cp "${SCRIPT_DIR}/install.sh" "${STAGING_DIR}/"