#!/bin/bash # RHEL Reproducible Build Script # Sprint: SPRINT_1227_0002_0001 (Reproducible Builders) # # Usage: build.sh --srpm [--patch ] [--output ] set -euo pipefail # Default values OUTPUT_DIR="/build/output" WORK_DIR="/build/work" SRPM="" PATCH_FILE="" SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-}" usage() { cat < Path or URL to SRPM file (required) --patch Path to security patch file (optional) --output Output directory (default: /build/output) --epoch SOURCE_DATE_EPOCH value (default: from changelog) --help Show this help message Examples: $0 --srpm openssl-3.0.7-1.el9.src.rpm --patch CVE-2023-0286.patch $0 --srpm https://mirror/srpms/curl-8.0.1-1.el9.src.rpm EOF exit 0 } log() { echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $*" } error() { log "ERROR: $*" >&2 exit 1 } # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --srpm) SRPM="$2" shift 2 ;; --patch) PATCH_FILE="$2" shift 2 ;; --output) OUTPUT_DIR="$2" shift 2 ;; --epoch) SOURCE_DATE_EPOCH="$2" shift 2 ;; --help) usage ;; *) error "Unknown option: $1" ;; esac done [[ -z "${SRPM}" ]] && error "SRPM path required. Use --srpm " # Create directories mkdir -p "${OUTPUT_DIR}" "${WORK_DIR}" cd "${WORK_DIR}" log "Starting RHEL reproducible build" log "SRPM: ${SRPM}" # Download or copy SRPM if [[ "${SRPM}" =~ ^https?:// ]]; then log "Downloading SRPM..." curl -fsSL -o source.src.rpm "${SRPM}" SRPM="source.src.rpm" elif [[ ! -f "${SRPM}" ]]; then error "SRPM file not found: ${SRPM}" fi # Install SRPM log "Installing SRPM..." rpm2cpio "${SRPM}" | cpio -idmv # Extract SOURCE_DATE_EPOCH from changelog if not provided if [[ -z "${SOURCE_DATE_EPOCH}" ]]; then SPEC_FILE=$(find . -name "*.spec" | head -1) if [[ -n "${SPEC_FILE}" ]]; then # Extract date from first changelog entry CHANGELOG_DATE=$(grep -m1 '^\*' "${SPEC_FILE}" | sed 's/^\* //' | cut -d' ' -f1-3) if [[ -n "${CHANGELOG_DATE}" ]]; then SOURCE_DATE_EPOCH=$(date -d "${CHANGELOG_DATE}" +%s 2>/dev/null || echo "") fi fi if [[ -z "${SOURCE_DATE_EPOCH}" ]]; then SOURCE_DATE_EPOCH=$(date +%s) log "Warning: Using current time for SOURCE_DATE_EPOCH" fi fi export SOURCE_DATE_EPOCH log "SOURCE_DATE_EPOCH: ${SOURCE_DATE_EPOCH}" # Apply security patch if provided if [[ -n "${PATCH_FILE}" ]]; then if [[ ! -f "${PATCH_FILE}" ]]; then error "Patch file not found: ${PATCH_FILE}" fi log "Applying security patch: ${PATCH_FILE}" # Copy patch to SOURCES PATCH_NAME=$(basename "${PATCH_FILE}") cp "${PATCH_FILE}" SOURCES/ # Add patch to spec file SPEC_FILE=$(find . -name "*.spec" | head -1) if [[ -n "${SPEC_FILE}" ]]; then # Find last Patch line or Source line LAST_PATCH=$(grep -n '^Patch[0-9]*:' "${SPEC_FILE}" | tail -1 | cut -d: -f1) if [[ -z "${LAST_PATCH}" ]]; then LAST_PATCH=$(grep -n '^Source[0-9]*:' "${SPEC_FILE}" | tail -1 | cut -d: -f1) fi # Calculate next patch number PATCH_NUM=$(grep -c '^Patch[0-9]*:' "${SPEC_FILE}" || echo 0) PATCH_NUM=$((PATCH_NUM + 100)) # Use 100+ for security patches # Insert patch declaration sed -i "${LAST_PATCH}a Patch${PATCH_NUM}: ${PATCH_NAME}" "${SPEC_FILE}" # Add %patch to %prep if not using autosetup if ! grep -q '%autosetup' "${SPEC_FILE}"; then PREP_LINE=$(grep -n '^%prep' "${SPEC_FILE}" | head -1 | cut -d: -f1) if [[ -n "${PREP_LINE}" ]]; then # Find last %patch line in %prep LAST_PATCH_LINE=$(sed -n "${PREP_LINE},\$p" "${SPEC_FILE}" | grep -n '^%patch' | tail -1 | cut -d: -f1) if [[ -n "${LAST_PATCH_LINE}" ]]; then INSERT_LINE=$((PREP_LINE + LAST_PATCH_LINE)) else INSERT_LINE=$((PREP_LINE + 1)) fi sed -i "${INSERT_LINE}a %patch${PATCH_NUM} -p1" "${SPEC_FILE}" fi fi fi fi # Set up rpmbuild tree log "Setting up rpmbuild tree..." rpmdev-setuptree || true # Copy sources and spec cp -r SOURCES/* ~/rpmbuild/SOURCES/ 2>/dev/null || true cp *.spec ~/rpmbuild/SPECS/ 2>/dev/null || true # Build using mock for isolation and reproducibility log "Building with mock (stellaops-repro config)..." SPEC_FILE=$(find ~/rpmbuild/SPECS -name "*.spec" | head -1) if [[ -n "${SPEC_FILE}" ]]; then # Build SRPM first rpmbuild -bs "${SPEC_FILE}" BUILT_SRPM=$(find ~/rpmbuild/SRPMS -name "*.src.rpm" | head -1) if [[ -n "${BUILT_SRPM}" ]]; then # Build with mock mock -r stellaops-repro --rebuild "${BUILT_SRPM}" --resultdir="${OUTPUT_DIR}/rpms" else error "SRPM build failed" fi else error "No spec file found" fi # Extract function fingerprints from built RPMs log "Extracting function fingerprints..." for rpm in "${OUTPUT_DIR}/rpms"/*.rpm; do if [[ -f "${rpm}" ]] && [[ ! "${rpm}" =~ \.src\.rpm$ ]]; then /usr/local/bin/extract-functions.sh "${rpm}" "${OUTPUT_DIR}/fingerprints" fi done # Generate build manifest log "Generating build manifest..." cat > "${OUTPUT_DIR}/manifest.json" </dev/null | sed 's/,$//' | sed 's/^/[/' | sed 's/$/]/'), "fingerprint_files": $(find "${OUTPUT_DIR}/fingerprints" -name "*.json" -printf '"%f",' 2>/dev/null | sed 's/,$//' | sed 's/^/[/' | sed 's/$/]/') } EOF log "Build complete. Output in: ${OUTPUT_DIR}" log "Manifest: ${OUTPUT_DIR}/manifest.json"