214 lines
6.2 KiB
Bash
214 lines
6.2 KiB
Bash
#!/bin/bash
|
|
# RHEL Reproducible Build Script
|
|
# Sprint: SPRINT_1227_0002_0001 (Reproducible Builders)
|
|
#
|
|
# Usage: build.sh --srpm <url_or_path> [--patch <patch_file>] [--output <dir>]
|
|
|
|
set -euo pipefail
|
|
|
|
# Default values
|
|
OUTPUT_DIR="/build/output"
|
|
WORK_DIR="/build/work"
|
|
SRPM=""
|
|
PATCH_FILE=""
|
|
SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-}"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
RHEL Reproducible Build Script
|
|
|
|
Usage: $0 [OPTIONS]
|
|
|
|
Options:
|
|
--srpm <path> Path or URL to SRPM file (required)
|
|
--patch <path> Path to security patch file (optional)
|
|
--output <dir> Output directory (default: /build/output)
|
|
--epoch <timestamp> 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 <path>"
|
|
|
|
# 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" <<EOF
|
|
{
|
|
"builder": "rhel",
|
|
"base_image": "${BASE_IMAGE:-almalinux:9}",
|
|
"source_date_epoch": ${SOURCE_DATE_EPOCH},
|
|
"build_timestamp": "$(date -u '+%Y-%m-%dT%H:%M:%SZ')",
|
|
"srpm": "${SRPM}",
|
|
"patch_applied": $(if [[ -n "${PATCH_FILE}" ]]; then echo "\"${PATCH_FILE}\""; else echo "null"; fi),
|
|
"rpm_outputs": $(find "${OUTPUT_DIR}/rpms" -name "*.rpm" ! -name "*.src.rpm" -printf '"%f",' 2>/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"
|