Files
omarchy-pkgs/bin/setup
T

345 lines
12 KiB
Bash
Executable File

#!/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"
source "$BUILD_ROOT/helpers/docker-helpers.sh"
source "$BUILD_ROOT/helpers/paths.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"
PKG_PODMAN="podman"
elif command -v pacman >/dev/null 2>&1; then
DISTRO="arch"
PKG_BSDTAR="libarchive"
PKG_DOCKER="docker"
PKG_PODMAN="podman"
else
print_error "Unsupported distribution — need apt-get or pacman"
exit 1
fi
print_info "Distribution: $DISTRO"
# A fresh repository host still defaults to Docker. An explicit Podman choice,
# or a working Podman selected by the shared helper, is left alone.
if [[ -z "$CONTAINER_ENGINE" ]]; then
CONTAINER_ENGINE=docker
export CONTAINER_ENGINE
fi
if ! container_engine_supported; then
print_error "Unsupported CONTAINER_ENGINE: $CONTAINER_ENGINE (use docker or podman)"
exit 1
fi
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
PKG_ENGINE="$PKG_DOCKER"
ENGINE_NAME="Docker"
else
PKG_ENGINE="$PKG_PODMAN"
ENGINE_NAME="Podman"
fi
if [[ "$CHECK_ONLY" != true && $EUID -ne 0 ]]; then
print_error "Run as root (installing packages and systemd units)"
exit 1
fi
# The release timers are 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 "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 ""
# --- container engine --------------------------------------------------------
# The selected engine is left alone when it already works. A host may 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 $ENGINE_NAME..."
if command -v "$CONTAINER_ENGINE" >/dev/null 2>&1; then
print_step "$CONTAINER_ENGINE present: $("$CONTAINER_ENGINE" --version 2>/dev/null | head -1)"
if "$CONTAINER_ENGINE" info >/dev/null 2>&1; then
print_success "$ENGINE_NAME is installed and available — leaving it alone"
elif [[ "$CHECK_ONLY" == true ]]; then
print_warning "$ENGINE_NAME is installed but unavailable"
elif [[ "$CONTAINER_ENGINE" == "podman" ]]; then
print_error "Podman is installed but unavailable to the current user"
exit 1
else
print_info "Docker is installed but not running — starting it"
systemctl enable --now docker.service
if "$CONTAINER_ENGINE" 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
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
print_warning "Would install $PKG_ENGINE and enable it"
else
print_warning "Would install $PKG_ENGINE"
fi
else
print_info "Installing $PKG_ENGINE..."
install_packages "$PKG_ENGINE"
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
systemctl enable --now docker.service
fi
if "$CONTAINER_ENGINE" info >/dev/null 2>&1; then
print_success "$ENGINE_NAME installed and available"
else
print_error "$ENGINE_NAME installed but not responding"
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
echo " Check 'systemctl status docker' — builds cannot run without it."
fi
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 ""
# --- rc branch worktree -------------------------------------------------------
# The rc channel builds from the standing `rc` branch (where the release
# orchestrator commits RC PKGBUILD pins) in its own worktree, so rc builds
# never contend with the master checkout. OMARCHY_REPO_ROOT in the rc service
# points its published tree back at this checkout, so all three channels live
# together.
RC_WORKTREE="/root/omarchy-pkgs-rc"
# Read-only: --check must not fetch. Local refs first, then a network peek.
rc_branch_exists() {
git -C "$BUILD_ROOT" show-ref --verify --quiet refs/heads/rc && return 0
git -C "$BUILD_ROOT" show-ref --verify --quiet refs/remotes/origin/rc && return 0
git -C "$BUILD_ROOT" ls-remote --exit-code --heads origin rc >/dev/null 2>&1
}
if [[ "$CHECK_ONLY" == true ]]; then
if [[ -d "$RC_WORKTREE" ]]; then
print_success "rc worktree present: $RC_WORKTREE"
elif rc_branch_exists; then
print_warning "rc worktree would be created at $RC_WORKTREE"
else
# Expected before the first RC: nothing to create, and nothing to do —
# omarchy-release creates the branch and the rc build trigger creates the
# worktree on demand, so this resolves itself.
print_info "rc worktree not needed yet (no rc branch — created on the first RC cut)"
fi
elif [[ -d "$RC_WORKTREE" ]]; then
print_success "rc worktree present: $RC_WORKTREE"
else
print_info "Creating rc branch worktree at $RC_WORKTREE..."
git -C "$BUILD_ROOT" fetch origin rc 2>/dev/null || true
if git -C "$BUILD_ROOT" show-ref --verify --quiet refs/heads/rc; then
git -C "$BUILD_ROOT" worktree add "$RC_WORKTREE" rc
elif git -C "$BUILD_ROOT" show-ref --verify --quiet refs/remotes/origin/rc; then
git -C "$BUILD_ROOT" worktree add --track -b rc "$RC_WORKTREE" origin/rc
else
print_info "No rc branch yet — the first RC cut creates it, and the rc"
echo " release unit creates this worktree on demand. Nothing to do."
fi
fi
echo ""
# --- release timers ----------------------------------------------------------
print_info "Published architectures: $(published_arches | tr '\n' ' ')"
echo " (PUBLISHED_ARCHES in helpers/paths.sh; OMARCHY_ARCHES overrides a one-off command)"
echo ""
TIMERS=(omarchy-check-versions omarchy-auto-release-edge omarchy-auto-release-rc 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
# Release reporting is optional, but silence is indistinguishable from a
# broken hook, so say which chat (if any) will receive build reports.
if [[ -f "$CREDENTIALS" ]]; then
if grep -q 'OMARCHY_RELEASE_CHATBOT_URL' "$CREDENTIALS"; then
print_success "Release reports go to the dedicated release chat"
elif grep -q 'BASECAMP_CHATBOT_URL' "$CREDENTIALS"; then
# A working setup, not a problem: one chat receives everything. Splitting
# them is a preference, so this states the fact rather than nagging.
print_success "Release reports go to BASECAMP_CHATBOT_URL (shared with the sync workflows)"
echo " To give releases their own Basecamp chat, create one, add a chatbot"
echo " to it, and export its lines URL as OMARCHY_RELEASE_CHATBOT_URL here."
else
print_warning "No chatbot URL in $CREDENTIALS — build reports are disabled"
echo " Export OMARCHY_RELEASE_CHATBOT_URL to receive start/publish/failure"
echo " reports in Basecamp."
fi
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