# Host bootstrap helpers for the Ananke installer. resolve_nut_ups_name() { if [[ -n "${NUT_UPS_NAME}" ]]; then return 0 fi if [[ -f "${CONF_DIR}/ananke.yaml" ]]; then local target="" target="$(grep -Eo 'target:[[:space:]]*[A-Za-z0-9._-]+@localhost' "${CONF_DIR}/ananke.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}" } ensure_ananke_kubeconfig() { local kubeconfig_path kubeconfig_path="$(migration_yaml_lookup "kubeconfig")" if [[ -z "${kubeconfig_path}" ]]; then kubeconfig_path="/etc/ananke/kubeconfig" fi install -d -m 0750 "$(dirname "${kubeconfig_path}")" if [[ -s "${kubeconfig_path}" ]] && KUBECONFIG="${kubeconfig_path}" kubectl version --request-timeout=5s >/dev/null 2>&1; then return 0 fi if [[ -r /etc/rancher/k3s/k3s.yaml ]]; then install -m 0600 /etc/rancher/k3s/k3s.yaml "${kubeconfig_path}" echo "[install] refreshed kubeconfig from local /etc/rancher/k3s/k3s.yaml" if KUBECONFIG="${kubeconfig_path}" kubectl version --request-timeout=5s >/dev/null 2>&1; then return 0 fi fi local cp_name cp_host ssh_user ssh_port ssh_cfg ssh_key cp_name="$(first_control_plane_name)" if [[ -z "${cp_name}" ]]; then echo "[install] warning: cannot infer control plane name; kubeconfig bootstrap skipped" return 0 fi cp_host="$(lookup_node_host "${cp_name}")" if [[ -z "${cp_host}" ]]; then cp_host="${cp_name}" fi ssh_user="$(migration_yaml_lookup "ssh_user")" ssh_port="$(migration_yaml_lookup "ssh_port")" ssh_cfg="$(migration_yaml_lookup "ssh_config_file")" ssh_key="$(migration_yaml_lookup "ssh_identity_file")" if [[ -z "${ssh_port}" ]]; then ssh_port="2277" fi local target target="${cp_host}" if [[ -n "${ssh_user}" ]]; then target="${ssh_user}@${cp_host}" fi local ssh_args=( -o BatchMode=yes -o ConnectTimeout=8 -o StrictHostKeyChecking=accept-new ) if [[ -n "${ssh_cfg}" && -f "${ssh_cfg}" ]]; then ssh_args+=(-F "${ssh_cfg}") fi if [[ -n "${ssh_key}" && -f "${ssh_key}" ]]; then ssh_args+=(-i "${ssh_key}") fi if [[ -n "${ssh_port}" ]]; then ssh_args+=(-p "${ssh_port}") fi local remote_cfg if remote_cfg="$(ssh "${ssh_args[@]}" "${target}" "sudo cat /etc/rancher/k3s/k3s.yaml" 2>/dev/null)"; then printf '%s\n' "${remote_cfg}" > "${kubeconfig_path}" sed -Ei "s#server:[[:space:]]*https://127\\.0\\.0\\.1:6443#server: https://${cp_host}:6443#g" "${kubeconfig_path}" || true chmod 0600 "${kubeconfig_path}" echo "[install] bootstrapped kubeconfig from control plane ${cp_name} (${cp_host})" if KUBECONFIG="${kubeconfig_path}" kubectl version --request-timeout=5s >/dev/null 2>&1; then return 0 fi else echo "[install] warning: failed to fetch kubeconfig from ${cp_name} (${cp_host})" fi echo "[install] warning: kubeconfig at ${kubeconfig_path} is still not validated; local startup fallback may fail" } ensure_ananke_ssh_identity() { local key_path key_dir key_user key_comment key_path="$(migration_yaml_lookup "ssh_identity_file")" if [[ -z "${key_path}" ]]; then key_path="/home/atlas/.ssh/id_ed25519" fi key_dir="$(dirname "${key_path}")" key_comment="ananke-$(hostname)-forward" key_user="root" if [[ "${key_path}" == /home/*/* ]]; then key_user="${key_path#/home/}" key_user="${key_user%%/*}" fi if ! id "${key_user}" >/dev/null 2>&1; then echo "[install] warning: ssh identity owner ${key_user} does not exist; skipping key bootstrap for ${key_path}" return 0 fi install -d -m 0700 -o "${key_user}" -g "${key_user}" "${key_dir}" if [[ ! -s "${key_path}" ]]; then echo "[install] generating missing SSH identity at ${key_path}" if [[ "${key_user}" == "root" ]]; then ssh-keygen -q -t ed25519 -N '' -C "${key_comment}" -f "${key_path}" else runuser -u "${key_user}" -- ssh-keygen -q -t ed25519 -N '' -C "${key_comment}" -f "${key_path}" fi fi chown "${key_user}:${key_user}" "${key_path}" "${key_path}.pub" 2>/dev/null || true chmod 0600 "${key_path}" || true chmod 0644 "${key_path}.pub" || true } 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 (ANANKE_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-ananke-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 }