240 lines
7.5 KiB
Bash
Executable File
240 lines
7.5 KiB
Bash
Executable File
# 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 <<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-ananke-ups.rules <<EOF
|
|
# Managed by ananke 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
|
|
}
|