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:
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
;;
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user