Files
omarchy-pkgs/bin/advance-channel
T

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"