305 lines
8.8 KiB
Bash
Executable File
305 lines
8.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# Move packages forward through the channel pipeline: edge -> rc -> stable.
|
|
#
|
|
# Copies package artifacts AND their detached signatures from one channel to
|
|
# the next, driven by the source channel's database (not the raw directory, so
|
|
# historical files never leak forward), then cleans, rebuilds, and syncs the
|
|
# destination. Published filenames are never overwritten: a same-name file
|
|
# that already exists at the destination is skipped (and reported when its
|
|
# bytes differ), because the R2 cache cannot recover from a rewrite.
|
|
|
|
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/lock-helpers.sh"
|
|
source "$BUILD_ROOT/helpers/package-metadata.sh"
|
|
|
|
FROM=""
|
|
TO=""
|
|
DRY_RUN=false
|
|
SYNC_REMOTE=""
|
|
SKIP_PROD_CHECK=false
|
|
FAST_RING_ONLY=false
|
|
BOOTSTRAP=false
|
|
PACKAGES=""
|
|
|
|
usage() {
|
|
echo "Usage: $0 --from <channel> --to <channel> [OPTIONS]"
|
|
echo ""
|
|
echo "Directions:"
|
|
echo " --from edge --to rc Open a release train: carry edge forward into rc"
|
|
echo " --from rc --to stable Ship: promote the tested rc channel to stable"
|
|
echo " --from stable --to rc Only with --fast-ring (parity replication)"
|
|
echo " or --bootstrap (one-time initial seed)"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --arch <arch> Target architecture (x86_64 or aarch64, default: x86_64)"
|
|
echo " --package <names...> Advance only the named package(s)"
|
|
echo " --fast-ring Restrict to fast-ring packages (stable -> rc parity copy)"
|
|
echo " --bootstrap One-time stable -> rc seed before forward-only enforcement"
|
|
echo " --dry-run Preview without changing files"
|
|
echo " --sync-remote <path> Rclone remote for sync (default: pkgs.omarchy.org:omarchy-pkgs)"
|
|
echo " --skip-prod-check Skip production confirmation during sync"
|
|
echo " -h, --help Show this help message"
|
|
echo ""
|
|
echo "What it does:"
|
|
echo " 1) Read the source channel database for the current package set"
|
|
echo " 2) Copy eligible packages + .sig files not yet at the destination"
|
|
echo " 3) Clean the destination (keep 2 versions)"
|
|
echo " 4) Rebuild the destination database"
|
|
echo " 5) Sync the destination to the remote (packages first, database last)"
|
|
exit "${1:-0}"
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--from)
|
|
FROM="$2"
|
|
shift 2
|
|
;;
|
|
--to)
|
|
TO="$2"
|
|
shift 2
|
|
;;
|
|
--arch)
|
|
ARCH="$2"
|
|
update_arch_paths
|
|
shift 2
|
|
;;
|
|
--package)
|
|
shift
|
|
while [[ $# -gt 0 && ! "$1" =~ ^-- ]]; do
|
|
PACKAGES="$PACKAGES $1"
|
|
shift
|
|
done
|
|
PACKAGES="${PACKAGES# }"
|
|
if [[ -z "$PACKAGES" ]]; then
|
|
print_error "--package requires at least one package name"
|
|
exit 1
|
|
fi
|
|
;;
|
|
--fast-ring)
|
|
FAST_RING_ONLY=true
|
|
shift
|
|
;;
|
|
--bootstrap)
|
|
BOOTSTRAP=true
|
|
shift
|
|
;;
|
|
--dry-run)
|
|
DRY_RUN=true
|
|
shift
|
|
;;
|
|
--sync-remote)
|
|
SYNC_REMOTE="$2"
|
|
shift 2
|
|
;;
|
|
--skip-prod-check)
|
|
SKIP_PROD_CHECK=true
|
|
shift
|
|
;;
|
|
-h | --help)
|
|
usage 0
|
|
;;
|
|
*)
|
|
print_error "Unknown option: $1"
|
|
usage 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
require_valid_mirror "$FROM"
|
|
require_valid_mirror "$TO"
|
|
|
|
# The pipeline only moves forward. stable -> rc is allowed for exactly two
|
|
# labeled purposes: fast-ring parity replication and the one-time bootstrap.
|
|
# edge -> stable is the legacy migrate path, kept for the transition cycle.
|
|
case "$FROM->$TO" in
|
|
"edge->rc" | "rc->stable") ;;
|
|
"edge->stable")
|
|
print_warning "edge -> stable is the legacy migrate path; new releases flow edge -> rc -> stable"
|
|
;;
|
|
"stable->rc")
|
|
if [[ "$FAST_RING_ONLY" != true && "$BOOTSTRAP" != true ]]; then
|
|
print_error "stable -> rc requires --fast-ring (parity replication) or --bootstrap (initial seed)"
|
|
exit 1
|
|
fi
|
|
;;
|
|
*)
|
|
print_error "Refusing $FROM -> $TO: packages only move forward (edge -> rc -> stable)"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
SOURCE_DIR="$REPO_ROOT/$FROM/$ARCH"
|
|
TARGET_DIR="$REPO_ROOT/$TO/$ARCH"
|
|
SOURCE_DB="$SOURCE_DIR/omarchy.db.tar.zst"
|
|
|
|
print_header "Advance Channel: $FROM -> $TO"
|
|
print_info "Architecture: $ARCH"
|
|
print_info "Source: $SOURCE_DIR"
|
|
print_info "Target: $TARGET_DIR"
|
|
[[ "$FAST_RING_ONLY" == true ]] && print_info "Scope: fast-ring packages only"
|
|
[[ "$BOOTSTRAP" == true ]] && print_info "Mode: bootstrap (initial rc seed)"
|
|
[[ -n "$PACKAGES" ]] && print_info "Packages: $PACKAGES"
|
|
|
|
if [[ ! -f "$SOURCE_DB" ]]; then
|
|
print_error "Source database not found: $SOURCE_DB"
|
|
echo "Nothing has been published to the $FROM channel on this host."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
print_warning "DRY RUN MODE - No changes will be made"
|
|
else
|
|
acquire_release_lock || exit 1
|
|
fi
|
|
|
|
# Manifest: "name<TAB>base<TAB>filename" per current package in the source db.
|
|
# Each desc record begins with %FILENAME%, so that marker both closes the
|
|
# previous record and opens the next.
|
|
read_manifest() {
|
|
tar -xOf "$SOURCE_DB" --wildcards '*/desc' 2>/dev/null | awk '
|
|
function emit() {
|
|
if (name != "" && filename != "") printf "%s\t%s\t%s\n", name, base, filename
|
|
name = ""; base = ""; filename = ""
|
|
}
|
|
$0 == "%FILENAME%" { emit(); getline; filename = $0; next }
|
|
$0 == "%NAME%" { getline; name = $0; next }
|
|
$0 == "%BASE%" { getline; base = $0; next }
|
|
END { emit() }
|
|
'
|
|
}
|
|
|
|
package_wanted() {
|
|
local name="$1" base="$2"
|
|
[[ -z "$PACKAGES" ]] && return 0
|
|
local want
|
|
for want in $PACKAGES; do
|
|
[[ "$want" == "$name" || "$want" == "$base" ]] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Split packages publish under their pkgname; eligibility metadata lives in
|
|
# the pkgbase directory. Packages whose PKGBUILD has since been removed from
|
|
# this repo default to moving: they are part of the source channel's set and
|
|
# leaving them behind would hole the destination.
|
|
package_eligible() {
|
|
local name="$1" base="$2"
|
|
local pkgdir
|
|
|
|
pkgdir=$(package_dir_for_name "$name" 2>/dev/null) ||
|
|
pkgdir=$(package_dir_for_name "$base" 2>/dev/null) || pkgdir=""
|
|
|
|
if [[ -z "$pkgdir" ]]; then
|
|
[[ "$FAST_RING_ONLY" == true ]] && return 1
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$FAST_RING_ONLY" == true ]]; then
|
|
package_is_fast_ring "$pkgdir" || return 1
|
|
fi
|
|
|
|
package_moves_to_channel "$pkgdir" "$TO"
|
|
}
|
|
|
|
mkdir -p "$TARGET_DIR"
|
|
|
|
COPIED=0
|
|
PRESENT=0
|
|
SKIPPED=0
|
|
MISSING_FILES=()
|
|
MISSING_SIGS=()
|
|
DIFFERING=()
|
|
|
|
while IFS=$'\t' read -r name base filename; do
|
|
package_wanted "$name" "$base" || continue
|
|
if ! package_eligible "$name" "$base"; then
|
|
SKIPPED=$((SKIPPED + 1))
|
|
continue
|
|
fi
|
|
|
|
src="$SOURCE_DIR/$filename"
|
|
sig="$src.sig"
|
|
dest="$TARGET_DIR/$filename"
|
|
|
|
if [[ ! -f "$src" ]]; then
|
|
MISSING_FILES+=("$filename")
|
|
continue
|
|
fi
|
|
if [[ ! -f "$sig" ]]; then
|
|
MISSING_SIGS+=("$filename")
|
|
continue
|
|
fi
|
|
|
|
if [[ -f "$dest" ]]; then
|
|
if cmp -s "$src" "$dest"; then
|
|
PRESENT=$((PRESENT + 1))
|
|
else
|
|
DIFFERING+=("$filename")
|
|
fi
|
|
continue
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
echo " would copy: $filename (+ .sig)"
|
|
else
|
|
cp -p "$src" "$dest"
|
|
cp -p "$sig" "$dest.sig"
|
|
echo " copied: $filename (+ .sig)"
|
|
fi
|
|
COPIED=$((COPIED + 1))
|
|
done < <(read_manifest)
|
|
|
|
echo ""
|
|
print_info "Copied: $COPIED | Already present: $PRESENT | Not eligible: $SKIPPED"
|
|
|
|
if [[ ${#MISSING_FILES[@]} -gt 0 ]]; then
|
|
print_error "${#MISSING_FILES[@]} package(s) named in the $FROM database are missing on disk:"
|
|
printf ' %s\n' "${MISSING_FILES[@]}"
|
|
echo "The $FROM channel is inconsistent; rebuild its database before advancing."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ${#MISSING_SIGS[@]} -gt 0 ]]; then
|
|
print_error "${#MISSING_SIGS[@]} package(s) have no detached signature in $FROM:"
|
|
printf ' %s\n' "${MISSING_SIGS[@]}"
|
|
echo "Signatures must travel with their packages. Backfill them by copying the"
|
|
echo "files into build-output/$FROM/$ARCH, running bin/repo sign --mirror $FROM,"
|
|
echo "and moving the .sig files back beside the packages — then re-run."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ${#DIFFERING[@]} -gt 0 ]]; then
|
|
print_warning "${#DIFFERING[@]} file(s) already published in $TO with different bytes (kept as-is):"
|
|
printf ' %s\n' "${DIFFERING[@]}"
|
|
echo "Published filenames are never rewritten. The destination copy stays"
|
|
echo "authoritative; a genuinely new build needs a new pkgver/pkgrel."
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
print_info "Dry run: skipping clean, database update, and sync"
|
|
exit 0
|
|
fi
|
|
|
|
print_info "Cleaning $TO repository..."
|
|
"$BUILD_ROOT/bin/clean-repo" --mirror "$TO" --arch "$ARCH"
|
|
|
|
print_info "Updating $TO repository database..."
|
|
"$BUILD_ROOT/bin/update-repo" --mirror "$TO" --arch "$ARCH"
|
|
|
|
echo ""
|
|
print_info "Syncing $TO repository to remote..."
|
|
SYNC_ARGS=("--mirror" "$TO" "--arch" "$ARCH")
|
|
[[ -n "$SYNC_REMOTE" ]] && SYNC_ARGS+=("--remote" "$SYNC_REMOTE")
|
|
[[ "$SKIP_PROD_CHECK" == true ]] && SYNC_ARGS+=("--skip-prod-check")
|
|
"$BUILD_ROOT/bin/sync-repo" "${SYNC_ARGS[@]}" || {
|
|
print_error "Sync failed"
|
|
exit 1
|
|
}
|
|
|
|
print_success "Advance complete: $FROM -> $TO"
|