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