Reporting to BASECAMP_CHATBOT_URL is a working setup — the second variable exists only for those who want release traffic in its own chat. setup now states which chat receives reports instead of warning about the common case, and the README frames the split as optional rather than expected.
308 lines
10 KiB
Bash
Executable File
308 lines
10 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"
|
|
|
|
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 ""
|
|
|
|
# --- 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 ----------------------------------------------------------
|
|
|
|
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
|