#!/usr/bin/env bash set -euo pipefail if [[ "${EUID}" -ne 0 ]]; then echo "Run as root: sudo ./scripts/install.sh" >&2 exit 1 fi REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" BIN_DIR="/usr/local/bin" CONF_DIR="/etc/hecate" STATE_DIR="/var/lib/hecate" SYSTEMD_DIR="/etc/systemd/system" LIB_DIR="/usr/local/lib/hecate" START_NOW=1 INSTALL_DEPS=1 ENABLE_BOOTSTRAP="${HECATE_ENABLE_BOOTSTRAP:-0}" MANAGE_NUT="${HECATE_MANAGE_NUT:-1}" NUT_UPS_NAME="${HECATE_NUT_UPS_NAME:-atlasups}" NUT_VENDOR_ID="${HECATE_NUT_VENDOR_ID:-0764}" NUT_PRODUCT_ID="${HECATE_NUT_PRODUCT_ID:-0601}" NUT_MONITOR_USER="${HECATE_NUT_MONITOR_USER:-monuser}" NUT_MONITOR_PASSWORD="${HECATE_NUT_MONITOR_PASSWORD:-atlasupsmon}" while [[ $# -gt 0 ]]; do case "$1" in --no-start) START_NOW=0 shift ;; --skip-deps) INSTALL_DEPS=0 shift ;; *) echo "Unknown argument: $1" >&2 exit 1 ;; esac done ensure_apt_packages() { local missing=() for pkg in "$@"; do if ! dpkg -s "${pkg}" >/dev/null 2>&1; then missing+=("${pkg}") fi done if [[ ${#missing[@]} -eq 0 ]]; then return 0 fi echo "[install] apt install: ${missing[*]}" export DEBIAN_FRONTEND=noninteractive apt-get update -y apt-get install -y "${missing[@]}" } install_kubectl_if_missing() { if command -v kubectl >/dev/null 2>&1; then return 0 fi ensure_apt_packages kubernetes-client || true if command -v kubectl >/dev/null 2>&1; then return 0 fi echo "[install] installing kubectl via upstream binary" local arch arch="$(uname -m)" case "${arch}" in x86_64) arch="amd64" ;; aarch64|arm64) arch="arm64" ;; *) echo "Unsupported arch for kubectl install: ${arch}" >&2; return 1 ;; esac local version version="$(curl -fsSL https://dl.k8s.io/release/stable.txt)" curl -fsSL -o /usr/local/bin/kubectl "https://dl.k8s.io/release/${version}/bin/linux/${arch}/kubectl" chmod 0755 /usr/local/bin/kubectl } ensure_dependencies() { if [[ "${INSTALL_DEPS}" -eq 0 ]]; then echo "[install] skipping dependency installation" return 0 fi if ! command -v apt-get >/dev/null 2>&1; then echo "This installer currently supports apt-based hosts only." >&2 exit 1 fi ensure_apt_packages ca-certificates curl git openssh-client jq nut-client nut-server nut-monitor golang-go install_kubectl_if_missing } configure_nut() { if [[ "${MANAGE_NUT}" != "1" ]]; then echo "[install] skipping NUT configuration (HECATE_MANAGE_NUT=${MANAGE_NUT})" return 0 fi echo "[install] configuring NUT + udev for UPS ${NUT_UPS_NAME} (${NUT_VENDOR_ID}:${NUT_PRODUCT_ID})" install -d -m 0755 /etc/nut /etc/udev/rules.d cat > /etc/nut/nut.conf < /etc/nut/ups.conf < /etc/nut/upsd.users </dev/null 2>&1; then chown root:nut /etc/nut/upsd.users else chown root:root /etc/nut/upsd.users fi cat > /etc/nut/upsmon.conf < /etc/udev/rules.d/99-hecate-ups.rules </dev/null 2>&1 || true systemctl restart nut-driver-enumerator.service >/dev/null 2>&1 || true systemctl restart "nut-driver@${NUT_UPS_NAME}.service" >/dev/null 2>&1 || true systemctl restart nut-server.service nut-monitor.service >/dev/null 2>&1 || true } ensure_dependencies echo "[install] building hecate" cd "${REPO_DIR}" mkdir -p dist go build -o dist/hecate ./cmd/hecate echo "[install] installing binary" install -d -m 0755 "${BIN_DIR}" install -m 0755 dist/hecate "${BIN_DIR}/hecate" echo "[install] installing config + state dirs" install -d -m 0750 "${CONF_DIR}" install -d -m 0750 "${STATE_DIR}" install -d -m 0755 "${LIB_DIR}" if [[ ! -f "${CONF_DIR}/hecate.yaml" ]]; then install -m 0640 configs/hecate.example.yaml "${CONF_DIR}/hecate.yaml" echo "[install] wrote default config to ${CONF_DIR}/hecate.yaml" else echo "[install] keeping existing config at ${CONF_DIR}/hecate.yaml" fi echo "[install] installing systemd units" install -m 0644 deploy/systemd/hecate.service "${SYSTEMD_DIR}/hecate.service" install -m 0644 deploy/systemd/hecate-bootstrap.service "${SYSTEMD_DIR}/hecate-bootstrap.service" install -m 0644 deploy/systemd/hecate-update.service "${SYSTEMD_DIR}/hecate-update.service" install -m 0644 deploy/systemd/hecate-update.timer "${SYSTEMD_DIR}/hecate-update.timer" install -m 0755 scripts/hecate-self-update.sh "${LIB_DIR}/hecate-self-update.sh" configure_nut systemctl daemon-reload systemctl enable hecate.service hecate-update.timer if [[ "${ENABLE_BOOTSTRAP}" == "1" ]]; then systemctl enable hecate-bootstrap.service else systemctl disable hecate-bootstrap.service >/dev/null 2>&1 || true fi if [[ "${START_NOW}" -eq 1 ]]; then systemctl restart hecate.service systemctl restart hecate-update.timer echo "[install] hecate.service restarted" fi echo "[install] done" echo "Next steps:" echo " 1. Edit /etc/hecate/hecate.yaml" echo " 2. Run: hecate status --config /etc/hecate/hecate.yaml" echo " 3. Test dry run: hecate startup --config /etc/hecate/hecate.yaml" echo " 4. Trigger bootstrap now (db host): systemctl start hecate-bootstrap.service" echo " 5. Trigger self-update now: systemctl start hecate-update.service"