Only failures were reported, so a push could reach the mirror with no way to know short of querying the database by hand. Release runs now report: - start: channel, arch, host, and the commit being built - published: the packages and versions that went out, duration, channel URL - no-changes: the run found nothing to build - promoted: what advance moved between channels, including the rc bootstrap and fast-ring replication - failed: unchanged, plus the commit context the other reports carry Release traffic goes to OMARCHY_RELEASE_CHATBOT_URL, falling back to BASECAMP_CHATBOT_URL, so build reports stop drowning the repository chat the sync workflows post to. bin/setup reports which destination is configured. The published list is captured after the build step because promote moves the files out of build-output, and is capped at 25 entries so a full rebuild does not produce an unreadable wall of chat.
306 lines
10 KiB
Bash
Executable File
306 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 build"
|
|
echo " trigger 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
|
|
print_warning "Release reports fall back to BASECAMP_CHATBOT_URL (the repository chat)"
|
|
echo " Export OMARCHY_RELEASE_CHATBOT_URL in $CREDENTIALS to send them"
|
|
echo " to their own chat instead."
|
|
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
|