From f8c1cbb072878eaa25a9117a5e60ad7eaeacd122 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 12 Aug 2026 05:04:06 -0700 Subject: [PATCH] Add bin/setup to prepare a repository host The sync guard could not read the repository database because bsdtar was not installed on the host, and the first fix was to parse around its absence. The better answer is for the host to have what the tooling needs: libarchive ships the library pacman links against without necessarily installing the binary, so bsdtar being present was an assumption, not a fact. bin/setup installs the dependencies, enables Docker, creates the state directory, and installs and enables the release timers -- the steps the README previously listed by hand. It is idempotent and takes --check to report without changing anything. Signing credentials and the rclone remote hold secrets, so it reports on those rather than creating them. sync-repo goes back to reading the database with bsdtar alone, and says to run bin/setup when it is missing. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 26 +++---- bin/repo | 5 ++ bin/setup | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++ bin/sync-repo | 22 +++--- 4 files changed, 217 insertions(+), 23 deletions(-) create mode 100755 bin/setup diff --git a/README.md b/README.md index f6433b6..46b60e3 100644 --- a/README.md +++ b/README.md @@ -560,21 +560,21 @@ 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@ 'cd /root/omarchy-pkgs && bin/setup' ``` +`bin/setup` installs the dependencies, enables Docker, creates the state +directory, and installs and enables the release timers. It is idempotent, so +run it again whenever a dependency is added. + +```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 diff --git a/bin/repo b/bin/repo index 0e2c40e..48c91cc 100755 --- a/bin/repo +++ b/bin/repo @@ -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 ;; diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..d3bf979 --- /dev/null +++ b/bin/setup @@ -0,0 +1,187 @@ +#!/bin/bash +# Prepare this machine to serve as the Omarchy repository host. +# +# The repository host builds packages, signs them, keeps the published tree, and +# syncs it to the mirror. Everything it needs is installed and enabled here, so +# the tooling can assume its toolchain instead of working around whatever +# happens to be present. +# +# Run this on the host itself: +# ssh root@ '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 + +# Packages, in "command:package" form so a missing tool names its own fix. +# +# tar, vercmp and repo-add come from base and pacman, which any Arch install +# already has. bsdtar does not: libarchive ships the library pacman links +# against without necessarily installing the binary. +REQUIREMENTS=( + "bsdtar:libarchive" # reads repo databases and .PKGINFO out of packages + "git:git" # PKGBUILD sources and release commits + "jq:jq" # package metadata in .omarchy/package.json + "curl:curl" # upstream version checks + "rsync:rsync" # receives uploads from bin/repo push + "gpg:gnupg" # package signing + "rclone:rclone" # publishes to the mirror + "docker:docker" # builds run in containers + "makepkg:base-devel" # source verification for releases +) + +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 "" + 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 + +if ! command -v pacman >/dev/null 2>&1; then + print_error "This is not an Arch system — the repository host must be Arch" + exit 1 +fi + +if [[ "$CHECK_ONLY" != true && $EUID -ne 0 ]]; then + print_error "Run as root (installing packages and systemd units)" + exit 1 +fi + +# --- dependencies ------------------------------------------------------------ + +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[*]}" + pacman -S --needed --noconfirm "${MISSING_PACKAGES[@]}" + print_success "Dependencies installed" + fi +else + print_success "All dependencies present" +fi +echo "" + +# --- docker ------------------------------------------------------------------ + +if [[ "$CHECK_ONLY" == true ]]; then + if systemctl is-enabled docker.service >/dev/null 2>&1; then + print_success "docker.service is enabled" + else + print_warning "docker.service would be enabled" + fi +else + print_info "Enabling docker..." + systemctl enable --now docker.service + print_success "docker.service enabled" +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 ---------------------------------------------------------- + +if [[ "$SKIP_TIMERS" == true ]]; then + print_info "Skipping release timers (--skip-timers)" +elif [[ "$CHECK_ONLY" == true ]]; then + for timer in omarchy-check-versions omarchy-auto-release-edge omarchy-auto-release-stable; 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 omarchy-check-versions omarchy-auto-release-edge omarchy-auto-release-stable; 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 diff --git a/bin/sync-repo b/bin/sync-repo index b0574c1..52fa72f 100755 --- a/bin/sync-repo +++ b/bin/sync-repo @@ -142,23 +142,25 @@ if grep -qx 'omarchy\.db' <<<"$REMOTE_LISTING"; then trap 'rm -f "$REMOTE_DB_FILE"' EXIT rclone cat "$REMOTE/$DESTINATION_DIRECTORY/omarchy.db" --s3-no-head >"$REMOTE_DB_FILE" 2>/dev/null - # Download to a file rather than piping: GNU tar detects the compression from - # a seekable archive but not from a pipe, and the database has been both gzip - # and zstd over its life. bsdtar reads either too, but it is not present on - # every host, so tar leads and bsdtar covers a tar too old for zstd. - REMOTE_ENTRIES=$(tar -tf "$REMOTE_DB_FILE" 2>/dev/null) - if [[ -z "$REMOTE_ENTRIES" ]] && command -v bsdtar >/dev/null 2>&1; then - REMOTE_ENTRIES=$(bsdtar -tf "$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 - REMOTE_NAMES=$(printf '%s\n' "$REMOTE_ENTRIES" | sed 's|/.*||' | - sed -E 's/-[^-]+-[^-]+$//' | sort -u | grep -v '^$') + # 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) if [[ -z "$REMOTE_NAMES" ]]; 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 "omarchy.db is not corrupt: rclone cat $REMOTE/$DESTINATION_DIRECTORY/omarchy.db | tar -t" + echo "omarchy.db is not corrupt:" + echo " rclone cat $REMOTE/$DESTINATION_DIRECTORY/omarchy.db | bsdtar -tf -" exit 1 fi