From 95d1a776593cc64185698b44d6debe9290c53b79 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 26 Oct 2025 23:33:22 -0400 Subject: [PATCH] Overhaul the whole build process --- .gitignore | 1 + bin/build | 65 +++------ bin/build-ruby | 2 +- bin/clean-repo | 33 +++-- bin/list-packages | 3 +- bin/promote-build | 97 ++++++++++++++ bin/release | 114 ++++++++++++++++ bin/remove-package | 18 ++- bin/repo | 47 +++++-- bin/sign | 129 ++++++++++++++++++ bin/sync-repo | 20 +-- bin/update-repo | 4 +- build/build.sh | 319 ++++++++++++++++++++++++++++++++++++++------- 13 files changed, 715 insertions(+), 137 deletions(-) create mode 100755 bin/promote-build create mode 100755 bin/release create mode 100755 bin/sign diff --git a/.gitignore b/.gitignore index 3a4168d..6322726 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ x86_64/ src/ logs/ +build-output/ diff --git a/bin/build b/bin/build index 016119b..73ebf77 100755 --- a/bin/build +++ b/bin/build @@ -9,12 +9,13 @@ source "$BUILD_ROOT/lib/message-helpers.sh" ARCH=${ARCH:-x86_64} -ARCH_DIR="$BUILD_ROOT/output/$ARCH" +BUILD_OUTPUT_DIR="$BUILD_ROOT/build-output/$ARCH" +FINAL_OUTPUT_DIR="$BUILD_ROOT/pkgs.omarchy.org/$ARCH" SRC_DIR="$BUILD_ROOT/src" BUILD_DIR="$BUILD_ROOT/build" # Create directories if they don't exist -mkdir -p "$ARCH_DIR" "$SRC_DIR" +mkdir -p "$BUILD_OUTPUT_DIR" "$FINAL_OUTPUT_DIR" "$SRC_DIR" # Check for Docker if ! command -v docker &>/dev/null; then @@ -80,7 +81,13 @@ done # Show target architecture after parsing args print_info "Target architecture: $ARCH" -print_info "Output directory: $ARCH_DIR" +print_info "Build workspace: $BUILD_OUTPUT_DIR" +print_info "Final output: $FINAL_OUTPUT_DIR" + +# Clean build-output directory to start fresh +print_info "Cleaning build workspace..." +rm -rf "$BUILD_OUTPUT_DIR"/* +mkdir -p "$BUILD_OUTPUT_DIR" # Check if AUR package list exists (only required if not building single package) if [[ -z "$SINGLE_PACKAGE" ]]; then @@ -92,36 +99,9 @@ else print_info "Building single package: $SINGLE_PACKAGE" fi -# Handle GPG signing setup -if [[ "$SKIP_SIGNING" == true ]]; then - print_warning "Skipping GPG signing (--skip-signing flag set)" - export SKIP_SIGNING=true -else - # Get GPG key from 1Password or environment for signing - if [[ -z "$GPG_PRIVATE_KEY" ]]; then - print_info "Fetching GPG signing key from 1Password..." - GPG_PRIVATE_KEY=$(op document get "Omarchy GPG Private Key" --account=omarchy.1password.com) || { - print_error "Failed to fetch GPG key from 1Password or environment" - exit 1 - } - else - print_info "Using existing GPG signing key from environment" - fi - export GPG_PRIVATE_KEY="$GPG_PRIVATE_KEY" - - # Get passphrase from 1Password or environment - if [[ -z "$GPG_PASSPHRASE" ]]; then - print_info "Fetching GPG key passphrase from 1Password..." - GPG_PASSPHRASE=$(op item get "Omarchy GPG Private Key" --account=omarchy.1password.com --fields password --reveal) || { - print_error "Failed to fetch GPG passphrase from 1Password or environment" - exit 1 - } - else - print_info "Using existing GPG passphrase from environment" - fi - export GPG_PASSPHRASE - print_success "GPG signing key and passphrase loaded" -fi +# GPG is no longer used during build - signing happens in separate step +# But we still need to import verification keys for package validation +export SKIP_SIGNING=true # Build/update the Docker image print_info "Building Docker image..." @@ -129,12 +109,12 @@ docker build -t omarchy-aur-builder:latest -f "$BUILD_DIR/Dockerfile" "$BUILD_DI print_info "Running AUR package build..." -# Ensure output directory is writable by container user +# Ensure output directories are writable by container user # The container runs as 'builder' user, so we need to make output writable if [ "$(id -u)" -eq 0 ]; then - chmod -R 777 "$ARCH_DIR" + chmod -R 777 "$BUILD_OUTPUT_DIR" "$FINAL_OUTPUT_DIR" else - sudo chown -R $(id -u):$(id -g) "$ARCH_DIR" 2>/dev/null || chmod -R 777 "$ARCH_DIR" + sudo chown -R $(id -u):$(id -g) "$BUILD_OUTPUT_DIR" "$FINAL_OUTPUT_DIR" 2>/dev/null || chmod -R 777 "$BUILD_OUTPUT_DIR" "$FINAL_OUTPUT_DIR" fi # Build Docker arguments @@ -142,10 +122,9 @@ DOCKER_ARGS=( --rm -e ARCH="$ARCH" -e SKIP_SIGNING - -e GPG_PRIVATE_KEY - -e GPG_PASSPHRASE -e SINGLE_PACKAGE="$SINGLE_PACKAGE" - -v "$BUILD_ROOT/output:/output" + -v "$BUILD_ROOT/build-output:/build-output" + -v "$BUILD_ROOT/pkgs.omarchy.org:/pkgs.omarchy.org" -v "$BUILD_DIR:/build:ro" -v "$BUILD_ROOT/pkgbuilds:/pkgbuilds:ro" ) @@ -158,20 +137,12 @@ fi # Run the builder with assembled args docker run "${DOCKER_ARGS[@]}" omarchy-aur-builder:latest /build/build.sh -# Clear the GPG key and passphrase from environment -unset GPG_PRIVATE_KEY -unset GPG_PASSPHRASE - BUILD_RESULT=$? # Summary echo "" if [[ $BUILD_RESULT -eq 0 ]]; then print_success "AUR build completed successfully!" - echo "" - print_info "Next steps:" - echo " 1. Run ./bin/update-repo to update the repository" - echo " 2. Commit and push changes" else print_warning "Some AUR packages failed (see details above)" exit $BUILD_RESULT diff --git a/bin/build-ruby b/bin/build-ruby index 9f2dd81..7f0e005 100755 --- a/bin/build-ruby +++ b/bin/build-ruby @@ -290,4 +290,4 @@ for arch in "${ARCHITECTURES[@]}"; do if [[ $TOTAL_FILES -gt 5 ]]; then echo " ... and $((TOTAL_FILES - 5)) more files" fi -done \ No newline at end of file +done diff --git a/bin/clean-repo b/bin/clean-repo index 64b8b99..ec7b252 100755 --- a/bin/clean-repo +++ b/bin/clean-repo @@ -9,7 +9,8 @@ BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") source "$BUILD_ROOT/lib/message-helpers.sh" # Repository configuration -ARCH_DIR="$BUILD_ROOT/output/x86_64" +ARCH=${ARCH:-x86_64} +ARCH_DIR="$BUILD_ROOT/pkgs.omarchy.org/$ARCH" KEEP_VERSIONS=2 # Function to clean old packages @@ -55,7 +56,7 @@ clean_packages() { local count=${#sorted_files[@]} if [[ $count -gt $keep ]]; then - print_success " -> Processing $pkgname ($count versions found)" + echo " -> Processing $pkgname ($count versions found)" # Remove old versions for ((i = $keep; i < $count; i++)); do @@ -64,9 +65,9 @@ clean_packages() { space_freed=$((space_freed + size)) if [[ "$dry_run" == true ]]; then - print_warning " Would remove: $file ($(numfmt --to=iec-i --suffix=B $size))" + echo " Would remove: $file ($(numfmt --to=iec-i --suffix=B $size))" else - print_error " Removing: $file ($(numfmt --to=iec-i --suffix=B $size))" + echo " Removing: $file ($(numfmt --to=iec-i --suffix=B $size))" rm -f "$file" rm -f "${file}.sig" 2>/dev/null || true fi @@ -79,14 +80,14 @@ clean_packages() { # Summary if [[ $total_removed -gt 0 ]]; then if [[ "$dry_run" == true ]]; then - print_info " -> Would remove $total_removed old package(s)" - print_info " -> Would free $(numfmt --to=iec-i --suffix=B $space_freed)" + echo " -> Would remove $total_removed old package(s)" + echo " -> Would free $(numfmt --to=iec-i --suffix=B $space_freed)" else - print_success " -> Removed $total_removed old package(s)" - print_success " -> Freed $(numfmt --to=iec-i --suffix=B $space_freed)" + echo " -> Removed $total_removed old package(s)" + echo " -> Freed $(numfmt --to=iec-i --suffix=B $space_freed)" fi else - print_warning " -> No old packages to remove" + echo " -> No old packages to remove" fi } @@ -121,7 +122,7 @@ remove_all_packages() { else rm -f *.pkg.tar.* rm -f *.db* *.files* - print_success "Removed $count package(s) and database files" + echo "Removed $count package(s) and database files" fi } @@ -134,11 +135,12 @@ show_disk_usage() { local total_size=$(du -sh . 2>/dev/null | cut -f1) local package_count=$(ls -1 *.pkg.tar.* 2>/dev/null | grep -v '\.sig$' | wc -l) - print_success " -> Total size: $total_size" - print_success " -> Package count: $package_count" + echo " -> Total size: $total_size" + echo " -> Package count: $package_count" if [[ $package_count -gt 0 ]]; then - print_success " -> Average package size: $(du -b *.pkg.tar.* 2>/dev/null | awk '{sum+=$1; count++} END {print sum/count}' | numfmt --to=iec-i --suffix=B)" + local avg_size=$(du -b *.pkg.tar.* 2>/dev/null | awk '{sum+=$1; count++} END {printf "%.0f", sum/count}') + echo " -> Average package size: $(numfmt --to=iec-i --suffix=B $avg_size 2>/dev/null || echo "N/A")" fi } @@ -216,7 +218,7 @@ main() { if [[ "$DRY_RUN" != true ]]; then print_info "Updating repository database..." "$BUILD_ROOT/bin/update-repo" >/dev/null 2>&1 && { - print_success " -> Database updated successfully!" + echo " -> Database updated successfully!" } || { print_warning " -> Database update failed (may need manual update)" } @@ -224,9 +226,6 @@ main() { show_disk_usage fi - - echo "" - print_success "Cleanup complete!" } # Run main function diff --git a/bin/list-packages b/bin/list-packages index 3c780df..ba99783 100755 --- a/bin/list-packages +++ b/bin/list-packages @@ -4,7 +4,8 @@ SCRIPT_DIR=$(realpath "${BASH_SOURCE[0]%/*}") BUILD_ROOT=$(realpath "$SCRIPT_DIR/..") -ARCH_DIR="$BUILD_ROOT/output/x86_64" +ARCH=${ARCH:-x86_64} +ARCH_DIR="$BUILD_ROOT/pkgs.omarchy.org/$ARCH" DB_FILE="$ARCH_DIR/omarchy.db.tar.zst" if [[ ! -f "$DB_FILE" ]]; then diff --git a/bin/promote-build b/bin/promote-build new file mode 100755 index 0000000..e57a9de --- /dev/null +++ b/bin/promote-build @@ -0,0 +1,97 @@ +#!/bin/bash +# Promote packages from build-output to pkgs.omarchy.org + +set -e + +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +source "$BUILD_ROOT/lib/message-helpers.sh" + +ARCH=${ARCH:-x86_64} +BUILD_OUTPUT_DIR="$BUILD_ROOT/build-output/$ARCH" +FINAL_OUTPUT_DIR="$BUILD_ROOT/pkgs.omarchy.org/$ARCH" + +print_header "Promote Build to Production" + +# Parse arguments +DRY_RUN=false +while [[ $# -gt 0 ]]; do + case $1 in + --arch) + ARCH="$2" + shift 2 + ;; + --dry-run) + DRY_RUN=true + shift + ;; + -h | --help) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --arch Target architecture (x86_64 or aarch64, default: x86_64)" + echo " --dry-run Show what would be copied without copying" + echo " -h, --help Show this help message" + echo "" + echo "This script promotes packages from build-output/ to pkgs.omarchy.org/" + exit 0 + ;; + *) + print_error "Unknown option: $1" + exit 1 + ;; + esac +done + +print_info "Build output: $BUILD_OUTPUT_DIR" +print_info "Final output: $FINAL_OUTPUT_DIR" + +# Check if build output exists +if [[ ! -d "$BUILD_OUTPUT_DIR" ]]; then + print_error "Build output directory not found: $BUILD_OUTPUT_DIR" + print_warning "Run bin/repo build first" + exit 1 +fi + +# Count packages in build output +cd "$BUILD_OUTPUT_DIR" +PACKAGE_COUNT=$(ls -1 *.pkg.tar.* 2>/dev/null | grep -v '\.sig$' | grep -v 'omarchy-build\.db' | wc -l) + +if [[ $PACKAGE_COUNT -eq 0 ]]; then + print_warning "No packages found in build output" + exit 0 +fi + +print_info "Found $PACKAGE_COUNT package(s) to promote" + +if [[ "$DRY_RUN" == true ]]; then + print_warning "DRY RUN MODE - No files will be copied" + echo "" + print_info "Packages that would be promoted:" + ls -1 *.pkg.tar.* 2>/dev/null | grep -v 'omarchy-build\.db' | while read -r pkg; do + echo " - $pkg" + done +else + echo "" + mkdir -p "$FINAL_OUTPUT_DIR" + + # Copy all package files (excluding build database) + COPIED=0 + for pkg_file in *.pkg.tar.*; do + # Skip build database files + [[ "$pkg_file" == omarchy-build.db* ]] && continue + + if [[ -f "$pkg_file" ]]; then + cp -v "$pkg_file" "$FINAL_OUTPUT_DIR/" + COPIED=$((COPIED + 1)) + fi + done + + echo "" + print_success "Promoted $COPIED file(s) to pkgs.omarchy.org" + + # Cleanup build directory after successful promotion + print_info "Cleaning up build directory..." + cd "$BUILD_OUTPUT_DIR" + rm -f *.pkg.tar.* omarchy-build.db* omarchy-build.files* + print_success "Build directory cleaned" +fi diff --git a/bin/release b/bin/release new file mode 100755 index 0000000..8510a8e --- /dev/null +++ b/bin/release @@ -0,0 +1,114 @@ +#!/bin/bash +# Run the complete release workflow: build, sign, promote, clean, sync + +set -e + +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +source "$BUILD_ROOT/lib/message-helpers.sh" + +ARCH=${ARCH:-x86_64} +SYNC_REMOTE="" +SKIP_PROD_CHECK=false + +print_header "Complete Release Workflow" + +echo "" +print_info "This will run the complete release workflow:" +echo " 1. Build packages" +echo " 2. Sign packages" +echo " 3. Promote to production" +echo " 4. Clean old versions" +echo " 5. Sync to remote" +echo "" + +# Parse arguments +BUILD_ARGS=() +while [[ $# -gt 0 ]]; do + case $1 in + --arch) + ARCH="$2" + BUILD_ARGS+=("--arch" "$2") + shift 2 + ;; + --package) + BUILD_ARGS+=("--package" "$2") + shift 2 + ;; + --sync-remote) + SYNC_REMOTE="$2" + shift 2 + ;; + --skip-prod-check) + SKIP_PROD_CHECK=true + shift + ;; + -h | --help) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --arch Target architecture (x86_64 or aarch64, default: x86_64)" + echo " --package Build only the specified package" + echo " --sync-remote Rclone remote for sync (default: pkgs.omarchy.org:omarchy-pkgs)" + echo " --skip-prod-check Skip production environment check during sync" + echo " -h, --help Show this help message" + echo "" + echo "This script runs the complete workflow:" + echo " build → sign → promote → clean → sync" + exit 0 + ;; + *) + print_error "Unknown option: $1" + exit 1 + ;; + esac +done + +# Step 1: Build +echo "" +print_info "Step 1/5: Building packages..." +"$BUILD_ROOT/bin/build" "${BUILD_ARGS[@]}" || { + print_error "Build failed" + exit 1 +} + +# Step 2: Sign +echo "" +print_info "Step 2/5: Signing packages..." +"$BUILD_ROOT/bin/sign" --arch "$ARCH" || { + print_error "Signing failed" + exit 1 +} + +# Step 3: Promote +echo "" +print_info "Step 3/5: Promoting to production..." +"$BUILD_ROOT/bin/promote-build" --arch "$ARCH" || { + print_error "Promotion failed" + exit 1 +} + +# Step 4: Clean (which also updates the repo) +echo "" +print_info "Step 4/5: Cleaning old versions..." +"$BUILD_ROOT/bin/clean-repo" || { + print_error "Cleaning failed" + exit 1 +} + +# Step 5: Sync +echo "" +print_info "Step 5/5: Syncing to remote..." +SYNC_ARGS=("pkgs.omarchy.org/$ARCH") +if [[ -n "$SYNC_REMOTE" ]]; then + SYNC_ARGS+=("--remote" "$SYNC_REMOTE") +fi +if [[ "$SKIP_PROD_CHECK" == true ]]; then + SYNC_ARGS+=("--skip-prod-check") +fi +"$BUILD_ROOT/bin/sync-repo" "${SYNC_ARGS[@]}" || { + print_error "Sync failed" + exit 1 +} + +echo "" +print_success "Release workflow completed successfully!" diff --git a/bin/remove-package b/bin/remove-package index 659db7d..764b18e 100755 --- a/bin/remove-package +++ b/bin/remove-package @@ -4,8 +4,10 @@ set -e # Get the directory of this script SCRIPT_DIR=$(realpath "${BASH_SOURCE[0]%/*}") BUILD_ROOT=$(realpath "$SCRIPT_DIR/..") +source "$BUILD_ROOT/lib/message-helpers.sh" -ARCH_DIR="$BUILD_ROOT/x86_64" +ARCH=${ARCH:-x86_64} +ARCH_DIR="$BUILD_ROOT/pkgs.omarchy.org/$ARCH" cd "$ARCH_DIR" if [[ -z "$1" ]]; then @@ -21,8 +23,18 @@ if [[ -z "$FILES" ]]; then exit 1 fi -# Confirm with gum -gum confirm "Remove package '$FILES'?" +# Confirm removal +echo "Package files to remove:" +for file in $FILES; do + echo " - $file" +done +echo "" +read -p "Remove package '$1'? (y/N) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + print_info "Removal cancelled" + exit 0 +fi # Remove from database repo-remove omarchy.db.tar.zst "$1" diff --git a/bin/repo b/bin/repo index 971cfa3..211be2a 100755 --- a/bin/repo +++ b/bin/repo @@ -11,30 +11,45 @@ BUILD_ROOT=$(realpath "$SCRIPT_DIR/..") # Source common functions source "$BUILD_ROOT/lib/message-helpers.sh" -# Setup logging +# Setup logging with timestamps LOG_DIR="$BUILD_ROOT/logs" mkdir -p "$LOG_DIR" -LOG_FILE="$LOG_DIR/repo.log" + +# Rotate logs first - keep only the 10 most recent +if [[ -d "$LOG_DIR" ]]; then + cd "$LOG_DIR" + # List logs by modification time (newest first), skip first 9, delete the rest + # This way after we create the new log, we'll have exactly 10 + ls -t repo_*.log 2>/dev/null | tail -n +10 | xargs -r rm -f + cd "$BUILD_ROOT" +fi + +TIMESTAMP=$(date +%Y%m%d_%H%M%S) +LOG_FILE="$LOG_DIR/repo_${TIMESTAMP}.log" # Show usage show_usage() { echo "Usage: $0 [options]" echo "" echo "Commands:" - echo " build Build AUR packages" + echo " release Run complete workflow: build → sign → promote → clean → sync" + echo " build Build AUR packages (unsigned)" + echo " sign Sign all packages in build-output" + echo " promote Promote build to production (build-output → pkgs.omarchy.org)" echo " update Update repository database" echo " clean Clean old package versions" echo " list List packages in repository" echo " remove Remove a specific package" echo " sync Sync repository to remote" echo "" - echo "Examples:" - echo " $0 build # Build all packages" - echo " $0 update --clean # Update database (clean rebuild)" - echo " $0 clean --dry-run # Preview cleanup" - echo " $0 list # List all packages" - echo " $0 remove package-name # Remove a package" - echo " $0 sync x86_64 # Sync to remote" + echo "Typical workflows:" + echo " $0 release # Complete release workflow" + echo "" + echo " $0 build # Manual step-by-step:" + echo " $0 sign" + echo " $0 promote" + echo " $0 clean" + echo " $0 sync pkgs.omarchy.org/x86_64" echo "" echo "For command-specific help, use:" echo " $0 --help" @@ -52,10 +67,22 @@ shift # Route to appropriate script with logging case $COMMAND in +release) + "$SCRIPT_DIR/release" "$@" 2>&1 | tee "$LOG_FILE" + exit ${PIPESTATUS[0]} + ;; build) "$SCRIPT_DIR/build" "$@" 2>&1 | tee "$LOG_FILE" exit ${PIPESTATUS[0]} ;; +sign) + "$SCRIPT_DIR/sign" "$@" 2>&1 | tee "$LOG_FILE" + exit ${PIPESTATUS[0]} + ;; +promote) + "$SCRIPT_DIR/promote-build" "$@" 2>&1 | tee "$LOG_FILE" + exit ${PIPESTATUS[0]} + ;; update) "$SCRIPT_DIR/update-repo" "$@" 2>&1 | tee "$LOG_FILE" exit ${PIPESTATUS[0]} diff --git a/bin/sign b/bin/sign new file mode 100755 index 0000000..0919891 --- /dev/null +++ b/bin/sign @@ -0,0 +1,129 @@ +#!/bin/bash +# Sign all packages in build-output + +set -e + +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +source "$BUILD_ROOT/lib/message-helpers.sh" + +ARCH=${ARCH:-x86_64} +BUILD_OUTPUT_DIR="$BUILD_ROOT/build-output/$ARCH" + +print_header "Sign Packages" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --arch) + ARCH="$2" + shift 2 + ;; + -h | --help) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --arch Target architecture (x86_64 or aarch64, default: x86_64)" + echo " -h, --help Show this help message" + echo "" + echo "This script signs all packages in build-output/" + exit 0 + ;; + *) + print_error "Unknown option: $1" + exit 1 + ;; + esac +done + +print_info "Build output: $BUILD_OUTPUT_DIR" + +# Check if build output exists +if [[ ! -d "$BUILD_OUTPUT_DIR" ]]; then + print_error "Build output directory not found: $BUILD_OUTPUT_DIR" + print_warning "Run bin/repo build first" + exit 1 +fi + +# Get GPG key from 1Password or environment +if [[ -z "$GPG_PRIVATE_KEY" ]]; then + print_info "Fetching GPG signing key from 1Password..." + GPG_PRIVATE_KEY=$(op document get "Omarchy GPG Private Key" --account=omarchy.1password.com) || { + print_error "Failed to fetch GPG key from 1Password" + exit 1 + } +fi + +# Get passphrase from 1Password or environment +if [[ -z "$GPG_PASSPHRASE" ]]; then + print_info "Fetching GPG key passphrase from 1Password..." + GPG_PASSPHRASE=$(op item get "Omarchy GPG Private Key" --account=omarchy.1password.com --fields password --reveal) || { + print_error "Failed to fetch GPG passphrase from 1Password" + exit 1 + } +fi + +# Import GPG key temporarily +print_info "Importing GPG signing key..." +echo "$GPG_PRIVATE_KEY" | gpg --batch --import 2>/dev/null || { + print_error "Failed to import signing key" + exit 1 +} + +# Get key ID +KEY_ID=$(gpg --list-secret-keys --keyid-format LONG 2>/dev/null | grep "sec" | head -1 | awk '{print $2}' | cut -d'/' -f2) + +if [[ -z "$KEY_ID" ]]; then + print_error "Could not extract key ID" + exit 1 +fi + +print_success "GPG signing key loaded: $KEY_ID" + +# Find all package files +cd "$BUILD_OUTPUT_DIR" +PACKAGE_FILES=$(ls -1 *.pkg.tar.zst 2>/dev/null || true) + +if [[ -z "$PACKAGE_FILES" ]]; then + print_warning "No packages found in build output" + exit 0 +fi + +PACKAGE_COUNT=$(echo "$PACKAGE_FILES" | wc -l) +print_info "Found $PACKAGE_COUNT package(s) to sign" + +echo "" + +# Sign all packages +SIGNED_COUNT=0 +FAILED_COUNT=0 + +for pkg_file in $PACKAGE_FILES; do + echo -n "Signing: $pkg_file ... " + + # Remove existing signature if present + rm -f "$pkg_file.sig" + + # Sign the package + if gpg --batch --yes --pinentry-mode loopback --passphrase "$GPG_PASSPHRASE" \ + --detach-sign --use-agent --no-armor --local-user "$KEY_ID" "$pkg_file" 2>/dev/null; then + echo "✓" + SIGNED_COUNT=$((SIGNED_COUNT + 1)) + else + echo "✗" + FAILED_COUNT=$((FAILED_COUNT + 1)) + fi +done + +echo "" + +# Summary +if [[ $FAILED_COUNT -eq 0 ]]; then + print_success "Successfully signed all $SIGNED_COUNT package(s)" +else + print_warning "Signed $SIGNED_COUNT package(s), failed $FAILED_COUNT" + exit 1 +fi + +# Clear GPG data +unset GPG_PRIVATE_KEY +unset GPG_PASSPHRASE diff --git a/bin/sync-repo b/bin/sync-repo index 41d549b..58730df 100755 --- a/bin/sync-repo +++ b/bin/sync-repo @@ -10,18 +10,18 @@ REMOTE="$DEFAULT_REMOTE" SKIP_PROD_CHECK=false # Parse arguments -if [ $# -eq 0 ]; then +if [ $# -eq 0 ] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then echo "Usage: $0 [options]" echo "" echo "Options:" - echo " --remote Specify rclone remote (default: $DEFAULT_REMOTE)" + echo " --remote Rclone remote destination (default: $DEFAULT_REMOTE)" echo " --skip-prod-check Skip production sync confirmation" echo "" echo "Examples:" - echo " $0 x86_64 # Sync to production (with confirmation)" - echo " $0 x86_64 --remote test:bucket # Sync to test remote" - echo " $0 x86_64 --skip-prod-check # Sync to production without confirmation" - exit 1 + echo " $0 pkgs.omarchy.org/x86_64 # Sync to production (with confirmation)" + echo " $0 pkgs.omarchy.org/x86_64 --remote dev-pkgs:/ # Sync to dev remote" + echo " $0 pkgs.omarchy.org/x86_64 --skip-prod-check # Sync without confirmation" + exit 0 fi DIRECTORY="$1" @@ -49,13 +49,15 @@ done if [[ "$REMOTE" == "$DEFAULT_REMOTE" ]] && [[ "$SKIP_PROD_CHECK" != true ]]; then print_warning "You are about to sync to PRODUCTION ($REMOTE)" echo "" - gum confirm "Are you sure you want to sync to production?" || { + read -p "Are you sure you want to sync to production? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then print_info "Sync cancelled" exit 0 - } + fi fi -print_info "Syncing to: $REMOTE" +print_info "Syncing to: $REMOTE/$(basename $DIRECTORY)" # First sync packages (excluding database files to ensure packages are uploaded first) # Use --ignore-existing to not overwrite different versions already on remote diff --git a/bin/update-repo b/bin/update-repo index 00e4f0a..5675251 100755 --- a/bin/update-repo +++ b/bin/update-repo @@ -10,7 +10,7 @@ source "$BUILD_ROOT/lib/message-helpers.sh" ARCH=${ARCH:-x86_64} BUILD_DIR="$BUILD_ROOT/build" -ARCH_DIR="$BUILD_ROOT/output/$ARCH" +ARCH_DIR="$BUILD_ROOT/pkgs.omarchy.org/$ARCH" # Function to update repository database using Docker update_database() { @@ -51,7 +51,7 @@ update_database() { # Run repo-add in Docker container docker run --rm \ -e ARCH="$ARCH" \ - -v "$BUILD_ROOT/output:/output" \ + -v "$BUILD_ROOT/pkgs.omarchy.org:/output" \ -v "$BUILD_DIR:/build:ro" \ omarchy-aur-builder:latest /build/update-repo.sh } diff --git a/build/build.sh b/build/build.sh index 94c61f5..b40f905 100755 --- a/build/build.sh +++ b/build/build.sh @@ -4,21 +4,55 @@ # Import GPG keys /build/import-gpg-keys.sh || exit 1 -# Add Omarchy repository to pacman.conf if database exists +# Setup directories ARCH=${ARCH:-x86_64} -OUTPUT_DIR="/output/$ARCH" +BUILD_OUTPUT_DIR="/build-output/$ARCH" +FINAL_OUTPUT_DIR="/pkgs.omarchy.org/$ARCH" -if [[ -f "$OUTPUT_DIR/omarchy.db.tar.zst" ]]; then - echo "==> Configuring Omarchy repository for dependency resolution..." +mkdir -p "$BUILD_OUTPUT_DIR" "$FINAL_OUTPUT_DIR" + +# Configure Omarchy repositories for dependency resolution +echo "==> Configuring Omarchy repositories for dependency resolution..." + +# Always add omarchy-build repo (for incremental builds) +# Packages in build-output are unsigned, so use SigLevel = Never +# Use the build-output dir as additional cache to avoid file copying issues +sudo tee -a /etc/pacman.conf > /dev/null < omarchy-build (priority 1): $BUILD_OUTPUT_DIR" + +# Initialize empty build database if it doesn't exist +cd "$BUILD_OUTPUT_DIR" +if [[ ! -f "omarchy-build.db.tar.zst" ]]; then + # Create an empty database + repo-add omarchy-build.db.tar.zst >/dev/null 2>&1 + ln -sf omarchy-build.db.tar.zst omarchy-build.db +else + # Database exists, check if we need to rebuild it from packages + if ls *.pkg.tar.* 2>/dev/null | grep -v '\.sig$' | grep -v 'omarchy-build\.db' | grep -q .; then + echo "==> Rebuilding build database from existing packages..." + ls *.pkg.tar.* | grep -v '\.sig$' | grep -v 'omarchy-build\.db' | xargs -r repo-add omarchy-build.db.tar.zst >/dev/null 2>&1 + ln -sf omarchy-build.db.tar.zst omarchy-build.db + fi +fi + +# Add omarchy repo if it has a database (stable packages) +if [[ -f "$FINAL_OUTPUT_DIR/omarchy.db.tar.zst" ]] || [[ -f "$FINAL_OUTPUT_DIR/omarchy.db" ]]; then sudo tee -a /etc/pacman.conf > /dev/null < Omarchy repository added to pacman.conf" -else - echo "==> No Omarchy repository database found (this is normal for first build)" + echo " -> omarchy (priority 2): $FINAL_OUTPUT_DIR" fi # Sync pacman database @@ -26,21 +60,20 @@ sudo pacman -Sy echo "==> Package Builder" echo "==> Target architecture: $ARCH" -echo "==> Output directory: $OUTPUT_DIR" - -mkdir -p "$OUTPUT_DIR" +echo "==> Build workspace: $BUILD_OUTPUT_DIR" +echo "==> Final output: $FINAL_OUTPUT_DIR" FAILED_PACKAGES="" SUCCESSFUL_PACKAGES="" SKIPPED_PACKAGES="" -# Get version from local repo database +# Get version from final output (production packages) get_local_version() { local pkg="$1" - if [[ -f "$OUTPUT_DIR/omarchy.db.tar.zst" ]]; then - local desc_file=$(tar -tf "$OUTPUT_DIR/omarchy.db.tar.zst" | grep "^${pkg}-[0-9r].*/desc$" | head -1) + if [[ -f "$FINAL_OUTPUT_DIR/omarchy.db.tar.zst" ]]; then + local desc_file=$(tar -tf "$FINAL_OUTPUT_DIR/omarchy.db.tar.zst" | grep "^${pkg}-[0-9r].*/desc$" | head -1) if [[ -n "$desc_file" ]]; then - tar -xOf "$OUTPUT_DIR/omarchy.db.tar.zst" "$desc_file" 2>/dev/null | + tar -xOf "$FINAL_OUTPUT_DIR/omarchy.db.tar.zst" "$desc_file" 2>/dev/null | awk '/%VERSION%/{getline; print; exit}' fi fi @@ -59,8 +92,8 @@ build_package() { cp -r "/pkgbuilds/$pkg" "$pkg" cd "/src/$pkg" || return 1 - # Get PKGBUILD version - local pkgbuild_version=$(bash -c 'source PKGBUILD; echo "${pkgver}-${pkgrel}"' 2>/dev/null) + # Get PKGBUILD version (including epoch if present) + local pkgbuild_version=$(bash -c 'source PKGBUILD; if [[ -n "$epoch" ]]; then echo "${epoch}:${pkgver}-${pkgrel}"; else echo "${pkgver}-${pkgrel}"; fi' 2>/dev/null) if [[ -z "$pkgbuild_version" ]]; then echo " ❌ Failed to read PKGBUILD version" @@ -68,14 +101,9 @@ build_package() { return 1 fi - # Check if already built + # Show version info (version check already done in first pass) local local_version=$(get_local_version "$pkg") - - if [[ "$local_version" == "$pkgbuild_version" ]]; then - echo " ✓ Up to date: $local_version - Skipping" - SKIPPED_PACKAGES="$SKIPPED_PACKAGES $pkg" - return 0 - elif [[ -n "$local_version" ]]; then + if [[ -n "$local_version" ]]; then echo " Update available: $local_version -> $pkgbuild_version" else echo " New package (version: $pkgbuild_version)" @@ -91,54 +119,251 @@ build_package() { done fi - # Build package with signing + # Build package without signing (signing is done separately) MAKEPKG_FLAGS="-scf --noconfirm" - # Only add sign flag if we have a GPG key configured - if grep -q "^GPGKEY=" ~/.makepkg.conf 2>/dev/null; then - GPG_KEY=$(grep "^GPGKEY=" ~/.makepkg.conf | cut -d'"' -f2) - echo " Using GPG key: $GPG_KEY" - MAKEPKG_FLAGS="$MAKEPKG_FLAGS --sign --key $GPG_KEY" - else - echo " No GPG key configured in makepkg.conf" - fi - if makepkg $MAKEPKG_FLAGS; then - # Copy to output (including signature files) + # Copy to build workspace for pkg_file in *.pkg.tar.*; do if [[ -f "$pkg_file" ]]; then - cp "$pkg_file" $OUTPUT_DIR/ + cp "$pkg_file" "$BUILD_OUTPUT_DIR/" fi done + + # If this package is a dependency of another package being built, + # update the build database so it's available via pacman + if [[ "${INSTALL_PACKAGES[$pkg]}" == "1" ]]; then + echo " Updating omarchy-build database (needed as dependency)..." + cd "$BUILD_OUTPUT_DIR" + + # Find the package file we just built (not .sig) + local new_pkg=$(ls -t ${pkg}-*.pkg.tar.* 2>/dev/null | grep -v '\.sig$' | head -1) + + if [[ -n "$new_pkg" ]]; then + echo " Adding $new_pkg to database..." + + # Add to omarchy-build database + repo-add omarchy-build.db.tar.zst "$new_pkg" 2>&1 | grep -E "==>|error" || true + ln -sf omarchy-build.db.tar.zst omarchy-build.db + + # Sync filesystem to ensure package file is fully written + sync + + # Refresh pacman databases so it sees the new package + echo " Refreshing pacman database..." + sudo pacman -Sy 2>&1 | grep -E "omarchy-build|error" || true + + # Verify the package is in the database + if pacman -Sl omarchy-build 2>/dev/null | grep -q "^omarchy-build $pkg "; then + echo " ✓ Package available in omarchy-build repo" + else + echo " ⚠ Warning: Package not found in omarchy-build repo" + fi + fi + + cd /src/$pkg + fi + echo " ✓ Successfully built $pkg" SUCCESSFUL_PACKAGES="$SUCCESSFUL_PACKAGES $pkg" return 0 else - echo " ❌ Failed to build $pkg" + echo " ❌ Makepkg failed for $pkg" + echo " DEBUG: Files in build directory:" + ls -lah *.pkg.tar.* 2>&1 | head -20 || echo " No package files found" FAILED_PACKAGES="$FAILED_PACKAGES $pkg" return 1 fi } +# Get package dependencies from PKGBUILD +get_package_deps() { + local pkg="$1" + local pkgbuild="/pkgbuilds/$pkg/PKGBUILD" + + if [[ ! -f "$pkgbuild" ]]; then + return + fi + + # Extract depends and makedepends, filter for packages in our pkgbuilds/ + ( + source "$pkgbuild" 2>/dev/null + echo "${depends[@]} ${makedepends[@]}" + ) | tr ' ' '\n' | while read -r dep; do + # Strip version constraints (e.g., 'hyprshade>=1.0' -> 'hyprshade') + dep=$(echo "$dep" | sed 's/[<>=].*$//') + # Check if this dependency exists in our pkgbuilds + if [[ -d "/pkgbuilds/$dep" ]]; then + echo "$dep" + fi + done +} + +# Simple dependency-aware build order +# Build packages with no internal deps first, then those that depend on them +build_order() { + local -a all_packages=() + local -a result=() + local -A package_deps_count=() + + # Collect all packages + for pkgdir in /pkgbuilds/*/; do + [[ ! -d "$pkgdir" ]] && continue + local pkg=$(basename "$pkgdir") + [[ ! -f "$pkgdir/PKGBUILD" ]] && continue + all_packages+=("$pkg") + + # Count internal dependencies + local dep_count=0 + while read -r dep; do + ((dep_count++)) + done < <(get_package_deps "$pkg") + package_deps_count[$pkg]=$dep_count + done + + # Sort: packages with fewer deps first + while IFS= read -r pkg; do + result+=("$pkg") + done < <( + for pkg in "${all_packages[@]}"; do + echo "${package_deps_count[$pkg]} $pkg" + done | sort -n | cut -d' ' -f2- + ) + + # Output in build order + printf '%s\n' "${result[@]}" +} + +# Check which packages need building (version check only) +check_needs_build() { + local pkg="$1" + local pkgbuild="/pkgbuilds/$pkg/PKGBUILD" + + [[ ! -f "$pkgbuild" ]] && return 1 + + # Get PKGBUILD version (including epoch if present) + local pkgbuild_version=$(cd "/pkgbuilds/$pkg" && bash -c 'source PKGBUILD; if [[ -n "$epoch" ]]; then echo "${epoch}:${pkgver}-${pkgrel}"; else echo "${pkgver}-${pkgrel}"; fi' 2>/dev/null) + [[ -z "$pkgbuild_version" ]] && return 1 + + # Check if already built + local local_version=$(get_local_version "$pkg") + + if [[ "$local_version" == "$pkgbuild_version" ]]; then + return 1 # Already up to date + else + return 0 # Needs building + fi +} + # Main execution cd /src TOTAL_COUNT=0 -# Build all packages in /pkgbuilds/ +echo "==> Checking which packages need building..." + +# First pass: determine which packages need building +PACKAGES_TO_BUILD=() for pkgdir in /pkgbuilds/*/; do [[ ! -d "$pkgdir" ]] && continue - pkg=$(basename "$pkgdir") - - # Skip if no PKGBUILD exists [[ ! -f "$pkgdir/PKGBUILD" ]] && continue - ((TOTAL_COUNT++)) - - build_package "$pkg" + if check_needs_build "$pkg"; then + PACKAGES_TO_BUILD+=("$pkg") + else + echo " ✓ $pkg - already up to date" + SKIPPED_PACKAGES="$SKIPPED_PACKAGES $pkg" + fi done +if [[ ${#PACKAGES_TO_BUILD[@]} -eq 0 ]]; then + echo "==> All packages are up to date!" +else + echo "==> ${#PACKAGES_TO_BUILD[@]} package(s) need building: ${PACKAGES_TO_BUILD[@]}" + echo "==> Determining build order based on dependencies..." + + # Second pass: order only the packages that need building + # Strategy: build packages with no unmet dependencies first + declare -A unmet_deps_count # How many dependencies does this package still need? + declare -A blocks_packages # Which packages are waiting for this one? + + # Count unmet dependencies for each package + for pkg in "${PACKAGES_TO_BUILD[@]}"; do + unmet_deps_count[$pkg]=0 + done + + # Build the dependency relationships + for pkg in "${PACKAGES_TO_BUILD[@]}"; do + while IFS= read -r dep; do + # Only care about deps that are being built in this run + for build_pkg in "${PACKAGES_TO_BUILD[@]}"; do + if [[ "$dep" == "$build_pkg" ]]; then + # pkg needs dep, so increment pkg's unmet count + ((unmet_deps_count[$pkg]++)) + # Track that dep blocks pkg from building + blocks_packages[$dep]="${blocks_packages[$dep]} $pkg" + fi + done + done < <(get_package_deps "$pkg") + done + + # Start with packages that have all dependencies met (count = 0) + ready_to_build=() + for pkg in "${PACKAGES_TO_BUILD[@]}"; do + if [[ ${unmet_deps_count[$pkg]} -eq 0 ]]; then + ready_to_build+=("$pkg") + fi + done + + # Build packages as dependencies become available + ORDERED_PACKAGES=() + while [[ ${#ready_to_build[@]} -gt 0 ]]; do + # Take the first ready package + current="${ready_to_build[0]}" + ready_to_build=("${ready_to_build[@]:1}") + ORDERED_PACKAGES+=("$current") + + # This package is now built, so packages waiting for it can proceed + for blocked_pkg in ${blocks_packages[$current]}; do + ((unmet_deps_count[$blocked_pkg]--)) + if [[ ${unmet_deps_count[$blocked_pkg]} -eq 0 ]]; then + ready_to_build+=("$blocked_pkg") + fi + done + done + + # Check for circular dependencies + if [[ ${#ORDERED_PACKAGES[@]} -ne ${#PACKAGES_TO_BUILD[@]} ]]; then + echo "ERROR: Circular dependency detected!" + exit 1 + fi + + echo "==> Build order: ${ORDERED_PACKAGES[@]}" + + # Determine which packages need to be installed for other packages being built + declare -A INSTALL_PACKAGES + for pkg in "${ORDERED_PACKAGES[@]}"; do + while IFS= read -r dep; do + [[ -z "$dep" ]] && continue + # Only install if it's being built in this run + for build_pkg in "${ORDERED_PACKAGES[@]}"; do + [[ "$dep" == "$build_pkg" ]] && INSTALL_PACKAGES["$dep"]=1 + done + done < <(get_package_deps "$pkg") + done + + if [[ ${#INSTALL_PACKAGES[@]} -gt 0 ]]; then + echo "==> Packages needed as dependencies: ${!INSTALL_PACKAGES[@]}" + fi + + # Build packages in dependency order + for pkg in "${ORDERED_PACKAGES[@]}"; do + ((TOTAL_COUNT++)) + build_package "$pkg" + done +fi + echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "==> Build Summary" @@ -164,7 +389,7 @@ if [[ -n "$FAILED_PACKAGES" ]]; then echo "" echo "==> Some packages failed to build" exit 1 -else - echo "" - echo "==> All packages processed successfully!" fi + +echo "" +echo "==> All packages processed successfully!"