ananke/scripts/install.sh

242 lines
7.1 KiB
Bash
Raw Normal View History

#!/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:-auto}"
MANAGE_NUT="${HECATE_MANAGE_NUT:-1}"
NUT_UPS_NAME="${HECATE_NUT_UPS_NAME:-}"
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:-hecateupsmon}"
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
resolve_nut_ups_name() {
if [[ -n "${NUT_UPS_NAME}" ]]; then
return 0
fi
if [[ -f "${CONF_DIR}/hecate.yaml" ]]; then
local target=""
target="$(grep -Eo 'target:[[:space:]]*[A-Za-z0-9._-]+@localhost' "${CONF_DIR}/hecate.yaml" | head -n 1 | awk '{print $2}')"
if [[ -n "${target}" ]]; then
NUT_UPS_NAME="${target%@localhost}"
echo "[install] inferred NUT UPS name from config: ${NUT_UPS_NAME}"
return 0
fi
fi
NUT_UPS_NAME="pyrphoros"
echo "[install] defaulting NUT UPS name to ${NUT_UPS_NAME}"
}
read_hecate_role() {
if [[ ! -f "${CONF_DIR}/hecate.yaml" ]]; then
echo "coordinator"
return 0
fi
local role
role="$(awk '/^[[:space:]]*role:[[:space:]]*/ {print $2; exit}' "${CONF_DIR}/hecate.yaml" 2>/dev/null || true)"
if [[ -z "${role}" ]]; then
role="coordinator"
fi
echo "${role}"
}
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 <<EOF
MODE=standalone
EOF
cat > /etc/nut/ups.conf <<EOF
[${NUT_UPS_NAME}]
driver = usbhid-ups
port = auto
vendorid = ${NUT_VENDOR_ID}
productid = ${NUT_PRODUCT_ID}
pollinterval = 5
EOF
cat > /etc/nut/upsd.users <<EOF
[${NUT_MONITOR_USER}]
password = ${NUT_MONITOR_PASSWORD}
upsmon primary
EOF
chmod 0640 /etc/nut/upsd.users
if getent group nut >/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 <<EOF
RUN_AS_USER nut
MONITOR ${NUT_UPS_NAME}@localhost 1 ${NUT_MONITOR_USER} ${NUT_MONITOR_PASSWORD} primary
MINSUPPLIES 1
SHUTDOWNCMD "/sbin/shutdown -h +0"
POLLFREQ 5
POLLFREQALERT 5
HOSTSYNC 15
DEADTIME 15
POWERDOWNFLAG /etc/killpower
EOF
cat > /etc/udev/rules.d/99-hecate-ups.rules <<EOF
# Managed by Hecate install.sh: ensure UPS USB HID devices are readable by NUT
ACTION=="add|change", SUBSYSTEM=="usb", ATTR{idVendor}=="${NUT_VENDOR_ID}", ATTR{idProduct}=="${NUT_PRODUCT_ID}", MODE:="0660", GROUP:="nut"
EOF
udevadm control --reload-rules || true
udevadm trigger --subsystem-match=usb --attr-match=idVendor="${NUT_VENDOR_ID}" --attr-match=idProduct="${NUT_PRODUCT_ID}" || true
systemctl enable nut-driver-enumerator.service nut-server.service nut-monitor.service >/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"
resolve_nut_ups_name
configure_nut
systemctl daemon-reload
systemctl enable hecate.service hecate-update.timer
if [[ "${ENABLE_BOOTSTRAP}" == "1" ]]; then
systemctl enable hecate-bootstrap.service
elif [[ "${ENABLE_BOOTSTRAP}" == "0" ]]; then
systemctl disable hecate-bootstrap.service >/dev/null 2>&1 || true
else
role="$(read_hecate_role)"
systemctl enable hecate-bootstrap.service
echo "[install] auto-enabled hecate-bootstrap.service for role=${role}"
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"