Merge pull request #134 from omacom-io/push-pkgbase-selection

Push whole pkgbases, and stop the sync guard tripping over bsdtar
This commit is contained in:
David Heinemeier Hansson
2026-08-12 14:45:00 +02:00
committed by GitHub
5 changed files with 307 additions and 24 deletions
+24 -16
View File
@@ -228,9 +228,11 @@ setting is named for the repository rather than for building, which happens
wherever you like. The same setting tells `bin/omarchy-pkgs release` which host
to poke after a release push.
Split packages are selected by their own names, not their pkgbase — pushing
`nvidia-580xx-utils` does not carry `nvidia-580xx-dkms` along. Omit `--package` to
push everything built.
`--package` means the same thing as it does to `build`: a pkgbase, whose every
output ships together. Pushing `nvidia-580xx-utils` carries `nvidia-580xx-dkms`
and `opencl-nvidia-580xx` with it, because that is what the build produced. An
output's own name still selects just that one, for publishing a single package
on purpose. Omit `--package` to push everything built.
Publishing signs and promotes everything staged on the host, not just what this
push uploaded, so `push` stops when it finds packages already staged there —
@@ -558,21 +560,27 @@ State files are stored in `/root/.state/`:
### Installation
```bash
# Copy systemd units
cp /root/omarchy-pkgs/systemd/*.service /root/omarchy-pkgs/systemd/*.timer /etc/systemd/system/
# Reload systemd
systemctl daemon-reload
# Enable and start timers
systemctl enable --now omarchy-check-versions.timer
systemctl enable --now omarchy-auto-release-edge.timer
systemctl enable --now omarchy-auto-release-stable.timer
# Create state directory
mkdir -p /root/.state
ssh root@<host> 'cd /root/omarchy-pkgs && bin/setup'
```
`bin/setup` installs the dependencies, ensures Docker is running, creates the
state directory, and installs and enables the release timers. It works on
Debian/Ubuntu and on Arch, and is idempotent, so run it again whenever a
dependency is added.
The host does not need to be Arch: makepkg, repo-add and package signing all
run inside containers, so it needs only Docker, rclone, bsdtar, jq, git and
rsync. Docker is left alone when it already works, rather than replacing a
working installation from Docker's own repository with the distribution's.
```bash
bin/repo setup --check # Report what is missing, change nothing
bin/repo setup --skip-timers # Prepare the host without the release timers
```
Signing credentials (`/root/.omarchy/build-credentials`) and the rclone remote
hold secrets, so setup reports on them rather than creating them.
### Management
```bash
+19 -7
View File
@@ -143,29 +143,41 @@ if [[ -z "$PACKAGES" && "$ASSUME_YES" == true ]] && ! on_repo_host; then
exit 1
fi
# --package means the same thing here as it does to bin/build: a pkgbase, whose
# every output ships together. Selecting only the artifact whose filename matched
# would publish one third of a split package like nvidia-580xx-utils and silently
# leave nvidia-580xx-dkms and opencl-nvidia-580xx behind. An output's own name
# still matches, for pushing just one of them on purpose.
#
# pkgbase comes from .PKGINFO rather than the PKGBUILD: it is what makepkg
# actually recorded, and it needs no guessing about which directory built what.
pkgbase_of() {
bsdtar -xOf "$1" .PKGINFO 2>/dev/null |
awk -F ' = ' '$1 == "pkgbase" { print $2; exit }'
}
FILES=()
if [[ -z "$PACKAGES" ]]; then
FILES=("${ALL_FILES[@]}")
else
declare -A MATCHED=()
for file in "${ALL_FILES[@]}"; do
# name-version-release-arch.pkg.tar.zst -> name
pkgname="${file%-*-*-*.pkg.tar.*}"
pkgbase=$(pkgbase_of "$BUILD_OUTPUT_DIR/$file")
for wanted in $PACKAGES; do
if [[ "$pkgname" == "$wanted" ]]; then
if [[ "$pkgname" == "$wanted" || "$pkgbase" == "$wanted" ]]; then
FILES+=("$file")
MATCHED["$wanted"]=1
break
fi
done
done
for wanted in $PACKAGES; do
found=false
for file in "${FILES[@]}"; do
[[ "${file%-*-*-*.pkg.tar.*}" == "$wanted" ]] && found=true && break
done
if [[ "$found" != true ]]; then
if [[ -z "${MATCHED[$wanted]:-}" ]]; then
print_error "No built artifact for '$wanted' in $BUILD_OUTPUT_DIR"
print_warning "Split packages are named after their outputs, not their pkgbase"
print_warning "Name a package or the pkgbase it was built from"
exit 1
fi
done
+5
View File
@@ -60,6 +60,7 @@ show_usage() {
echo " sync Sync repository to remote"
echo " push Upload local builds to the repository host and publish them there"
echo " deploy Build locally, then push: one command from a build machine"
echo " setup Install everything the repository host needs"
echo ""
echo "Typical workflows:"
echo " $0 release # Complete release workflow"
@@ -135,6 +136,10 @@ deploy)
"$SCRIPT_DIR/deploy" "$@" 2>&1 | tee "$LOG_FILE"
exit ${PIPESTATUS[0]}
;;
setup)
"$SCRIPT_DIR/setup" "$@" 2>&1 | tee "$LOG_FILE"
exit ${PIPESTATUS[0]}
;;
-h | --help | help)
show_usage
;;
Executable
+246
View File
@@ -0,0 +1,246 @@
#!/bin/bash
# Prepare this machine to serve as the Omarchy repository host.
#
# The host receives uploads, promotes packages into the published tree, and
# syncs that tree to the mirror. Everything Arch-specific — makepkg, repo-add,
# package signing — happens inside containers, so the host itself needs very
# little and does not need to be Arch. The production host is Ubuntu.
#
# Run this on the host itself:
# ssh root@<host> 'cd /root/omarchy-pkgs && bin/setup'
#
# It is idempotent — run it again after adding a dependency.
set -e
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh"
CHECK_ONLY=false
SKIP_TIMERS=false
STATE_DIR="${OMARCHY_STATE_DIR:-/root/.state}"
CREDENTIALS="/root/.omarchy/build-credentials"
print_header "Omarchy Repository Host Setup"
while [[ $# -gt 0 ]]; do
case $1 in
--check)
CHECK_ONLY=true
shift
;;
--skip-timers)
SKIP_TIMERS=true
shift
;;
-h | --help)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Install and enable everything the repository host needs."
echo "Works on Debian/Ubuntu (apt) and Arch (pacman)."
echo ""
echo "Options:"
echo " --check Report what is missing, change nothing"
echo " --skip-timers Do not install or enable the release timers"
echo " -h, --help Show this help message"
exit 0
;;
*)
print_error "Unknown option: $1"
exit 1
;;
esac
done
# --- distribution ------------------------------------------------------------
# Package names differ where it matters: bsdtar is libarchive-tools on Debian
# and libarchive on Arch, and Docker is docker.io rather than docker.
if command -v apt-get >/dev/null 2>&1; then
DISTRO="debian"
PKG_BSDTAR="libarchive-tools"
PKG_DOCKER="docker.io"
elif command -v pacman >/dev/null 2>&1; then
DISTRO="arch"
PKG_BSDTAR="libarchive"
PKG_DOCKER="docker"
else
print_error "Unsupported distribution — need apt-get or pacman"
exit 1
fi
print_info "Distribution: $DISTRO"
if [[ "$CHECK_ONLY" != true && $EUID -ne 0 ]]; then
print_error "Run as root (installing packages and systemd units)"
exit 1
fi
# Docker and the release timers are both systemd units. Say so plainly rather
# than failing later on a missing command — a container is the usual way to end
# up here, and it cannot be a repository host.
if [[ "$CHECK_ONLY" != true ]] && ! command -v systemctl >/dev/null 2>&1; then
print_error "systemctl not found — the repository host must run systemd"
echo ""
echo "Docker and the release timers are systemd units. This looks like a"
echo "container; run setup on the host itself."
exit 1
fi
install_packages() {
case "$DISTRO" in
debian)
apt-get update -qq
DEBIAN_FRONTEND=noninteractive apt-get install -y "$@"
;;
arch)
pacman -S --needed --noconfirm "$@"
;;
esac
}
# --- dependencies ------------------------------------------------------------
# Only what the host runs directly. Signing and repo-add happen in containers,
# so gnupg and the Arch build tools are deliberately absent from this list.
REQUIREMENTS=(
"bsdtar:$PKG_BSDTAR" # reads repo databases and .PKGINFO out of packages
"git:git" # pulls this repository
"jq:jq" # package metadata in .omarchy/package.json
"rsync:rsync" # receives uploads from bin/repo push
"rclone:rclone" # publishes to the mirror
)
print_info "Checking dependencies..."
MISSING_PACKAGES=()
for requirement in "${REQUIREMENTS[@]}"; do
cmd="${requirement%%:*}"
pkg="${requirement#*:}"
if command -v "$cmd" >/dev/null 2>&1; then
print_step "$cmd"
else
print_warning "$cmd missing (provided by $pkg)"
MISSING_PACKAGES+=("$pkg")
fi
done
echo ""
if [[ ${#MISSING_PACKAGES[@]} -gt 0 ]]; then
if [[ "$CHECK_ONLY" == true ]]; then
print_warning "Would install: ${MISSING_PACKAGES[*]}"
else
print_info "Installing: ${MISSING_PACKAGES[*]}"
install_packages "${MISSING_PACKAGES[@]}"
print_success "Dependencies installed"
fi
else
print_success "All dependencies present"
fi
echo ""
# --- docker ------------------------------------------------------------------
# Docker is left alone when it already works. A host may well be running a
# version from Docker's own repository rather than the distribution's, and
# replacing that underneath a working builder would be a poor trade for
# tidiness.
print_info "Checking Docker..."
if command -v docker >/dev/null 2>&1; then
print_step "docker present: $(docker --version 2>/dev/null | head -1)"
if docker info >/dev/null 2>&1; then
print_success "Docker is installed and running — leaving it alone"
elif [[ "$CHECK_ONLY" == true ]]; then
print_warning "Docker is installed but not running; would start it"
else
print_info "Docker is installed but not running — starting it"
systemctl enable --now docker.service
if docker info >/dev/null 2>&1; then
print_success "Docker started"
else
print_error "Docker is installed but still not responding"
echo " Check 'systemctl status docker' — builds cannot run without it."
exit 1
fi
fi
elif [[ "$CHECK_ONLY" == true ]]; then
print_warning "Would install $PKG_DOCKER and enable it"
else
print_info "Installing $PKG_DOCKER..."
install_packages "$PKG_DOCKER"
systemctl enable --now docker.service
if docker info >/dev/null 2>&1; then
print_success "Docker installed and running"
else
print_error "Docker installed but not responding"
echo " Check 'systemctl status docker' — builds cannot run without it."
exit 1
fi
fi
echo ""
# --- state directory ---------------------------------------------------------
if [[ -d "$STATE_DIR" ]]; then
print_success "State directory present: $STATE_DIR"
elif [[ "$CHECK_ONLY" == true ]]; then
print_warning "Would create $STATE_DIR"
else
mkdir -p "$STATE_DIR"
print_success "Created $STATE_DIR"
fi
echo ""
# --- release timers ----------------------------------------------------------
TIMERS=(omarchy-check-versions omarchy-auto-release-edge omarchy-auto-release-stable)
if [[ "$SKIP_TIMERS" == true ]]; then
print_info "Skipping release timers (--skip-timers)"
elif [[ "$CHECK_ONLY" == true ]]; then
for timer in "${TIMERS[@]}"; do
if systemctl is-enabled "$timer.timer" >/dev/null 2>&1; then
print_success "$timer.timer is enabled"
else
print_warning "$timer.timer would be enabled"
fi
done
else
print_info "Installing release timers..."
cp "$BUILD_ROOT"/systemd/*.service "$BUILD_ROOT"/systemd/*.timer /etc/systemd/system/
systemctl daemon-reload
for timer in "${TIMERS[@]}"; do
systemctl enable --now "$timer.timer"
print_step "$timer.timer"
done
print_success "Release timers enabled"
fi
echo ""
# --- credentials -------------------------------------------------------------
# These hold secrets, so setup reports on them rather than creating them.
print_info "Checking credentials..."
if [[ -f "$CREDENTIALS" ]]; then
print_success "Signing credentials present: $CREDENTIALS"
else
print_warning "Missing $CREDENTIALS"
echo " Must export GPG_PRIVATE_KEY and GPG_PASSPHRASE; the release"
echo " services source it before signing."
fi
if rclone listremotes 2>/dev/null | grep -q '^pkgs.omarchy.org:'; then
print_success "rclone remote 'pkgs.omarchy.org' configured"
else
print_warning "rclone remote 'pkgs.omarchy.org' not configured"
echo " bin/repo sync publishes there; configure it with 'rclone config'."
fi
echo ""
if [[ "$CHECK_ONLY" == true ]]; then
print_info "Check complete — nothing was changed"
else
print_success "Repository host ready"
fi
+13 -1
View File
@@ -141,6 +141,17 @@ if grep -qx 'omarchy\.db' <<<"$REMOTE_LISTING"; then
REMOTE_DB_FILE=$(mktemp)
trap 'rm -f "$REMOTE_DB_FILE"' EXIT
rclone cat "$REMOTE/$DESTINATION_DIRECTORY/omarchy.db" --s3-no-head >"$REMOTE_DB_FILE" 2>/dev/null
if ! command -v bsdtar >/dev/null 2>&1; then
print_error "bsdtar is not installed"
echo ""
echo "The repository host needs it to read the repository database."
echo "Run bin/setup to install everything this host requires."
exit 1
fi
# bsdtar, not tar: repo-add has used both gzip and zstd for the database, and
# libarchive detects either without being told which.
REMOTE_NAMES=$(bsdtar -tf "$REMOTE_DB_FILE" 2>/dev/null | sed 's|/.*||' |
sed -E 's/-[^-]+-[^-]+$//' | sort -u)
@@ -148,7 +159,8 @@ if grep -qx 'omarchy\.db' <<<"$REMOTE_LISTING"; then
print_error "The remote database exists but could not be read"
echo ""
echo "Refusing to sync rather than assume the remote is empty. Check that"
echo "bsdtar is installed and that omarchy.db is not corrupt."
echo "omarchy.db is not corrupt:"
echo " rclone cat $REMOTE/$DESTINATION_DIRECTORY/omarchy.db | bsdtar -tf -"
exit 1
fi