From 2ffe4f811cde31f8a526b69d8357820392f9bf12 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Fri, 8 May 2026 01:02:35 -0400 Subject: [PATCH] Refactor --- .github/workflows/sync-aur-edge.yml | 70 ---- .github/workflows/sync-aur-shared.yml | 62 ---- .github/workflows/sync-aur.yml | 91 +++++ .gitignore | 31 ++ README.md | 200 ++++++++--- bin/add-package | 180 ++++++++++ bin/build | 49 ++- bin/check-versions | 114 +++--- bin/list-packages | 215 ++++++++++- bin/migrate-edge-to-stable | 6 +- bin/package-worktree | 288 +++++++++++++++ bin/release | 21 +- bin/repo | 4 +- bin/sync-aur | 498 +++++++++++++++++++------- build/build.sh | 133 +++---- helpers/package-metadata.sh | 184 ++++++++++ 16 files changed, 1650 insertions(+), 496 deletions(-) delete mode 100644 .github/workflows/sync-aur-edge.yml delete mode 100644 .github/workflows/sync-aur-shared.yml create mode 100644 .github/workflows/sync-aur.yml create mode 100755 bin/add-package create mode 100755 bin/package-worktree create mode 100644 helpers/package-metadata.sh diff --git a/.github/workflows/sync-aur-edge.yml b/.github/workflows/sync-aur-edge.yml deleted file mode 100644 index cfa1aed..0000000 --- a/.github/workflows/sync-aur-edge.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Sync AUR Packages (Edge) - -on: - schedule: - # Daily at 6 AM UTC - - cron: '0 6 * * *' - workflow_dispatch: - inputs: - packages: - description: 'Specific packages to sync (space-separated, leave empty for all)' - required: false - default: '' - -jobs: - sync: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Sync AUR packages (edge tier) - run: | - if [ -n "$PACKAGES" ]; then - ./bin/sync-aur --tier edge $PACKAGES - else - ./bin/sync-aur --tier edge - fi - env: - PACKAGES: ${{ github.event.inputs.packages }} - - - name: Check for changes - id: changes - run: | - if git diff --quiet; then - echo "has_changes=false" >> "$GITHUB_OUTPUT" - else - echo "has_changes=true" >> "$GITHUB_OUTPUT" - fi - - - name: Create Pull Request - if: steps.changes.outputs.has_changes == 'true' - uses: peter-evans/create-pull-request@v7 - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: 'chore: sync AUR packages (edge)' - title: 'chore: sync AUR packages (edge)' - body: | - Automated AUR package sync for edge tier. - - Please review the changes before merging. - branch: auto/sync-aur-edge - delete-branch: true - labels: automated - reviewers: ryanrhughes - - - name: Notify Basecamp on failure - if: failure() && env.BASECAMP_CHATBOT_URL != '' - env: - BASECAMP_CHATBOT_URL: ${{ secrets.BASECAMP_CHATBOT_URL }} - run: | - curl -s -o /dev/null \ - -H "Content-Type: application/json" \ - -d "$(jq -n --arg content \ - "🔴 AUR sync failed (edge)
View run" \ - '{content: $content}')" \ - "$BASECAMP_CHATBOT_URL" diff --git a/.github/workflows/sync-aur-shared.yml b/.github/workflows/sync-aur-shared.yml deleted file mode 100644 index ba2461b..0000000 --- a/.github/workflows/sync-aur-shared.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Sync AUR Packages (Shared) - -on: - schedule: - # Every 6 hours - - cron: '0 */6 * * *' - workflow_dispatch: - inputs: - packages: - description: 'Specific packages to sync (space-separated, leave empty for all)' - required: false - default: '' - -jobs: - sync: - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Sync AUR packages (shared tier) - run: | - if [ -n "$PACKAGES" ]; then - ./bin/sync-aur --tier shared $PACKAGES - else - ./bin/sync-aur --tier shared - fi - env: - PACKAGES: ${{ github.event.inputs.packages }} - - - name: Check for changes - id: changes - run: | - if git diff --quiet; then - echo "has_changes=false" >> "$GITHUB_OUTPUT" - else - echo "has_changes=true" >> "$GITHUB_OUTPUT" - fi - - - name: Commit and push changes - if: steps.changes.outputs.has_changes == 'true' - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add -A - git commit -m "chore: sync AUR packages (shared)" - git push - - - name: Notify Basecamp on failure - if: failure() && env.BASECAMP_CHATBOT_URL != '' - env: - BASECAMP_CHATBOT_URL: ${{ secrets.BASECAMP_CHATBOT_URL }} - run: | - curl -s -o /dev/null \ - -H "Content-Type: application/json" \ - -d "$(jq -n --arg content \ - "🔴 AUR sync failed (shared)
View run" \ - '{content: $content}')" \ - "$BASECAMP_CHATBOT_URL" diff --git a/.github/workflows/sync-aur.yml b/.github/workflows/sync-aur.yml new file mode 100644 index 0000000..b5d3fd3 --- /dev/null +++ b/.github/workflows/sync-aur.yml @@ -0,0 +1,91 @@ +name: Sync AUR Packages + +on: + schedule: + # Every 6 hours + - cron: '0 */6 * * *' + workflow_dispatch: + inputs: + packages: + description: 'Specific packages to sync (space-separated, leave empty for all)' + required: false + default: '' + +jobs: + sync: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Sync AUR packages + run: | + docker run --rm \ + -e PACKAGES="$PACKAGES" \ + -e HOST_UID="$(id -u)" \ + -e HOST_GID="$(id -g)" \ + -v "$PWD/bin:/workspace/bin:ro" \ + -v "$PWD/helpers:/workspace/helpers:ro" \ + -v "$PWD/pkgbuilds:/workspace/pkgbuilds" \ + -w /workspace \ + archlinux:base-devel bash -lc ' + set -euo pipefail + + pacman -Syu --noconfirm git jq + + groupadd -g "$HOST_GID" runner + useradd -m -u "$HOST_UID" -g "$HOST_GID" runner + chown -R runner:runner /workspace/pkgbuilds + + if [[ -n "${PACKAGES:-}" ]]; then + read -r -a package_args <<< "$PACKAGES" + runuser -u runner -- ./bin/sync-aur "${package_args[@]}" + else + runuser -u runner -- ./bin/sync-aur + fi + ' + env: + PACKAGES: ${{ github.event.inputs.packages }} + + - name: Check for changes + id: changes + run: | + if [ -z "$(git status --porcelain)" ]; then + echo "has_changes=false" >> "$GITHUB_OUTPUT" + else + echo "has_changes=true" >> "$GITHUB_OUTPUT" + fi + + - name: Create Pull Request + if: steps.changes.outputs.has_changes == 'true' + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: 'chore: sync AUR packages' + title: 'chore: sync AUR packages' + body: | + Automated AUR package sync. + + Package sync behavior is controlled by `.omarchy/package.json`. + branch: auto/sync-aur + delete-branch: true + labels: automated + reviewers: ryanrhughes + + - name: Notify Basecamp on failure + if: failure() && env.BASECAMP_CHATBOT_URL != '' + env: + BASECAMP_CHATBOT_URL: ${{ secrets.BASECAMP_CHATBOT_URL }} + run: | + curl -s -o /dev/null \ + -H "Content-Type: application/json" \ + -d "$(jq -n --arg content \ + "🔴 AUR sync failed
View run" \ + '{content: $content}')" \ + "$BASECAMP_CHATBOT_URL" diff --git a/.gitignore b/.gitignore index d6290ee..07b6c6a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,34 @@ src/ logs/ build-output/ pkgs.omarchy.org/ +pkgbuilds/*/.SRCINFO +pkgbuilds/*/.gitignore +pkgbuilds/*/src/ +pkgbuilds/*/pkg/ +pkgbuilds/*/*.pkg.tar* +pkgbuilds/*/*.tar +pkgbuilds/*/*.tar.* +pkgbuilds/*/*.tgz +pkgbuilds/*/*.gz +pkgbuilds/*/*.zip +pkgbuilds/*/*.deb +pkgbuilds/*/*.AppImage +pkgbuilds/*/*.log +pkgbuilds/*/*.sig +pkgbuilds/*/*.zst +pkgbuilds/*/*.in +pkgbuilds/*/.xml +pkgbuilds/*/*~ +pkgbuilds/*/*.swp +pkgbuilds/*/*.swo +pkgbuilds/*/.DS_Store +pkgbuilds/*/__pycache__/ +pkgbuilds/*/squashfs-root/ +pkgbuilds/*/starter/ +pkgbuilds/asdcontrol/asdcontrol/ +pkgbuilds/dropbox-cli/dropbox-cli-*.py +pkgbuilds/macbook12-spi-driver-dkms/macbook12-spi-driver/ +pkgbuilds/omazed/steps.txt +pkgbuilds/symfony-cli/symfony* +!pkgbuilds/symfony-cli/symfony-cli.install +pkgbuilds/yay/yay/ diff --git a/README.md b/README.md index abf490c..b4fa686 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,15 @@ Build system for the Omarchy Package Repository. Builds PKGBUILDs from local sou **Multi-Architecture**: Supports both x86_64 and aarch64 (ARM64). ## PKGBUILDs -There are 3 folders housing PKGBUILD that drive what is ultimately on the respective repos. -- `edge` - Built and pushed to the edge repo. These are synced daily with AUR if they're set to mirror it. -- `stable` - Built and pushed to the stable repo. These are only synced manually when stable is bumped. -- `shared` - Built and pushed to both stable and edge repo every 6hrs. +Each package lives directly under `pkgbuilds//` and carries Omarchy metadata in `.omarchy/package.json`. + +The filesystem no longer encodes release policy. Instead: + +- all packages build for `edge` +- packages with `"release_ring": "fast"` also build directly for `stable` +- all other packages reach `stable` by promoting tested edge artifacts with `bin/repo migrate` +- AUR sync behavior is controlled by `source`, `sync`, `aur`, patches, and hooks in `.omarchy/` ## Prerequisites ### aarch64 Builds (Optional) @@ -54,6 +58,9 @@ bin/repo release --arch aarch64 # Force build specific package (useful for debugging failures) bin/repo release --package omarchy-nvim + +# Show what would build without signing/promoting/syncing +bin/repo release --dry-run ``` ### Step-by-Step @@ -83,10 +90,13 @@ bin/repo build # All packages (x86_64, edge) bin/repo build --arch aarch64 # ARM64 bin/repo build --mirror stable # Stable mirror bin/repo build --package yay cursor-bin # Specific packages +bin/repo build --dry-run # Show what would build ``` **Output**: Unsigned `.pkg.tar.zst` in `build-output/`. Only builds packages that are newer than what is in the repository. +Use `--dry-run` to show the build plan without running `makepkg`. + ### Sign ```bash @@ -123,7 +133,7 @@ bin/repo update # Update database Updates the repository database (adding the newest version of each package). Run this after `promote` or `clean`. -### Sync +### Sync Repository ```bash bin/repo sync # Sync current arch/mirror @@ -132,30 +142,56 @@ bin/repo sync --arch aarch64 # Sync ARM64 bin/repo sync --skip-prod-check # No confirmation ``` -Syncs to remote server using rclone based on the configured mirror and architecture. +Syncs package repositories to the remote server using rclone based on the configured mirror and architecture. + +### Sync AUR PKGBUILDs + +```bash +bin/sync-aur # Sync all AUR packages with sync enabled +bin/sync-aur yay v4l2-relayd # Sync specific packages +``` + +AUR sync is metadata-driven. It preserves `.omarchy/`, replaces the package root with AUR contents, applies `.omarchy/patches/*.patch`, runs `.omarchy/post-sync.sh` when present, applies pkgrel metadata, removes AUR-only `.SRCINFO` and `.gitignore` files, and records `upstream_commit`. ### Other ```bash -bin/repo migrate --arch x86_64 # Migrate missing packages edge -> stable, then clean + update +bin/repo migrate --arch x86_64 # Promote tested edge artifacts -> stable, then clean + update bin/repo migrate --dry-run # Preview migration and cleanup -bin/repo list # List packages -bin/repo remove # Remove package -bin/clean-docker # Clear Docker images/cache (forces fresh rebuild) +bin/repo list # List package metadata +bin/add-package # Add an AUR/local package with metadata +bin/package-worktree # Create upstream/patched/current scratch workspace +bin/repo remove # Remove package +bin/clean-docker # Clear Docker images/cache (forces fresh rebuild) +``` + +### Package Metadata Tools + +```bash +bin/add-package yay # Add AUR package, create metadata, sync from AUR +bin/add-package spotify --fast # Add package to the fast release ring +bin/add-package foo --no-sync # Sync once, then mark AUR sync disabled +bin/add-package my-package --local # Create local package metadata + +bin/repo list # Table view of source package metadata +bin/repo list --json # Agent/script-friendly JSON +bin/repo list --repo --mirror stable # List packages in a published repo database + +bin/package-worktree v4l2-relayd # Create upstream/patched/current scratch workspace ``` ## Directory Structure ``` omarchy-pkgs/ -├── pkgbuilds/ # Source PKGBUILDs (tiered) -│ ├── stable/ # Manual promotion only, builds for stable -│ ├── edge/ # Daily sync w/ PR review, builds for edge only -│ └── shared/ # 6-hourly sync, auto-merge, builds for BOTH +├── pkgbuilds/ # Source PKGBUILDs +│ └── package-name/ +│ ├── PKGBUILD +│ └── .omarchy/ +│ ├── package.json # Source/sync/release metadata +│ ├── patches/ # Omarchy patches reapplied after AUR sync +│ └── post-sync.sh # Optional dynamic post-sync customization hook ├── build/ -│ └── packages/ -│ ├── edge.packages # Package list for daily sync (PR review) -│ └── shared.packages # Package list for 6-hourly sync (auto-merge) ├── build-output/ # Unsigned packages (temporary) │ ├── edge/ │ │ ├── x86_64/ @@ -173,66 +209,121 @@ omarchy-pkgs/ └── bin/ # CLI tools (on host) ``` -## Package Tiers +## Package Metadata -Packages are organized into three tiers based on their release cadence: +Each source package has Omarchy metadata at `pkgbuilds//.omarchy/package.json`. -| Tier | Directory | Sync Frequency | Review | Builds To | -|------|-----------|----------------|--------|-----------| -| **Stable** | `pkgbuilds/stable/` | Manual | N/A | stable only | -| **Edge** | `pkgbuilds/edge/` | Daily | PR required | edge only | -| **Shared** | `pkgbuilds/shared/` | Every 6 hours | Auto-merge | edge AND stable | +Minimal examples: + +```json +{ "source": "aur" } +``` + +```json +{ "source": "aur", "sync": false } +``` + +```json +{ "source": "aur", "release_ring": "fast" } +``` + +```json +{ "source": "local" } +``` + +```json +{ "source": "aur", "pkgrel": { "suffix": 1 } } +``` + +Fields: + +- `source`: `aur` or `local` +- `sync`: optional for AUR packages; defaults to `true`. Set `false` for AUR-origin packages that Omarchy maintains manually. +- `aur`: optional AUR package name when it differs from the local package directory, usually for split packages. +- `release_ring`: optional. `fast` means the package is built directly for stable as well as edge. Packages without a ring build in edge and reach stable through tested artifact promotion (`bin/repo migrate`). +- `pkgrel`: optional Omarchy pkgrel suffix for a version-pinned rebuild bump. This emits `.` instead of replacing AUR's pkgrel. `offset` can be used only when preserving monotonic upgrades from old absolute pkgrel bumps. The metadata is removed automatically when AUR sync changes `pkgver`; the current package version is read from the checked-in PKGBUILD, so the version is not duplicated in JSON. +- `upstream_commit`: set by `bin/sync-aur` for AUR packages. Used by `bin/package-worktree` to recreate the exact raw AUR package that Omarchy last synced. ### Build Matrix -- **Edge builds** (`--mirror edge`): `pkgbuilds/edge/*` + `pkgbuilds/shared/*` -- **Stable builds** (`--mirror stable`): `pkgbuilds/stable/*` + `pkgbuilds/shared/*` +- **Edge builds** (`--mirror edge`): all packages in `pkgbuilds/*` +- **Stable builds** (`--mirror stable`): packages with `"release_ring": "fast"` +- **Stable promotion** (`bin/repo migrate`): copies tested edge artifacts into stable ## Adding Packages -### From AUR (Edge - Daily Sync, PR Review) +### From AUR ```bash -# Add to edge package list -echo "package-name" >> build/packages/edge.packages - -# Sync PKGBUILD -bin/sync-aur --tier edge package-name - -# Build and release +bin/add-package package-name bin/repo release --package package-name ``` -### From AUR (Shared - 6-Hourly Sync, Auto-Merge) +### From AUR, fast release ring ```bash -# Add to shared package list -echo "package-name" >> build/packages/shared.packages - -# Sync PKGBUILD -bin/sync-aur --tier shared package-name - -# Build and release (will publish to both edge and stable) +bin/add-package package-name --fast bin/repo release --package package-name bin/repo release --mirror stable --package package-name ``` -### Local Patches for AUR Packages - -Create `pkgbuilds//package-name/patches/*.patch` to maintain modifications across AUR syncs: +### AUR-origin, manually maintained by Omarchy ```bash -cd pkgbuilds/edge/package-name -# Make changes -git diff > patches/my-fix.patch -# Next sync will auto-apply patches +bin/add-package package-name --no-sync ``` +### Local Customizations for AUR Packages + +For static changes, create `pkgbuilds/package-name/.omarchy/patches/*.patch` to maintain modifications across AUR syncs. + +The recommended workflow is to use a scratch workspace: + +```bash +bin/package-worktree package-name --dir /tmp/package-name-worktree +``` + +This creates: + +```text +upstream/ # raw AUR package at upstream_commit +patched/ # AUR + existing Omarchy .omarchy customizations +current/ # current checked-in package directory +``` + +Patch-authoring flow: + +```bash +# 1. Make the intended change in pkgbuilds/package-name/ + +# 2. Recreate the scratch workspace +bin/package-worktree package-name --dir /tmp/package-name-worktree + +# 3. Inspect drift from patched -> current +# For multi-file changes, inspect this and split into focused patches. +diff -ruN /tmp/package-name-worktree/patched /tmp/package-name-worktree/current + +# For a single PKGBUILD change, write a patch like this: +mkdir -p pkgbuilds/package-name/.omarchy/patches +( + cd /tmp/package-name-worktree/patched + diff -u --label a/PKGBUILD --label b/PKGBUILD \ + PKGBUILD /tmp/package-name-worktree/current/PKGBUILD || true +) > pkgbuilds/package-name/.omarchy/patches/my-fix.patch + +# 4. Verify the package is reproducible from AUR + .omarchy +bin/sync-aur package-name +bin/package-worktree package-name --dir /tmp/package-name-check +diff -ruN /tmp/package-name-check/patched /tmp/package-name-check/current +``` + +For dynamic changes that depend on the current upstream version, add `pkgbuilds/package-name/.omarchy/post-sync.sh`. The hook runs after the AUR package is copied into a temporary worktree and before the Omarchy pkgrel suffix is applied. After patches/hooks/metadata pkgrel overrides, `bin/sync-aur` removes AUR-only `.SRCINFO` and `.gitignore` files before writing the package back. + ### Custom Package ```bash -mkdir pkgbuilds/edge/my-package -# Add PKGBUILD and files +bin/add-package my-package --local --scaffold +# Fill in PKGBUILD and package files bin/repo release --package my-package ``` @@ -286,14 +377,13 @@ The repository includes GitHub workflows and systemd services for automated rele #### GitHub Workflows -1. **sync-aur-edge.yml** (Daily at 6:00 AM UTC): Syncs edge packages from AUR, creates PR for review -2. **sync-aur-shared.yml** (Every 6 hours): Syncs shared packages from AUR, auto-commits to master +1. **sync-aur.yml** (Every 6 hours): Syncs AUR packages according to `.omarchy/package.json` and opens a PR when changes are found. #### Systemd Services 1. **check-versions** (Every 6 hours at :30): Pulls latest from git, compares PKGBUILD versions to published versions, creates state files if builds are needed -2. **auto-release-edge** (Every 6 hours at +1:00): If state file exists, builds edge packages -3. **auto-release-stable** (Every 6 hours at +1:00): If state file exists, builds stable packages (runs in parallel with edge) +2. **auto-release-edge** (Every 6 hours at +1:00): If state file exists, builds all edge packages that need updates +3. **auto-release-stable** (Every 6 hours at +1:00): If state file exists, builds `release_ring=fast` packages for stable (runs in parallel with edge) State files are stored in `/root/.state/`: - `.sync-needed-edge` diff --git a/bin/add-package b/bin/add-package new file mode 100755 index 0000000..0fef98a --- /dev/null +++ b/bin/add-package @@ -0,0 +1,180 @@ +#!/bin/bash +set -euo pipefail + +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +source "$BUILD_ROOT/helpers/message-helpers.sh" +source "$BUILD_ROOT/helpers/paths.sh" + +PACKAGE="" +SOURCE="aur" +SYNC="true" +RELEASE_RING="" +AUR_PACKAGE="" +FORCE=false +SCAFFOLD=false + +usage() { + cat < [OPTIONS] + +Create pkgbuilds/ with Omarchy metadata. AUR packages are synced +immediately after metadata is written. + +Options: + --source Package source (default: aur) + --local Shortcut for --source local + --aur AUR package name when different from local directory + --no-sync For AUR packages, mark sync disabled after initial setup + --fast Put package in the fast release ring + --release-ring Release ring (currently: fast) + --scaffold For local packages, create a starter PKGBUILD + --force Overwrite existing .omarchy/package.json + -h, --help Show this help message + +Examples: + $0 yay + $0 spotify --fast + $0 signal-desktop --no-sync + $0 omarchy-zsh --local --scaffold +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --source) + SOURCE="$2" + shift 2 + ;; + --local) + SOURCE="local" + shift + ;; + --aur) + AUR_PACKAGE="$2" + shift 2 + ;; + --no-sync) + SYNC="false" + shift + ;; + --fast) + RELEASE_RING="fast" + shift + ;; + --release-ring) + RELEASE_RING="$2" + shift 2 + ;; + --scaffold) + SCAFFOLD=true + shift + ;; + --force) + FORCE=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + --*) + print_error "Unknown option: $1" + exit 1 + ;; + *) + if [[ -n "$PACKAGE" ]]; then + print_error "Only one package can be added at a time" + exit 1 + fi + PACKAGE="$1" + shift + ;; + esac +done + +if [[ -z "$PACKAGE" ]]; then + print_error "Package name required" + usage + exit 1 +fi + +case "$SOURCE" in + aur|local) ;; + *) print_error "Invalid source: $SOURCE (must be aur or local)"; exit 1 ;; +esac + +case "$RELEASE_RING" in + ""|fast) ;; + *) print_error "Invalid release ring: $RELEASE_RING (currently only 'fast' is supported)"; exit 1 ;; +esac + +PACKAGE_DIR="$PKGBUILDS_DIR/$PACKAGE" +OMARCHY_DIR="$PACKAGE_DIR/.omarchy" +METADATA_FILE="$OMARCHY_DIR/package.json" + +if [[ -f "$METADATA_FILE" && "$FORCE" != true ]]; then + print_error "Package metadata already exists: $METADATA_FILE" + print_info "Use --force to overwrite it" + exit 1 +fi + +print_header "Add Package" +print_info "Package: $PACKAGE" +print_info "Source: $SOURCE" +[[ -n "$RELEASE_RING" ]] && print_info "Release ring: $RELEASE_RING" + +mkdir -p "$OMARCHY_DIR" + +jq_args=(--arg source "$SOURCE") +jq_filter='{source: $source}' + +if [[ "$SOURCE" == "aur" && -n "$AUR_PACKAGE" && "$AUR_PACKAGE" != "$PACKAGE" ]]; then + jq_args+=(--arg aur "$AUR_PACKAGE") + jq_filter="$jq_filter + {aur: $aur}" +fi + +if [[ "$SOURCE" == "aur" && "$SYNC" == "false" ]]; then + jq_filter="$jq_filter + {sync: false}" +fi + +if [[ -n "$RELEASE_RING" ]]; then + jq_args+=(--arg release_ring "$RELEASE_RING") + jq_filter="$jq_filter + {release_ring: $release_ring}" +fi + +jq -n "${jq_args[@]}" "$jq_filter" > "$METADATA_FILE" +print_success "Wrote $METADATA_FILE" + +if [[ "$SOURCE" == "aur" ]]; then + if [[ "$SYNC" == "false" ]]; then + # Temporarily sync once, then restore sync=false so future automated syncs skip it. + tmpfile=$(mktemp) + jq 'del(.sync)' "$METADATA_FILE" > "$tmpfile" + mv "$tmpfile" "$METADATA_FILE" + "$BUILD_ROOT/bin/sync-aur" "$PACKAGE" + jq '. + {sync: false}' "$METADATA_FILE" > "$tmpfile" + mv "$tmpfile" "$METADATA_FILE" + print_info "AUR sync disabled for future runs" + else + "$BUILD_ROOT/bin/sync-aur" "$PACKAGE" + fi +else + if [[ "$SCAFFOLD" == true && ! -f "$PACKAGE_DIR/PKGBUILD" ]]; then + cat > "$PACKAGE_DIR/PKGBUILD" < Target architecture (x86_64 or aarch64, default: x86_64)" echo " --mirror Mirror to use (edge or stable, default: edge)" echo " --package Build only the specified package(s) (space-separated)" + echo " --dry-run Show what would build without running makepkg" echo " -h, --help Show this help message" echo "" - echo "This script builds packages from pkgbuilds/ directories based on mirror:" - echo " --mirror edge: builds from pkgbuilds/edge/ + pkgbuilds/shared/" - echo " --mirror stable: builds from pkgbuilds/shared/ only (edge pkgs promoted via 'repo migrate')" + echo "This script builds packages from pkgbuilds/ based on .omarchy/package.json:" + echo " --mirror edge: builds all packages" + echo " --mirror stable: builds packages with release_ring=fast only" echo "" echo "Or build specific packages with --package:" - echo " Package names should match directories in the relevant pkgbuilds/ tier" + echo " Package names should match directories in pkgbuilds/" echo "" echo "Examples:" echo " $0 --arch aarch64" @@ -80,6 +81,27 @@ print_info "Mirror: $MIRROR" print_info "Build workspace: $BUILD_OUTPUT_DIR" print_info "Final output: $REPO_DIR" +if [[ "$DRY_RUN" == true ]]; then + print_warning "DRY RUN MODE - build plan only; no Docker or makepkg will run" + ARCH="$ARCH" \ + MIRROR="$MIRROR" \ + PACKAGES="$PACKAGES" \ + DRY_RUN=true \ + PKGBUILDS_DIR="$PKGBUILDS_DIR" \ + BUILD_OUTPUT_DIR="$BUILD_OUTPUT_DIR" \ + FINAL_OUTPUT_DIR="$REPO_DIR" \ + HELPERS_DIR="$BUILD_ROOT/helpers" \ + SRC_DIR="$SRC_DIR" \ + "$BUILD_ROOT/build/build.sh" + exit $? +fi + +# Create directories if they don't exist +mkdir -p "$BUILD_OUTPUT_DIR" "$REPO_DIR" "$SRC_DIR" + +# Check Docker is available +check_docker + # Setup QEMU for aarch64 builds on x86_64 hosts if [[ "$(uname -m)" == "x86_64" && "$ARCH" == "aarch64" ]]; then # Check if QEMU is already working @@ -104,7 +126,7 @@ fi # Build/update the Docker image build_docker_image "$BUILD_DIR" "$ARCH" "$MIRROR" -print_info "Running AUR package build..." +print_info "Running package build..." # Create output directories if they don't exist mkdir -p "$BUILD_OUTPUT_DIR" @@ -123,6 +145,7 @@ DOCKER_ARGS=( -v "$BUILD_ROOT/build-output:/build-output" -v "$BUILD_ROOT/pkgs.omarchy.org:/pkgs.omarchy.org" -v "$BUILD_DIR:/build:ro" + -v "$BUILD_ROOT/helpers:/helpers:ro" -v "$BUILD_ROOT/pkgbuilds:/pkgbuilds:ro" ) @@ -137,8 +160,8 @@ BUILD_RESULT=$? # Summary echo "" if [[ $BUILD_RESULT -eq 0 ]]; then - print_success "AUR build completed successfully!" + print_success "Build completed successfully!" else - print_warning "Some AUR packages failed (see details above)" + print_warning "Some packages failed (see details above)" exit $BUILD_RESULT fi diff --git a/bin/check-versions b/bin/check-versions index 754daeb..9498c44 100755 --- a/bin/check-versions +++ b/bin/check-versions @@ -7,30 +7,29 @@ set -e BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") source "$BUILD_ROOT/helpers/message-helpers.sh" source "$BUILD_ROOT/helpers/paths.sh" +source "$BUILD_ROOT/helpers/package-metadata.sh" STATE_DIR="${OMARCHY_STATE_DIR:-/root/.state}" ARCH="${ARCH:-x86_64}" -# Ensure state directory exists mkdir -p "$STATE_DIR" print_header "Package Version Check" -# Get version from repo database get_repo_version() { local pkg="$1" local mirror="$2" local repo_db="$BUILD_ROOT/pkgs.omarchy.org/$mirror/$ARCH/omarchy.db.tar.zst" - + if [[ -f "$repo_db" ]]; then - local desc_file=$(tar -tf "$repo_db" 2>/dev/null | grep "^${pkg}-[0-9r].*/desc$" | head -1) + local desc_file + desc_file=$(tar -tf "$repo_db" 2>/dev/null | grep "^${pkg}-[0-9r].*/desc$" | head -1) if [[ -n "$desc_file" ]]; then tar -xOf "$repo_db" "$desc_file" 2>/dev/null | awk '/%VERSION%/{getline; print; exit}' fi fi } -# Get version from PKGBUILD get_pkgbuild_version() { local pkgdir="$1" if [[ -f "$pkgdir/PKGBUILD" ]]; then @@ -38,88 +37,57 @@ get_pkgbuild_version() { fi } -# Check if package needs building for a mirror check_package() { local pkg="$1" local pkgdir="$2" local mirror="$3" - - local pkgbuild_version=$(get_pkgbuild_version "$pkgdir") - local repo_version=$(get_repo_version "$pkg" "$mirror") - + + local pkgbuild_version repo_version + pkgbuild_version=$(get_pkgbuild_version "$pkgdir") + repo_version=$(get_repo_version "$pkg" "$mirror") + if [[ -z "$pkgbuild_version" ]]; then - return 1 # Can't read version, skip + return 1 fi - + if [[ "$pkgbuild_version" != "$repo_version" ]]; then - return 0 # Needs building + return 0 fi - - return 1 # Up to date + + return 1 } -edge_needs_build=false -stable_needs_build=false -edge_packages=() -stable_packages=() +check_mirror() { + local mirror="$1" + local state_file="$STATE_DIR/.sync-needed-$mirror" + local needs_build=false + local packages=() -print_info "Checking edge packages..." + print_info "Checking $mirror packages..." -# Check edge/ packages -> only affects edge -for pkgdir in "$BUILD_ROOT/pkgbuilds/edge"/*/; do - [[ ! -d "$pkgdir" ]] && continue - pkg=$(basename "$pkgdir") - [[ ! -f "$pkgdir/PKGBUILD" ]] && continue - - if check_package "$pkg" "$pkgdir" "edge"; then - edge_needs_build=true - edge_packages+=("$pkg") + while IFS= read -r pkg; do + local pkgdir="$PKGBUILDS_DIR/$pkg" + if check_package "$pkg" "$pkgdir" "$mirror"; then + needs_build=true + packages+=("$pkg") + fi + done < <(packages_for_mirror "$mirror") + + echo "" + + if [[ "$needs_build" == true ]]; then + touch "$state_file" + print_success "${mirror^} needs building (${#packages[@]} packages)" + print_info "Packages: ${packages[*]}" + print_info "State file created: $state_file" + else + print_info "${mirror^} is up to date" fi -done -print_info "Checking shared packages..." + echo "" +} -# Check shared/ packages -> affects BOTH edge and stable -for pkgdir in "$BUILD_ROOT/pkgbuilds/shared"/*/; do - [[ ! -d "$pkgdir" ]] && continue - pkg=$(basename "$pkgdir") - [[ ! -f "$pkgdir/PKGBUILD" ]] && continue - - # Check against edge - if check_package "$pkg" "$pkgdir" "edge"; then - edge_needs_build=true - edge_packages+=("$pkg") - fi - - # Check against stable - if check_package "$pkg" "$pkgdir" "stable"; then - stable_needs_build=true - stable_packages+=("$pkg") - fi -done +check_mirror edge +check_mirror stable -echo "" - -# Drop state files as needed -if [[ "$edge_needs_build" == true ]]; then - touch "$STATE_DIR/.sync-needed-edge" - print_success "Edge needs building (${#edge_packages[@]} packages)" - print_info "Packages: ${edge_packages[*]}" - print_info "State file created: $STATE_DIR/.sync-needed-edge" -else - print_info "Edge is up to date" -fi - -echo "" - -if [[ "$stable_needs_build" == true ]]; then - touch "$STATE_DIR/.sync-needed-stable" - print_success "Stable needs building (${#stable_packages[@]} packages)" - print_info "Packages: ${stable_packages[*]}" - print_info "State file created: $STATE_DIR/.sync-needed-stable" -else - print_info "Stable is up to date" -fi - -echo "" print_success "Version check complete!" diff --git a/bin/list-packages b/bin/list-packages index 0fec794..a2f5d5b 100755 --- a/bin/list-packages +++ b/bin/list-packages @@ -1,15 +1,66 @@ #!/bin/bash +set -euo pipefail -# Get the directory of this script SCRIPT_DIR=$(realpath "${BASH_SOURCE[0]%/*}") BUILD_ROOT=$(realpath "$SCRIPT_DIR/..") source "$BUILD_ROOT/helpers/message-helpers.sh" source "$BUILD_ROOT/helpers/paths.sh" +source "$BUILD_ROOT/helpers/package-metadata.sh" + +OUTPUT_JSON=false +LIST_REPO=false +SOURCE_FILTER="" +SYNC_FILTER="" +RING_FILTER="" + +usage() { + cat </.omarchy/package.json. + +Options: + --json Output JSON for agents/scripts + --source Filter by source + --sync Filter by AUR sync state + --release-ring Filter by release ring, e.g. fast + --repo List packages from the published repo database instead + --arch Target architecture for --repo (default: x86_64) + --mirror Mirror for --repo (edge or stable, default: edge) + -h, --help Show this help message + +Examples: + $0 + $0 --release-ring fast + $0 --source aur --sync false + $0 --json + $0 --repo --mirror stable +EOF +} -# Parse arguments while [[ $# -gt 0 ]]; do case $1 in + --json) + OUTPUT_JSON=true + shift + ;; + --repo) + LIST_REPO=true + shift + ;; + --source) + SOURCE_FILTER="$2" + shift 2 + ;; + --sync) + SYNC_FILTER="$2" + shift 2 + ;; + --release-ring) + RING_FILTER="$2" + shift 2 + ;; --arch) ARCH="$2" update_arch_paths @@ -21,12 +72,7 @@ while [[ $# -gt 0 ]]; do shift 2 ;; -h | --help) - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Options:" - echo " --arch Target architecture (x86_64 or aarch64, default: x86_64)" - echo " --mirror Mirror to use (edge or stable, default: edge)" - echo " -h, --help Show this help message" + usage exit 0 ;; *) @@ -36,18 +82,149 @@ while [[ $# -gt 0 ]]; do esac done -DB_FILE="$REPO_DIR/omarchy.db.tar.zst" +list_repo_packages() { + local db_file="$REPO_DIR/omarchy.db.tar.zst" -if [[ ! -f "$DB_FILE" ]]; then - echo "Repository database not found: $DB_FILE" - exit 1 + if [[ ! -f "$db_file" ]]; then + echo "Repository database not found: $db_file" >&2 + exit 1 + fi + + tar -xOf "$db_file" --wildcards "*/desc" 2>/dev/null | + awk '/%NAME%/{getline; name=$0} + /%VERSION%/{getline; print name "-" $0}' | + sort +} + +get_pkgbuild_version() { + local pkgdir="$1" + + if [[ -f "$pkgdir/PKGBUILD" ]]; then + (cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; if [[ -n "$epoch" ]]; then echo "${epoch}:${pkgver}-${pkgrel}"; else echo "${pkgver}-${pkgrel}"; fi') || true + fi +} + +patch_count() { + local pkgdir="$1" + local patches_dir="$pkgdir/.omarchy/patches" + + [[ -d "$patches_dir" ]] || { echo 0; return; } + find "$patches_dir" -maxdepth 1 -type f -name '*.patch' | wc -l +} + +post_sync_hook_enabled() { + local pkgdir="$1" + + [[ -f "$pkgdir/.omarchy/post-sync.sh" ]] && echo true || echo false +} + +package_matches_filters() { + local source="$1" + local sync="$2" + local ring="$3" + + [[ -z "$SOURCE_FILTER" || "$source" == "$SOURCE_FILTER" ]] || return 1 + [[ -z "$RING_FILTER" || "$ring" == "$RING_FILTER" ]] || return 1 + + if [[ -n "$SYNC_FILTER" ]]; then + [[ "$source" == "aur" ]] || return 1 + [[ "$sync" == "$SYNC_FILTER" ]] || return 1 + fi + + return 0 +} + +emit_package_json() { + local name="$1" source="$2" aur="$3" sync="$4" ring="$5" pkgrel="$6" upstream_commit="$7" patches="$8" hook="$9" version="${10}" path="${11}" + + jq -n \ + --arg name "$name" \ + --arg source "$source" \ + --arg aur "$aur" \ + --arg sync "$sync" \ + --arg release_ring "$ring" \ + --argjson pkgrel "$pkgrel" \ + --arg upstream_commit "$upstream_commit" \ + --argjson patches "$patches" \ + --arg hook "$hook" \ + --arg version "$version" \ + --arg path "$path" \ + '{name: $name, source: $source, aur: $aur, sync: (if $sync == "" then null else $sync == "true" end), release_ring: $release_ring, pkgrel: $pkgrel, upstream_commit: $upstream_commit, patches: $patches, post_sync_hook: ($hook == "true"), version: $version, path: $path}' +} + +if [[ "$LIST_REPO" == true ]]; then + list_repo_packages + exit 0 fi -echo "Packages in $MIRROR mirror ($ARCH):" -echo "" +rows_file=$(mktemp) +json_file=$(mktemp) +trap 'rm -f "$rows_file" "$json_file"' EXIT -# Extract package names and versions from database -tar -xOf "$DB_FILE" --wildcards "*/desc" 2>/dev/null | - awk '/%NAME%/{getline; name=$0} - /%VERSION%/{getline; print name "-" $0}' | - sort +first_json=true +printf '[\n' > "$json_file" + +while IFS= read -r pkgdir; do + name=$(basename "$pkgdir") + metadata=$(metadata_file_for_dir "$pkgdir") + source=$(jq -r '.source // ""' "$metadata") + ring=$(jq -r '.release_ring // ""' "$metadata") + aur=$(jq -r --arg name "$name" '.aur // $name' "$metadata") + aur_display="$aur" + [[ "$aur_display" == "$name" ]] && aur_display="" + + if [[ "$source" == "aur" ]]; then + sync=$(jq -r 'if has("sync") then .sync else true end' "$metadata") + else + sync="" + fi + + package_matches_filters "$source" "$sync" "$ring" || continue + + pkgrel=$(jq -c '.pkgrel // {}' "$metadata") + pkgrel_display=$(jq -r '.pkgrel // {} | to_entries | map("\(.key)=\(.value)") | join(",")' "$metadata") + upstream_commit=$(jq -r '.upstream_commit // ""' "$metadata") + upstream_display="${upstream_commit:0:7}" + patches=$(patch_count "$pkgdir" | tr -d ' ') + hook=$(post_sync_hook_enabled "$pkgdir") + hook_display="$([[ "$hook" == true ]] && echo yes || echo no)" + version=$(get_pkgbuild_version "$pkgdir") + relpath=${pkgdir#"$BUILD_ROOT/"} + + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ + "$name" \ + "$source" \ + "$aur_display" \ + "$([[ -n "$sync" ]] && [[ "$sync" == true ]] && echo yes || ([[ "$sync" == false ]] && echo no || echo -))" \ + "$ring" \ + "$pkgrel_display" \ + "$upstream_display" \ + "$patches" \ + "$hook_display" \ + "$version" \ + "$relpath" >> "$rows_file" + + if [[ "$first_json" == false ]]; then + printf ',\n' >> "$json_file" + fi + emit_package_json "$name" "$source" "$aur" "$sync" "$ring" "$pkgrel" "$upstream_commit" "$patches" "$hook" "$version" "$relpath" >> "$json_file" + first_json=false +done < <(package_dirs) + +printf '\n]\n' >> "$json_file" + +if [[ "$OUTPUT_JSON" == true ]]; then + jq . "$json_file" + exit 0 +fi + +if command -v gum >/dev/null 2>&1; then + gum table --print --separator $'\t' \ + --columns Package,Source,AUR,Sync,Release_Ring,Pkgrel,Upstream,Patches,Post_Sync,Version,Path \ + < "$rows_file" +else + { + printf 'Package\tSource\tAUR\tSync\tRelease_Ring\tPkgrel\tUpstream\tPatches\tPost_Sync\tVersion\tPath\n' + cat "$rows_file" + } | column -t -s $'\t' +fi diff --git a/bin/migrate-edge-to-stable b/bin/migrate-edge-to-stable index 3c38787..3c49c3b 100755 --- a/bin/migrate-edge-to-stable +++ b/bin/migrate-edge-to-stable @@ -1,5 +1,5 @@ #!/bin/bash -# Migrate package files from edge repo to stable repo without overwriting +# Promote tested edge package artifacts into stable without overwriting set -e @@ -41,7 +41,7 @@ while [[ $# -gt 0 ]]; do echo " -h, --help Show this help message" echo "" echo "What it does:" - echo " 1) Rsync package files from edge -> stable (without overwriting existing files)" + echo " 1) Rsync tested package artifacts from edge -> stable (without overwriting existing files)" echo " 2) Run stable cleanup (keep 2 versions)" echo " 3) Update stable repository database" echo " 4) Sync stable repository to remote (prompts for confirmation)" @@ -57,7 +57,7 @@ done SOURCE_DIR="$BUILD_ROOT/pkgs.omarchy.org/edge/$ARCH" TARGET_DIR="$BUILD_ROOT/pkgs.omarchy.org/stable/$ARCH" -print_header "Migrate Edge to Stable" +print_header "Promote Edge Artifacts to Stable" print_info "Architecture: $ARCH" print_info "Source: $SOURCE_DIR" print_info "Target: $TARGET_DIR" diff --git a/bin/package-worktree b/bin/package-worktree new file mode 100755 index 0000000..8deca92 --- /dev/null +++ b/bin/package-worktree @@ -0,0 +1,288 @@ +#!/bin/bash +set -euo pipefail + +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +source "$BUILD_ROOT/helpers/message-helpers.sh" +source "$BUILD_ROOT/helpers/paths.sh" +source "$BUILD_ROOT/helpers/package-metadata.sh" + +PACKAGE="" +DEST_DIR="" +COMMIT_OVERRIDE="" + +usage() { + cat < [OPTIONS] + +Create a scratch workspace for inspecting an AUR-backed package. + +The workspace contains: + upstream/ Raw AUR package at .omarchy/package.json upstream_commit, or HEAD + patched/ Raw AUR package with Omarchy .omarchy patches/hooks/pkgrel applied + current/ Current checked-in package directory + +Options: + --dir Workspace directory (default: mktemp under /tmp) + --commit Use a specific AUR commit instead of upstream_commit + -h, --help Show this help message + +Examples: + $0 v4l2-relayd + $0 v4l2-relayd --dir /tmp/v4l2-relayd-worktree +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --dir) + DEST_DIR="$2" + shift 2 + ;; + --commit) + COMMIT_OVERRIDE="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + --*) + print_error "Unknown option: $1" + exit 1 + ;; + *) + if [[ -n "$PACKAGE" ]]; then + print_error "Only one package can be inspected at a time" + exit 1 + fi + PACKAGE="$1" + shift + ;; + esac +done + +if [[ -z "$PACKAGE" ]]; then + print_error "Package name required" + usage + exit 1 +fi + +PACKAGE_DIR="$PKGBUILDS_DIR/$PACKAGE" +METADATA="$PACKAGE_DIR/.omarchy/package.json" + +if [[ ! -f "$METADATA" ]]; then + print_error "Missing package metadata: $METADATA" + exit 1 +fi + +if [[ "$(jq -r '.source // ""' "$METADATA")" != "aur" ]]; then + print_error "package-worktree only supports AUR-backed packages" + exit 1 +fi + +AUR_PACKAGE=$(jq -r --arg package "$PACKAGE" '.aur // $package' "$METADATA") +UPSTREAM_COMMIT=${COMMIT_OVERRIDE:-$(jq -r '.upstream_commit // ""' "$METADATA")} + +if [[ -z "$DEST_DIR" ]]; then + DEST_DIR=$(mktemp -d "${TMPDIR:-/tmp}/omarchy-${PACKAGE}.XXXXXX") +else + mkdir -p "$DEST_DIR" + DEST_DIR=$(realpath "$DEST_DIR") +fi + +UPSTREAM_DIR="$DEST_DIR/upstream" +PATCHED_DIR="$DEST_DIR/patched" +CURRENT_DIR="$DEST_DIR/current" + +get_pkgbuild_field() { + local package_dir="$1" + local field="$2" + local value="" + + if [[ -f "$package_dir/PKGBUILD" ]]; then + value=$(grep -m1 "^${field}=" "$package_dir/PKGBUILD" | cut -d= -f2- | tr -d "\"'") || true + if [[ -n "$value" ]]; then + echo "$value" + return + fi + fi + + if [[ -f "$package_dir/.SRCINFO" ]]; then + awk -F' = ' -v field="$field" '$1 ~ "^[[:space:]]*" field "$" { print $2; exit }' "$package_dir/.SRCINFO" + fi +} + +set_pkgrel() { + local package_dir="$1" + local pkgrel="$2" + + sed -i "s/^pkgrel=.*/pkgrel=$pkgrel/" "$package_dir/PKGBUILD" +} + +copy_aur_contents() { + local aur_dir="$1" + local target_dir="$2" + + mkdir -p "$target_dir" + shopt -s dotglob nullglob + local item base + for item in "$aur_dir"/*; do + base=$(basename "$item") + [[ "$base" == ".git" ]] && continue + cp -a "$item" "$target_dir/" + done + shopt -u dotglob nullglob +} + +remove_aur_only_files() { + local package_dir="$1" + + rm -f "$package_dir/.SRCINFO" "$package_dir/.gitignore" +} + +apply_omarchy_patches() { + local package_dir="$1" + local patches_dir="$package_dir/.omarchy/patches" + local applied=false + + [[ -d "$patches_dir" ]] || return 1 + + shopt -s nullglob + local patch_files=("$patches_dir"/*.patch) + shopt -u nullglob + [[ ${#patch_files[@]} -gt 0 ]] || return 1 + + local patch_file + for patch_file in "${patch_files[@]}"; do + print_info " Applying $(basename "$patch_file")" + if ! (cd "$package_dir" && patch -p1 --forward --batch --no-backup-if-mismatch < "$patch_file"); then + print_error "Failed to apply patch: $patch_file" + return 2 + fi + applied=true + done + + [[ "$applied" == true ]] +} + +run_post_sync_hook() { + local package_dir="$1" + local aur_pkgrel="$2" + local hook="$package_dir/.omarchy/post-sync.sh" + + [[ -f "$hook" ]] || return 1 + + print_info " Running post-sync.sh" + if ! ( + cd "$package_dir" + PACKAGE_NAME="$PACKAGE" \ + AUR_PACKAGE_NAME="$AUR_PACKAGE" \ + AUR_PKGREL="$aur_pkgrel" \ + bash ".omarchy/post-sync.sh" + ); then + print_error "post-sync.sh failed" + return 2 + fi +} + +apply_pkgrel_metadata() { + local package_dir="$1" + local aur_pkgrel="$2" + local previous_pkgver="$3" + local metadata="$package_dir/.omarchy/package.json" + + jq -e 'has("pkgrel")' "$metadata" >/dev/null || return 1 + + local current_pkgver suffix offset base + current_pkgver=$(get_pkgbuild_field "$package_dir" pkgver) + + if [[ -n "$previous_pkgver" && "$current_pkgver" != "$previous_pkgver" ]]; then + print_info " Skipping stale pkgrel metadata (pkgver changed: $previous_pkgver -> $current_pkgver)" + return 1 + fi + + suffix=$(jq -r '.pkgrel.suffix // 1' "$metadata") + offset=$(jq -r '.pkgrel.offset // 0' "$metadata") + base=$((aur_pkgrel + offset)) + + print_info " Applying pkgrel metadata: $base.$suffix" + set_pkgrel "$package_dir" "$base.$suffix" +} + +apply_auto_pkgrel_suffix() { + local package_dir="$1" + local aur_pkgrel="$2" + + print_info " Applying automatic pkgrel suffix: $aur_pkgrel.1" + set_pkgrel "$package_dir" "$aur_pkgrel.1" +} + +print_header "Package Worktree: $PACKAGE" +print_info "Workspace: $DEST_DIR" +print_info "AUR package: $AUR_PACKAGE" + +if [[ -n "$UPSTREAM_COMMIT" ]]; then + print_info "Upstream commit: $UPSTREAM_COMMIT" +else + print_warning "No upstream_commit recorded; using AUR HEAD" +fi + +rm -rf "$UPSTREAM_DIR" "$PATCHED_DIR" "$CURRENT_DIR" + +git clone "https://aur.archlinux.org/${AUR_PACKAGE}.git" "$UPSTREAM_DIR" >/dev/null 2>&1 +if [[ -n "$UPSTREAM_COMMIT" ]]; then + git -C "$UPSTREAM_DIR" checkout --detach "$UPSTREAM_COMMIT" >/dev/null 2>&1 +fi +rm -rf "$UPSTREAM_DIR/.git" + +copy_aur_contents "$UPSTREAM_DIR" "$PATCHED_DIR" +rm -rf "$PATCHED_DIR/.omarchy" +cp -a "$PACKAGE_DIR/.omarchy" "$PATCHED_DIR/.omarchy" +cp -a "$PACKAGE_DIR" "$CURRENT_DIR" + +AUR_PKGREL=$(get_pkgbuild_field "$UPSTREAM_DIR" pkgrel) +PREVIOUS_PKGVER=$(get_pkgbuild_field "$PACKAGE_DIR" pkgver || true) +CUSTOMIZED=false +PKGREL_APPLIED=false + +print_info "Creating patched/ from upstream/ + .omarchy customizations" +if apply_omarchy_patches "$PATCHED_DIR"; then + CUSTOMIZED=true +else + patch_status=$? + if [[ $patch_status -eq 2 ]]; then + exit 1 + fi +fi + +if run_post_sync_hook "$PATCHED_DIR" "$AUR_PKGREL"; then + CUSTOMIZED=true +else + hook_status=$? + if [[ $hook_status -eq 2 ]]; then + exit 1 + fi +fi + +if apply_pkgrel_metadata "$PATCHED_DIR" "$AUR_PKGREL" "$PREVIOUS_PKGVER"; then + PKGREL_APPLIED=true +fi + +if [[ "$CUSTOMIZED" == true && "$PKGREL_APPLIED" == false ]]; then + apply_auto_pkgrel_suffix "$PATCHED_DIR" "$AUR_PKGREL" +fi + +remove_aur_only_files "$PATCHED_DIR" +remove_aur_only_files "$CURRENT_DIR" + +cat < Mirror to use (edge or stable, default: edge)" 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 " --skip-prod-check Skip production environment check during sync + --dry-run Show build plan only; do not sign/promote/clean/update/sync" echo " -h, --help Show this help message" echo "" echo "This script runs the complete workflow:" @@ -80,13 +87,23 @@ print_info "Build workspace: $BUILD_OUTPUT_DIR" # Step 1: Build echo "" -print_info "Step 1/6: Building packages..." +if [[ "$DRY_RUN" == true ]]; then + print_info "Step 1/6: Planning build..." +else + print_info "Step 1/6: Building packages..." +fi "$BUILD_ROOT/bin/build" "${BUILD_ARGS[@]}" || { print_error "Build failed" notify_error "Release failed: Build step failed" "Mirror: $MIRROR | Arch: $ARCH" exit 1 } +if [[ "$DRY_RUN" == true ]]; then + echo "" + print_success "Release dry run complete (build plan only)." + exit 0 +fi + # Step 2: Sign echo "" print_info "Step 2/6: Signing packages..." diff --git a/bin/repo b/bin/repo index 6ca8fce..bce345f 100755 --- a/bin/repo +++ b/bin/repo @@ -47,13 +47,13 @@ show_usage() { echo "" echo "Commands:" echo " release Run complete workflow: build → sign → promote → clean → update → sync" - echo " build Build AUR packages (unsigned)" + echo " build Build 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 " migrate Migrate edge packages into stable (no overwrite), then clean + update" - echo " list List packages in repository" + echo " list List source package metadata (use --repo for published repo)" echo " remove Remove a specific package" echo " sync Sync repository to remote" echo "" diff --git a/bin/sync-aur b/bin/sync-aur index 66e9b92..1d177ad 100755 --- a/bin/sync-aur +++ b/bin/sync-aur @@ -1,46 +1,50 @@ #!/bin/bash +set -euo pipefail BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") source "$BUILD_ROOT/helpers/message-helpers.sh" source "$BUILD_ROOT/helpers/paths.sh" +source "$BUILD_ROOT/helpers/package-metadata.sh" TEMP_DIR=$(mktemp -d) +trap 'rm -rf "$TEMP_DIR"' EXIT -trap "rm -rf $TEMP_DIR" EXIT - -# Default tier -TIER="" SPECIFIC_PACKAGES=() -# Parse arguments +usage() { + cat </. + +Package selection is driven by pkgbuilds//.omarchy/package.json: + { "source": "aur" } # synced from matching AUR package name + { "source": "aur", "aur": "yaru" } # synced from different AUR package name + { "source": "aur", "sync": false } # AUR-origin, but not auto-synced + +Arguments: + PACKAGE One or more package names to sync (optional) + +Examples: + $0 # Sync all AUR packages with sync enabled + $0 yay cursor-bin # Sync specific packages +EOF +} + while [[ $# -gt 0 ]]; do case "$1" in - --tier) - TIER="$2" - if [[ "$TIER" != "edge" && "$TIER" != "shared" ]]; then - print_error "Invalid tier: $TIER (must be 'edge' or 'shared')" - exit 1 - fi - shift 2 - ;; -h|--help) - echo "Usage: $0 --tier [PACKAGE...]" - echo "" - echo "Sync AUR packages to pkgbuilds// directory" - echo "" - echo "Options:" - echo " --tier Target tier: edge or shared (required)" - echo "" - echo "Arguments:" - echo " PACKAGE One or more package names to sync (optional)" - echo "" - echo "Examples:" - echo " $0 --tier edge # Sync all packages from edge.packages" - echo " $0 --tier shared # Sync all packages from shared.packages" - echo " $0 --tier edge yay # Sync only the yay package to edge/" - echo " $0 --tier shared cursor-bin # Sync only cursor-bin to shared/" + usage exit 0 ;; + --tier) + print_error "--tier is no longer supported; package metadata controls sync behavior" + exit 1 + ;; + --*) + print_error "Unknown option: $1" + exit 1 + ;; *) SPECIFIC_PACKAGES+=("$1") shift @@ -48,127 +52,355 @@ while [[ $# -gt 0 ]]; do esac done -# Require tier -if [[ -z "$TIER" ]]; then - print_error "Missing required --tier argument" - echo "Usage: $0 --tier [PACKAGE...]" - exit 1 -fi - -# Set paths based on tier -PACKAGES_FILE="$BUILD_ROOT/build/packages/${TIER}.packages" -TARGET_DIR="$PKGBUILDS_DIR/$TIER" - -print_header "AUR Package Sync (tier: $TIER)" - -mkdir -p "$TARGET_DIR" - -if [[ ! -f "$PACKAGES_FILE" ]]; then - print_error "Package list not found: $PACKAGES_FILE" - exit 1 -fi +print_header "AUR Package Sync" +mkdir -p "$PKGBUILDS_DIR" SYNCED=0 +SKIPPED=0 FAILED=0 SYNCED_PACKAGES=() +SPECIFIC_MODE=false -# Function to sync a package from AUR -sync_package() { - local package="$1" - - print_info "Syncing $package from AUR..." - - cd "$TEMP_DIR" - rm -rf "$package" - - if git clone "https://aur.archlinux.org/${package}.git" 2>/dev/null; then - # Create target directory if it doesn't exist - mkdir -p "$TARGET_DIR/$package" - - # Copy files (not .git directory) - cp -r "$package"/* "$TARGET_DIR/$package/" 2>/dev/null || true - cp -r "$package"/.* "$TARGET_DIR/$package/" 2>/dev/null || true - - # Remove .git from destination - rm -rf "$TARGET_DIR/$package/.git" - - SYNCED_PACKAGES+=("$package") - ((SYNCED++)) - else - print_warning "Failed to clone $package (may not exist in AUR)" - ((FAILED++)) +get_pkgbuild_field() { + local package_dir="$1" + local field="$2" + local value="" + + if [[ -f "$package_dir/PKGBUILD" ]]; then + value=$(grep -m1 "^${field}=" "$package_dir/PKGBUILD" | cut -d= -f2- | tr -d "\"'") || true + if [[ -n "$value" ]]; then + echo "$value" + return + fi + fi + + if [[ -f "$package_dir/.SRCINFO" ]]; then + awk -F' = ' -v field="$field" '$1 ~ "^[[:space:]]*" field "$" { print $2; exit }' "$package_dir/.SRCINFO" fi } -# If specific packages provided, sync only those +set_pkgrel() { + local package_dir="$1" + local pkgrel="$2" + local pkgbuild="$package_dir/PKGBUILD" + + if [[ -f "$pkgbuild" ]]; then + sed -i "s/^pkgrel=.*/pkgrel=$pkgrel/" "$pkgbuild" + fi +} + +display_package_name() { + local package_dir="$1" + local name + name=$(basename "$package_dir") + echo "${name%.work}" +} + +remove_aur_only_files() { + local package_dir="$1" + + rm -f "$package_dir/.SRCINFO" "$package_dir/.gitignore" +} + +copy_aur_contents() { + local aur_dir="$1" + local target_dir="$2" + + mkdir -p "$target_dir" + + shopt -s dotglob nullglob + local item base + for item in "$aur_dir"/*; do + base=$(basename "$item") + [[ "$base" == ".git" ]] && continue + cp -a "$item" "$target_dir/" + done + shopt -u dotglob nullglob +} + +commit_synced_worktree() { + local work_dir="$1" + local target_dir="$2" + local parent base staged_dir backup_root backup_dir + + parent=$(dirname "$target_dir") + base=$(basename "$target_dir") + staged_dir=$(mktemp -d "$parent/.${base}.staged.XXXXXX") + backup_root=$(mktemp -d "$parent/.${base}.backup.XXXXXX") + backup_dir="$backup_root/$base" + + # Copy to the package filesystem before swapping. This keeps the original + # package directory intact if copying from /tmp fails or is interrupted. + if ! cp -a "$work_dir/." "$staged_dir/"; then + print_error "Failed to stage synced package for $base" + rm -rf "$staged_dir" "$backup_root" + return 1 + fi + + if [[ -e "$target_dir" ]]; then + if ! mv "$target_dir" "$backup_dir"; then + print_error "Failed to back up existing package directory: $target_dir" + rm -rf "$staged_dir" "$backup_root" + return 1 + fi + fi + + if ! mv "$staged_dir" "$target_dir"; then + print_error "Failed to install synced package directory: $target_dir" + if [[ -e "$backup_dir" ]]; then + mv "$backup_dir" "$target_dir" || true + fi + rm -rf "$staged_dir" "$backup_root" + return 1 + fi + + rm -rf "$backup_root" "$work_dir" +} + +set_upstream_commit() { + local package_dir="$1" + local commit="$2" + local metadata="$package_dir/.omarchy/package.json" + local tmpfile + + tmpfile=$(mktemp) + jq --arg commit "$commit" '.upstream_commit = $commit' "$metadata" > "$tmpfile" + mv "$tmpfile" "$metadata" +} + +apply_omarchy_patches() { + local package_dir="$1" + local patches_dir="$package_dir/.omarchy/patches" + local applied=false + + [[ -d "$patches_dir" ]] || return 1 + + shopt -s nullglob + local patch_files=("$patches_dir"/*.patch) + local patch_dir_files=("$patches_dir"/*) + shopt -u nullglob + + if [[ ${#patch_files[@]} -eq 0 ]]; then + if [[ ${#patch_dir_files[@]} -gt 0 ]]; then + print_warning "No .patch files found in $patches_dir" + fi + return 1 + fi + + print_info "Applying Omarchy patches for $(display_package_name "$package_dir")..." + local patch_file + for patch_file in "${patch_files[@]}"; do + print_info " $(basename "$patch_file")" + if ! (cd "$package_dir" && patch -p1 --forward --batch --no-backup-if-mismatch < "$patch_file"); then + print_error "Failed to apply patch: $patch_file" + return 2 + fi + applied=true + done + + [[ "$applied" == true ]] +} + +run_omarchy_post_sync_hook() { + local package_dir="$1" + local package="$2" + local aur_package="$3" + local aur_pkgrel="$4" + local hook="$package_dir/.omarchy/post-sync.sh" + + [[ -f "$hook" ]] || return 1 + + print_info "Running Omarchy post-sync hook for $(display_package_name "$package_dir")..." + if ! ( + cd "$package_dir" + PACKAGE_NAME="$package" \ + AUR_PACKAGE_NAME="$aur_package" \ + AUR_PKGREL="$aur_pkgrel" \ + bash ".omarchy/post-sync.sh" + ); then + print_error "Failed to run post-sync hook: $hook" + return 2 + fi + + return 0 +} + +apply_pkgrel_suffix_if_customized() { + local package_dir="$1" + local aur_pkgrel="$2" + + [[ -n "$aur_pkgrel" ]] || return 0 + + print_info "Applying Omarchy pkgrel suffix for $(display_package_name "$package_dir"): pkgrel=$aur_pkgrel.1" + set_pkgrel "$package_dir" "$aur_pkgrel.1" +} + +apply_pkgrel_override() { + local package_dir="$1" + local aur_pkgrel="$2" + local previous_pkgver="$3" + local metadata="$package_dir/.omarchy/package.json" + local pkgbuild="$package_dir/PKGBUILD" + + [[ -f "$metadata" ]] || return 1 + jq -e 'has("pkgrel")' "$metadata" >/dev/null || return 1 + + local current_pkgver suffix offset base rel tmpfile + current_pkgver=$(grep -m1 '^pkgver=' "$pkgbuild" | cut -d= -f2) + + if [[ -n "$previous_pkgver" && "$current_pkgver" != "$previous_pkgver" ]]; then + print_info "Removing stale pkgrel metadata for $(display_package_name "$package_dir") (pkgver changed: $previous_pkgver -> $current_pkgver)" + tmpfile=$(mktemp) + jq 'del(.pkgrel)' "$metadata" > "$tmpfile" + mv "$tmpfile" "$metadata" + return 1 + fi + + suffix=$(jq -r '.pkgrel.suffix // 1' "$metadata") + offset=$(jq -r '.pkgrel.offset // 0' "$metadata") + + if [[ ! "$offset" =~ ^[0-9]+$ || ! "$aur_pkgrel" =~ ^[0-9]+$ ]]; then + print_error "pkgrel offset requires numeric AUR pkgrel for $(display_package_name "$package_dir")" + return 2 + fi + + base=$((aur_pkgrel + offset)) + rel="$base.$suffix" + + print_info "Applying pkgrel suffix for $(display_package_name "$package_dir"): AUR pkgrel=$aur_pkgrel, offset=$offset, suffix=$suffix -> pkgrel=$rel" + set_pkgrel "$package_dir" "$rel" + return 0 +} + +sync_package() { + local package="$1" + local package_dir="$PKGBUILDS_DIR/$package" + local metadata="$package_dir/.omarchy/package.json" + + if [[ ! -f "$metadata" ]]; then + if [[ "$SPECIFIC_MODE" == true ]]; then + print_error "Package $package is missing .omarchy/package.json" + ((++FAILED)) + else + print_warning "Skipping $package: missing .omarchy/package.json" + ((++SKIPPED)) + fi + return 0 + fi + + if [[ "$(jq -r '.source // ""' "$metadata")" != "aur" ]]; then + print_info "Skipping $package: source is not AUR" + ((++SKIPPED)) + return 0 + fi + + if [[ "$(jq -r 'if has("sync") then .sync else true end' "$metadata")" == "false" ]]; then + print_info "Skipping $package: AUR sync disabled" + ((++SKIPPED)) + return 0 + fi + + local aur_package + aur_package=$(jq -r --arg package "$package" '.aur // $package' "$metadata") + + if [[ "$aur_package" == "$package" ]]; then + print_info "Syncing $package from AUR..." + else + print_info "Syncing $package from AUR package $aur_package..." + fi + + cd "$TEMP_DIR" + rm -rf "$aur_package" + + if ! git clone "https://aur.archlinux.org/${aur_package}.git" "$aur_package" >/dev/null 2>&1; then + print_warning "Failed to clone $aur_package (may not exist in AUR)" + ((++FAILED)) + return 0 + fi + + local aur_dir="$TEMP_DIR/$aur_package" + local work_dir="$TEMP_DIR/${package}.work" + local aur_pkgrel previous_pkgver upstream_commit + aur_pkgrel=$(get_pkgbuild_field "$aur_dir" pkgrel) + previous_pkgver=$(get_pkgbuild_field "$package_dir" pkgver || true) + upstream_commit=$(git -C "$aur_dir" rev-parse HEAD) + + rm -rf "$work_dir" + copy_aur_contents "$aur_dir" "$work_dir" + rm -rf "$work_dir/.omarchy" + cp -a "$package_dir/.omarchy" "$work_dir/.omarchy" + + local customized=false + + if apply_omarchy_patches "$work_dir"; then + customized=true + else + local patch_status=$? + if [[ $patch_status -eq 2 ]]; then + ((++FAILED)) + return 0 + fi + fi + + if run_omarchy_post_sync_hook "$work_dir" "$package" "$aur_package" "$aur_pkgrel"; then + customized=true + else + local hook_status=$? + if [[ $hook_status -eq 2 ]]; then + ((++FAILED)) + return 0 + fi + fi + + local pkgrel_overridden=false + set +e + apply_pkgrel_override "$work_dir" "$aur_pkgrel" "$previous_pkgver" + local pkgrel_status=$? + set -e + case "$pkgrel_status" in + 0) pkgrel_overridden=true ;; + 1) ;; + *) ((++FAILED)); return 0 ;; + esac + + if [[ "$customized" == true && "$pkgrel_overridden" == false ]]; then + apply_pkgrel_suffix_if_customized "$work_dir" "$aur_pkgrel" + fi + + remove_aur_only_files "$work_dir" + + set_upstream_commit "$work_dir" "$upstream_commit" + if ! commit_synced_worktree "$work_dir" "$package_dir"; then + ((++FAILED)) + return 0 + fi + + SYNCED_PACKAGES+=("$package") + ((++SYNCED)) +} + if [[ ${#SPECIFIC_PACKAGES[@]} -gt 0 ]]; then + SPECIFIC_MODE=true for package in "${SPECIFIC_PACKAGES[@]}"; do sync_package "$package" done else - # Sync all packages from list - while IFS= read -r line || [ -n "$line" ]; do - [[ "$line" =~ ^#.*$ ]] && continue - [[ -z "$line" ]] && continue - - package=$(echo "$line" | awk '{print $1}') - - # Skip GitHub repos (contain /) - if [[ "$package" == *"/"* ]]; then - continue - fi - + while IFS= read -r package; do sync_package "$package" - done < <(grep -v "^#" "$PACKAGES_FILE" | grep -v "^$") + done < <(packages_for_aur_sync) fi -# Apply patches only to packages that were actually synced -for package in "${SYNCED_PACKAGES[@]}"; do - if [[ -d "$TARGET_DIR/$package/patches" ]]; then - print_info "Applying patches for $package..." - (cd "$TARGET_DIR/$package" && shopt -s nullglob && for patch in patches/*.patch; do - if ! patch -p1 --no-backup-if-mismatch < "$patch"; then - print_error "Failed to apply patch: $patch" - exit 1 - fi - done) - fi -done - -# Apply pkgrel overrides (version-pinned pkgrel bumps that self-clean) -for package in "${SYNCED_PACKAGES[@]}"; do - override_file="$TARGET_DIR/$package/pkgrel.override" - [[ -f "$override_file" ]] || continue - - pkgbuild="$TARGET_DIR/$package/PKGBUILD" - srcinfo="$TARGET_DIR/$package/.SRCINFO" - gitignore="$TARGET_DIR/$package/.gitignore" - current_pkgver=$(grep -m1 '^pkgver=' "$pkgbuild" | cut -d= -f2) - matched=false - - while IFS='=' read -r ver rel; do - [[ "$ver" =~ ^#.*$ || -z "$ver" ]] && continue - if [[ "$current_pkgver" == "$ver" ]]; then - print_info "Applying pkgrel override for $package: pkgver=$ver -> pkgrel=$rel" - sed -i "s/^pkgrel=.*/pkgrel=$rel/" "$pkgbuild" - sed -i "s/pkgrel = .*/pkgrel = $rel/" "$srcinfo" - # Ensure .gitignore tracks the override file (AUR may have overwritten it) - if [[ -f "$gitignore" ]] && ! grep -q '^!pkgrel.override$' "$gitignore"; then - echo '!pkgrel.override' >> "$gitignore" - fi - matched=true - break - fi - done < "$override_file" - - if [[ "$matched" == false ]]; then - print_info "Removing stale pkgrel.override for $package (no match for pkgver=$current_pkgver)" - rm "$override_file" - fi -done - echo "" -print_success "Sync complete! (tier: $TIER)" -echo " Target: $TARGET_DIR" +if [[ $FAILED -gt 0 ]]; then + print_error "Sync completed with failures" +else + print_success "Sync complete!" +fi +echo " Target: $PKGBUILDS_DIR" echo " Synced: $SYNCED" +echo " Skipped: $SKIPPED" echo " Failed: $FAILED" + +if [[ $FAILED -gt 0 ]]; then + exit 1 +fi diff --git a/build/build.sh b/build/build.sh index 28edfeb..b698a72 100755 --- a/build/build.sh +++ b/build/build.sh @@ -1,90 +1,86 @@ #!/bin/bash -# Build script - builds packages based on mirror tier -# Edge builds: /pkgbuilds/edge/* + /pkgbuilds/shared/* -# Stable builds: /pkgbuilds/shared/* only (edge packages are promoted via binary copy) - -# Import GPG keys -/build/import-gpg-keys.sh || exit 1 +# Build script - builds packages based on package metadata +# Edge builds all packages. Stable builds only packages in the fast release ring. # Setup directories ARCH=${ARCH:-x86_64} MIRROR=${MIRROR:-edge} -BUILD_OUTPUT_DIR="/build-output/$MIRROR/$ARCH" -FINAL_OUTPUT_DIR="/pkgs.omarchy.org/$MIRROR/$ARCH" +DRY_RUN=${DRY_RUN:-false} +PKGBUILDS_DIR=${PKGBUILDS_DIR:-/pkgbuilds} +BUILD_OUTPUT_DIR=${BUILD_OUTPUT_DIR:-/build-output/$MIRROR/$ARCH} +FINAL_OUTPUT_DIR=${FINAL_OUTPUT_DIR:-/pkgs.omarchy.org/$MIRROR/$ARCH} +HELPERS_DIR=${HELPERS_DIR:-/helpers} +SRC_DIR=${SRC_DIR:-/src} -# Determine which package directories to use based on mirror -# Edge builds from edge + shared; stable only builds shared (edge packages are promoted via binary copy) -if [[ "$MIRROR" == "stable" ]]; then - PKGBUILD_DIRS="/pkgbuilds/shared" -else - PKGBUILD_DIRS="/pkgbuilds/edge /pkgbuilds/shared" -fi +source "$HELPERS_DIR/package-metadata.sh" -mkdir -p "$BUILD_OUTPUT_DIR" "$FINAL_OUTPUT_DIR" +if [[ "$DRY_RUN" != true ]]; then + # Import GPG keys + /build/import-gpg-keys.sh || exit 1 -# Configure Omarchy repositories for dependency resolution -echo "==> Configuring Omarchy repositories for dependency resolution..." + mkdir -p "$BUILD_OUTPUT_DIR" "$FINAL_OUTPUT_DIR" -# Always add omarchy-build repo (for incremental builds) -# Packages in build-output are unsigned, so use SigLevel = Never -sudo tee -a /etc/pacman.conf > /dev/null < Configuring Omarchy repositories for dependency resolution..." + + # Always add omarchy-build repo (for incremental builds) + # Packages in build-output are unsigned, so use SigLevel = Never + sudo tee -a /etc/pacman.conf > /dev/null < omarchy-build (priority 1): $BUILD_OUTPUT_DIR" + echo " -> 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 + # 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 -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 < /dev/null < omarchy (priority 2): $FINAL_OUTPUT_DIR" -fi + echo " -> omarchy (priority 2): $FINAL_OUTPUT_DIR" + fi -# Sync pacman database -sudo pacman -Sy + # Sync pacman database + sudo pacman -Sy +fi echo "==> Package Builder" echo "==> Target architecture: $ARCH" echo "==> Mirror: $MIRROR" -echo "==> Package directories: $PKGBUILD_DIRS" +echo "==> Package root: $PKGBUILDS_DIR" echo "==> Build workspace: $BUILD_OUTPUT_DIR" echo "==> Final output: $FINAL_OUTPUT_DIR" +if [[ "$DRY_RUN" == true ]]; then + echo "==> Dry run: yes (plan only; makepkg will not run)" +fi FAILED_PACKAGES="" SUCCESSFUL_PACKAGES="" SKIPPED_PACKAGES="" -# Find package directory - searches through PKGBUILD_DIRS +# Find package directory find_package_dir() { local pkg="$1" - for dir in $PKGBUILD_DIRS; do - if [[ -d "$dir/$pkg" ]]; then - echo "$dir/$pkg" - return 0 - fi - done - return 1 + package_dir_for_name "$pkg" } # Get version from final output (production packages) @@ -228,7 +224,7 @@ get_package_deps() { ) | 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 (any tier) + # Check if this dependency exists in our pkgbuilds if find_package_dir "$dep" >/dev/null 2>&1; then echo "$dep" fi @@ -315,22 +311,15 @@ check_needs_build() { fi } -# Collect all packages from the relevant directories +# Collect packages that should be built for the selected mirror collect_packages() { - for dir in $PKGBUILD_DIRS; do - if [[ -d "$dir" ]]; then - for pkgdir in "$dir"/*/; do - [[ ! -d "$pkgdir" ]] && continue - local pkg=$(basename "$pkgdir") - [[ ! -f "$pkgdir/PKGBUILD" ]] && continue - echo "$pkg" - done - fi - done + packages_for_mirror "$MIRROR" } # Main execution -cd /src +if [[ "$DRY_RUN" != true ]]; then + cd "$SRC_DIR" +fi TOTAL_COUNT=0 @@ -345,10 +334,20 @@ if [[ -n "$PACKAGES" ]]; then for pkg_name in $PACKAGES; do pkgdir=$(find_package_dir "$pkg_name") if [[ -z "$pkgdir" || ! -f "$pkgdir/PKGBUILD" ]]; then - echo "==> ERROR: Package '$pkg_name' not found in $PKGBUILD_DIRS" + echo "==> ERROR: Package '$pkg_name' not found in $PKGBUILDS_DIR" exit 1 fi + if ! package_builds_for_mirror "$pkgdir" "$MIRROR"; then + if [[ "$MIRROR" == "stable" ]]; then + echo " - $pkg_name - not in release_ring=fast; build edge and promote with repo migrate" + else + echo " - $pkg_name - not configured for direct $MIRROR builds" + fi + SKIPPED_PACKAGES="$SKIPPED_PACKAGES $pkg_name" + continue + fi + # Check if package should be built for this architecture if ! should_build_for_arch "$pkg_name"; then echo " - $pkg_name - not built for $ARCH" @@ -446,6 +445,12 @@ else echo "==> Build order: ${ORDERED_PACKAGES[@]}" + if [[ "$DRY_RUN" == true ]]; then + echo "" + echo "==> Dry run complete. Packages that would build: ${ORDERED_PACKAGES[@]}" + exit 0 + fi + # Determine which packages need to be installed for other packages being built declare -A INSTALL_PACKAGES for pkg in "${ORDERED_PACKAGES[@]}"; do diff --git a/helpers/package-metadata.sh b/helpers/package-metadata.sh new file mode 100644 index 0000000..68e008b --- /dev/null +++ b/helpers/package-metadata.sh @@ -0,0 +1,184 @@ +# Package metadata helpers for Omarchy package build tooling +# +# Expects package directories in $PKGBUILDS_DIR, each with: +# .omarchy/package.json +# +# Minimal schema: +# { "source": "aur" } +# { "source": "aur", "sync": false } +# { "source": "aur", "aur": "different-aur-name" } +# { "source": "aur", "release_ring": "fast" } +# { "source": "aur", "pkgrel": { "suffix": 1, "offset": 1 } } +# { "source": "local" } +# +# bin/sync-aur also writes upstream_commit for AUR-backed packages. + +if [[ -z "${PKGBUILDS_DIR:-}" ]]; then + if [[ -n "${BUILD_ROOT:-}" ]]; then + PKGBUILDS_DIR="$BUILD_ROOT/pkgbuilds" + elif [[ -d /pkgbuilds ]]; then + PKGBUILDS_DIR="/pkgbuilds" + else + PKGBUILDS_DIR="pkgbuilds" + fi +fi + +metadata_file_for_dir() { + local pkgdir="$1" + echo "$pkgdir/.omarchy/package.json" +} + +package_dir_for_name() { + local package="$1" + local pkgdir="$PKGBUILDS_DIR/$package" + + [[ -d "$pkgdir" && -f "$pkgdir/PKGBUILD" ]] || return 1 + echo "$pkgdir" +} + +package_metadata_value() { + local pkgdir="$1" + local jq_filter="$2" + local default="${3:-}" + local metadata + + metadata=$(metadata_file_for_dir "$pkgdir") + if [[ ! -f "$metadata" ]]; then + echo "$default" + return 0 + fi + + jq -r --arg default "$default" "$jq_filter // \$default" "$metadata" +} + +package_sync_enabled() { + local pkgdir="$1" + local metadata source sync + + metadata=$(metadata_file_for_dir "$pkgdir") + [[ -f "$metadata" ]] || return 1 + + source=$(jq -r '.source // ""' "$metadata") + [[ "$source" == "aur" ]] || return 1 + + sync=$(jq -r 'if has("sync") then .sync else true end' "$metadata") + [[ "$sync" != "false" ]] +} + +package_release_ring() { + local pkgdir="$1" + package_metadata_value "$pkgdir" '.release_ring' "" +} + +package_is_fast_ring() { + local pkgdir="$1" + [[ "$(package_release_ring "$pkgdir")" == "fast" ]] +} + +package_has_metadata() { + local pkgdir="$1" + [[ -f "$(metadata_file_for_dir "$pkgdir")" ]] +} + +package_has_pkgbuild() { + local pkgdir="$1" + [[ -f "$pkgdir/PKGBUILD" ]] +} + +package_builds_for_mirror() { + local pkgdir="$1" + local mirror="$2" + + package_has_pkgbuild "$pkgdir" || return 1 + package_has_metadata "$pkgdir" || return 1 + + case "$mirror" in + edge) + return 0 + ;; + stable) + package_is_fast_ring "$pkgdir" + ;; + *) + return 1 + ;; + esac +} + +package_dirs() { + [[ -d "$PKGBUILDS_DIR" ]] || return 0 + + find "$PKGBUILDS_DIR" -mindepth 1 -maxdepth 1 -type d -print | sort | while IFS= read -r pkgdir; do + package_has_pkgbuild "$pkgdir" || continue + package_has_metadata "$pkgdir" || continue + echo "$pkgdir" + done +} + +packages_for_aur_sync() { + package_dirs | while IFS= read -r pkgdir; do + if package_sync_enabled "$pkgdir"; then + basename "$pkgdir" + fi + done +} + +packages_for_mirror() { + local mirror="$1" + + package_dirs | while IFS= read -r pkgdir; do + if package_builds_for_mirror "$pkgdir" "$mirror"; then + basename "$pkgdir" + fi + done +} + +validate_package_metadata() { + local pkgdir="$1" + local metadata source sync aur ring pkgrel_type + + metadata=$(metadata_file_for_dir "$pkgdir") + [[ -f "$metadata" ]] || { echo "missing metadata: $metadata"; return 1; } + + jq empty "$metadata" >/dev/null || return 1 + + source=$(jq -r '.source // ""' "$metadata") + case "$source" in + aur|local) ;; + *) echo "invalid source for $(basename "$pkgdir"): $source"; return 1 ;; + esac + + sync=$(jq -r 'if has("sync") then .sync | type else "missing" end' "$metadata") + case "$sync" in + boolean|missing) ;; + *) echo "invalid sync for $(basename "$pkgdir"): must be boolean"; return 1 ;; + esac + + aur=$(jq -r 'if has("aur") then .aur | type else "missing" end' "$metadata") + case "$aur" in + string|missing) ;; + *) echo "invalid aur for $(basename "$pkgdir"): must be string"; return 1 ;; + esac + + ring=$(jq -r '.release_ring // ""' "$metadata") + case "$ring" in + ""|fast) ;; + *) echo "invalid release_ring for $(basename "$pkgdir"): $ring"; return 1 ;; + esac + + pkgrel_type=$(jq -r 'if has("pkgrel") then .pkgrel | type else "missing" end' "$metadata") + case "$pkgrel_type" in + object|missing) ;; + *) echo "invalid pkgrel for $(basename "$pkgdir"): must be an object with optional suffix/offset"; return 1 ;; + esac + + if ! jq -e '(.pkgrel // {}) | type == "object" and ((.suffix // 1) | type == "number" and floor == . and . >= 1) and ((.offset // 0) | type == "number" and floor == . and . >= 0)' "$metadata" >/dev/null; then + echo "invalid pkgrel for $(basename "$pkgdir"): must be an object with optional integer suffix >= 1 and offset >= 0" + return 1 + fi + + if ! jq -e '(.upstream_commit // "") | type == "string"' "$metadata" >/dev/null; then + echo "invalid upstream_commit for $(basename "$pkgdir"): must be a string" + return 1 + fi +}