installer: migrate legacy hecate host assets to ananke

This commit is contained in:
Brad Stein 2026-04-07 12:17:38 -03:00
parent 4c17d22de6
commit 26ca73302a

View File

@ -8,20 +8,20 @@ fi
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BIN_DIR="/usr/local/bin" BIN_DIR="/usr/local/bin"
CONF_DIR="/etc/hecate" CONF_DIR="/etc/ananke"
STATE_DIR="/var/lib/hecate" STATE_DIR="/var/lib/ananke"
SYSTEMD_DIR="/etc/systemd/system" SYSTEMD_DIR="/etc/systemd/system"
LIB_DIR="/usr/local/lib/hecate" LIB_DIR="/usr/local/lib/ananke"
START_NOW=1 START_NOW=1
INSTALL_DEPS=1 INSTALL_DEPS=1
ENABLE_BOOTSTRAP="${HECATE_ENABLE_BOOTSTRAP:-auto}" ENABLE_BOOTSTRAP="${ANANKE_ENABLE_BOOTSTRAP:-auto}"
MANAGE_NUT="${HECATE_MANAGE_NUT:-1}" MANAGE_NUT="${ANANKE_MANAGE_NUT:-1}"
NUT_UPS_NAME="${HECATE_NUT_UPS_NAME:-}" NUT_UPS_NAME="${ANANKE_NUT_UPS_NAME:-}"
NUT_VENDOR_ID="${HECATE_NUT_VENDOR_ID:-0764}" NUT_VENDOR_ID="${ANANKE_NUT_VENDOR_ID:-0764}"
NUT_PRODUCT_ID="${HECATE_NUT_PRODUCT_ID:-0601}" NUT_PRODUCT_ID="${ANANKE_NUT_PRODUCT_ID:-0601}"
NUT_MONITOR_USER="${HECATE_NUT_MONITOR_USER:-monuser}" NUT_MONITOR_USER="${ANANKE_NUT_MONITOR_USER:-monuser}"
NUT_MONITOR_PASSWORD="${HECATE_NUT_MONITOR_PASSWORD:-hecateupsmon}" NUT_MONITOR_PASSWORD="${ANANKE_NUT_MONITOR_PASSWORD:-anankeupsmon}"
FORCE_CONFIG_TEMPLATE="${HECATE_FORCE_CONFIG_TEMPLATE:-}" FORCE_CONFIG_TEMPLATE="${ANANKE_FORCE_CONFIG_TEMPLATE:-}"
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
@ -45,9 +45,9 @@ resolve_nut_ups_name() {
return 0 return 0
fi fi
if [[ -f "${CONF_DIR}/hecate.yaml" ]]; then if [[ -f "${CONF_DIR}/ananke.yaml" ]]; then
local target="" local target=""
target="$(grep -Eo 'target:[[:space:]]*[A-Za-z0-9._-]+@localhost' "${CONF_DIR}/hecate.yaml" | head -n 1 | awk '{print $2}')" target="$(grep -Eo 'target:[[:space:]]*[A-Za-z0-9._-]+@localhost' "${CONF_DIR}/ananke.yaml" | head -n 1 | awk '{print $2}')"
if [[ -n "${target}" ]]; then if [[ -n "${target}" ]]; then
NUT_UPS_NAME="${target%@localhost}" NUT_UPS_NAME="${target%@localhost}"
echo "[install] inferred NUT UPS name from config: ${NUT_UPS_NAME}" echo "[install] inferred NUT UPS name from config: ${NUT_UPS_NAME}"
@ -59,13 +59,13 @@ resolve_nut_ups_name() {
echo "[install] defaulting NUT UPS name to ${NUT_UPS_NAME}" echo "[install] defaulting NUT UPS name to ${NUT_UPS_NAME}"
} }
read_hecate_role() { read_ananke_role() {
if [[ ! -f "${CONF_DIR}/hecate.yaml" ]]; then if [[ ! -f "${CONF_DIR}/ananke.yaml" ]]; then
echo "coordinator" echo "coordinator"
return 0 return 0
fi fi
local role local role
role="$(awk '/^[[:space:]]*role:[[:space:]]*/ {print $2; exit}' "${CONF_DIR}/hecate.yaml" 2>/dev/null || true)" role="$(awk '/^[[:space:]]*role:[[:space:]]*/ {print $2; exit}' "${CONF_DIR}/ananke.yaml" 2>/dev/null || true)"
if [[ -z "${role}" ]]; then if [[ -z "${role}" ]]; then
role="coordinator" role="coordinator"
fi fi
@ -74,7 +74,7 @@ read_hecate_role() {
migration_yaml_lookup() { migration_yaml_lookup() {
local key="$1" local key="$1"
awk -F': *' -v k="${key}" '$1 == k {print $2; exit}' "${CONF_DIR}/hecate.yaml" 2>/dev/null || true awk -F': *' -v k="${key}" '$1 == k {print $2; exit}' "${CONF_DIR}/ananke.yaml" 2>/dev/null || true
} }
first_control_plane_name() { first_control_plane_name() {
@ -82,19 +82,19 @@ first_control_plane_name() {
/^control_planes:[[:space:]]*$/ {in_list=1; next} /^control_planes:[[:space:]]*$/ {in_list=1; next}
in_list && /^[[:space:]]*-[[:space:]]*/ {gsub(/^[[:space:]]*-[[:space:]]*/, "", $0); print $0; exit} in_list && /^[[:space:]]*-[[:space:]]*/ {gsub(/^[[:space:]]*-[[:space:]]*/, "", $0); print $0; exit}
in_list && /^[^[:space:]]/ {in_list=0} in_list && /^[^[:space:]]/ {in_list=0}
' "${CONF_DIR}/hecate.yaml" 2>/dev/null || true ' "${CONF_DIR}/ananke.yaml" 2>/dev/null || true
} }
lookup_node_host() { lookup_node_host() {
local node="$1" local node="$1"
awk -F': *' -v n="${node}" '$1 == " " n {print $2; exit}' "${CONF_DIR}/hecate.yaml" 2>/dev/null || true awk -F': *' -v n="${node}" '$1 == " " n {print $2; exit}' "${CONF_DIR}/ananke.yaml" 2>/dev/null || true
} }
ensure_hecate_kubeconfig() { ensure_ananke_kubeconfig() {
local kubeconfig_path local kubeconfig_path
kubeconfig_path="$(migration_yaml_lookup "kubeconfig")" kubeconfig_path="$(migration_yaml_lookup "kubeconfig")"
if [[ -z "${kubeconfig_path}" ]]; then if [[ -z "${kubeconfig_path}" ]]; then
kubeconfig_path="/etc/hecate/kubeconfig" kubeconfig_path="/etc/ananke/kubeconfig"
fi fi
install -d -m 0750 "$(dirname "${kubeconfig_path}")" install -d -m 0750 "$(dirname "${kubeconfig_path}")"
@ -164,14 +164,14 @@ ensure_hecate_kubeconfig() {
echo "[install] warning: kubeconfig at ${kubeconfig_path} is still not validated; local startup fallback may fail" echo "[install] warning: kubeconfig at ${kubeconfig_path} is still not validated; local startup fallback may fail"
} }
ensure_hecate_ssh_identity() { ensure_ananke_ssh_identity() {
local key_path key_dir key_user key_comment local key_path key_dir key_user key_comment
key_path="$(migration_yaml_lookup "ssh_identity_file")" key_path="$(migration_yaml_lookup "ssh_identity_file")"
if [[ -z "${key_path}" ]]; then if [[ -z "${key_path}" ]]; then
key_path="/home/atlas/.ssh/id_ed25519" key_path="/home/atlas/.ssh/id_ed25519"
fi fi
key_dir="$(dirname "${key_path}")" key_dir="$(dirname "${key_path}")"
key_comment="hecate-$(hostname)-forward" key_comment="ananke-$(hostname)-forward"
key_user="root" key_user="root"
if [[ "${key_path}" == /home/*/* ]]; then if [[ "${key_path}" == /home/*/* ]]; then
@ -198,51 +198,51 @@ ensure_hecate_ssh_identity() {
chmod 0644 "${key_path}.pub" || true chmod 0644 "${key_path}.pub" || true
} }
migrate_hecate_config() { migrate_ananke_config() {
if [[ ! -f "${CONF_DIR}/hecate.yaml" ]]; then if [[ ! -f "${CONF_DIR}/ananke.yaml" ]]; then
return 0 return 0
fi fi
local changed=0 local changed=0
local role_hint local role_hint
role_hint="$(read_hecate_role)" role_hint="$(read_ananke_role)"
if grep -Eq 'default_budget_seconds:[[:space:]]*300' "${CONF_DIR}/hecate.yaml"; then if grep -Eq 'default_budget_seconds:[[:space:]]*300' "${CONF_DIR}/ananke.yaml"; then
sed -Ei 's/(default_budget_seconds:[[:space:]]*)300/\11380/' "${CONF_DIR}/hecate.yaml" sed -Ei 's/(default_budget_seconds:[[:space:]]*)300/\11380/' "${CONF_DIR}/ananke.yaml"
echo "[install] migrated default_budget_seconds 300 -> 1380 in ${CONF_DIR}/hecate.yaml" echo "[install] migrated default_budget_seconds 300 -> 1380 in ${CONF_DIR}/ananke.yaml"
changed=1 changed=1
fi fi
if grep -Eq 'runtime_safety_factor:[[:space:]]*1\.10' "${CONF_DIR}/hecate.yaml"; then if grep -Eq 'runtime_safety_factor:[[:space:]]*1\.10' "${CONF_DIR}/ananke.yaml"; then
sed -Ei 's/(runtime_safety_factor:[[:space:]]*)1\.10/\11.25/' "${CONF_DIR}/hecate.yaml" sed -Ei 's/(runtime_safety_factor:[[:space:]]*)1\.10/\11.25/' "${CONF_DIR}/ananke.yaml"
echo "[install] migrated runtime_safety_factor 1.10 -> 1.25 in ${CONF_DIR}/hecate.yaml" echo "[install] migrated runtime_safety_factor 1.10 -> 1.25 in ${CONF_DIR}/ananke.yaml"
changed=1 changed=1
fi fi
if grep -Eq '^ssh_node_users:[[:space:]]*$' "${CONF_DIR}/hecate.yaml" \ if grep -Eq '^ssh_node_users:[[:space:]]*$' "${CONF_DIR}/ananke.yaml" \
&& grep -Eq '^ titan-24:[[:space:]]*tethys[[:space:]]*$' "${CONF_DIR}/hecate.yaml"; then && grep -Eq '^ titan-24:[[:space:]]*tethys[[:space:]]*$' "${CONF_DIR}/ananke.yaml"; then
sed -Ei 's/^ titan-24:[[:space:]]*tethys[[:space:]]*$/ titan-24: atlas/' "${CONF_DIR}/hecate.yaml" sed -Ei 's/^ titan-24:[[:space:]]*tethys[[:space:]]*$/ titan-24: atlas/' "${CONF_DIR}/ananke.yaml"
echo "[install] migrated ssh_node_users titan-24 override to atlas" echo "[install] migrated ssh_node_users titan-24 override to atlas"
changed=1 changed=1
fi fi
if grep -Eq '^ command_timeout_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/hecate.yaml" \ if grep -Eq '^ command_timeout_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/ananke.yaml" \
&& ! grep -Eq '^ startup_guard_max_age_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/hecate.yaml"; then && ! grep -Eq '^ startup_guard_max_age_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/ananke.yaml"; then
sed -Ei '/^ command_timeout_seconds:[[:space:]]*[0-9]+/a\ startup_guard_max_age_seconds: 900' "${CONF_DIR}/hecate.yaml" sed -Ei '/^ command_timeout_seconds:[[:space:]]*[0-9]+/a\ startup_guard_max_age_seconds: 900' "${CONF_DIR}/ananke.yaml"
echo "[install] added coordination.startup_guard_max_age_seconds=900" echo "[install] added coordination.startup_guard_max_age_seconds=900"
changed=1 changed=1
fi fi
if ! grep -Eq '^ peer_hosts:' "${CONF_DIR}/hecate.yaml"; then if ! grep -Eq '^ peer_hosts:' "${CONF_DIR}/ananke.yaml"; then
if [[ "${role_hint}" == "peer" ]] && grep -Eq '^ forward_shutdown_host:[[:space:]]*[A-Za-z0-9._-]+' "${CONF_DIR}/hecate.yaml"; then if [[ "${role_hint}" == "peer" ]] && grep -Eq '^ forward_shutdown_host:[[:space:]]*[A-Za-z0-9._-]+' "${CONF_DIR}/ananke.yaml"; then
local peer_host local peer_host
peer_host="$(awk -F': *' '/^ forward_shutdown_host:[[:space:]]*/ {print $2; exit}' "${CONF_DIR}/hecate.yaml" 2>/dev/null || true)" peer_host="$(awk -F': *' '/^ forward_shutdown_host:[[:space:]]*/ {print $2; exit}' "${CONF_DIR}/ananke.yaml" 2>/dev/null || true)"
if [[ -n "${peer_host}" ]]; then if [[ -n "${peer_host}" ]]; then
sed -Ei '/^ forward_shutdown_config:[[:space:]]*.*$/a\ peer_hosts:\n - '"${peer_host}"'' "${CONF_DIR}/hecate.yaml" sed -Ei '/^ forward_shutdown_config:[[:space:]]*.*$/a\ peer_hosts:\n - '"${peer_host}"'' "${CONF_DIR}/ananke.yaml"
echo "[install] added coordination.peer_hosts from forward_shutdown_host (${peer_host})" echo "[install] added coordination.peer_hosts from forward_shutdown_host (${peer_host})"
changed=1 changed=1
fi fi
elif [[ "${role_hint}" == "coordinator" ]] && grep -Eq '^ titan-24:[[:space:]]*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' "${CONF_DIR}/hecate.yaml"; then elif [[ "${role_hint}" == "coordinator" ]] && grep -Eq '^ titan-24:[[:space:]]*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' "${CONF_DIR}/ananke.yaml"; then
sed -Ei '/^ forward_shutdown_config:[[:space:]]*.*$/a\ peer_hosts:\n - titan-24' "${CONF_DIR}/hecate.yaml" sed -Ei '/^ forward_shutdown_config:[[:space:]]*.*$/a\ peer_hosts:\n - titan-24' "${CONF_DIR}/ananke.yaml"
echo "[install] added coordination.peer_hosts default (titan-24) for coordinator role" echo "[install] added coordination.peer_hosts default (titan-24) for coordinator role"
changed=1 changed=1
else else
sed -Ei '/^ forward_shutdown_config:[[:space:]]*.*$/a\ peer_hosts: []' "${CONF_DIR}/hecate.yaml" sed -Ei '/^ forward_shutdown_config:[[:space:]]*.*$/a\ peer_hosts: []' "${CONF_DIR}/ananke.yaml"
echo "[install] added coordination.peer_hosts empty default" echo "[install] added coordination.peer_hosts empty default"
changed=1 changed=1
fi fi
@ -252,58 +252,58 @@ migrate_hecate_config() {
if [[ -z "${default_restore_cp}" ]]; then if [[ -z "${default_restore_cp}" ]]; then
default_restore_cp="titan-0a" default_restore_cp="titan-0a"
fi fi
if grep -Eq '^ api_poll_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/hecate.yaml" \ if grep -Eq '^ api_poll_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/ananke.yaml" \
&& ! grep -Eq '^ auto_etcd_restore_on_api_failure:[[:space:]]*(true|false)' "${CONF_DIR}/hecate.yaml"; then && ! grep -Eq '^ auto_etcd_restore_on_api_failure:[[:space:]]*(true|false)' "${CONF_DIR}/ananke.yaml"; then
sed -Ei '/^ api_poll_seconds:[[:space:]]*[0-9]+/a\ require_time_sync: true\n time_sync_wait_seconds: 240\n time_sync_poll_seconds: 5\n reconcile_access_on_boot: true\n auto_etcd_restore_on_api_failure: true\n etcd_restore_control_plane: '"${default_restore_cp}"'' "${CONF_DIR}/hecate.yaml" sed -Ei '/^ api_poll_seconds:[[:space:]]*[0-9]+/a\ require_time_sync: true\n time_sync_wait_seconds: 240\n time_sync_poll_seconds: 5\n reconcile_access_on_boot: true\n auto_etcd_restore_on_api_failure: true\n etcd_restore_control_plane: '"${default_restore_cp}"'' "${CONF_DIR}/ananke.yaml"
echo "[install] added startup.auto_etcd_restore_on_api_failure + startup.etcd_restore_control_plane defaults" echo "[install] added startup.auto_etcd_restore_on_api_failure + startup.etcd_restore_control_plane defaults"
changed=1 changed=1
fi fi
if grep -Eq '^ api_poll_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/hecate.yaml" \ if grep -Eq '^ api_poll_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/ananke.yaml" \
&& ! grep -Eq '^ require_time_sync:[[:space:]]*(true|false)' "${CONF_DIR}/hecate.yaml"; then && ! grep -Eq '^ require_time_sync:[[:space:]]*(true|false)' "${CONF_DIR}/ananke.yaml"; then
sed -Ei '/^ api_poll_seconds:[[:space:]]*[0-9]+/a\ require_time_sync: true\n time_sync_wait_seconds: 240\n time_sync_poll_seconds: 5\n reconcile_access_on_boot: true' "${CONF_DIR}/hecate.yaml" sed -Ei '/^ api_poll_seconds:[[:space:]]*[0-9]+/a\ require_time_sync: true\n time_sync_wait_seconds: 240\n time_sync_poll_seconds: 5\n reconcile_access_on_boot: true' "${CONF_DIR}/ananke.yaml"
echo "[install] added startup time sync + access reconciliation defaults" echo "[install] added startup time sync + access reconciliation defaults"
changed=1 changed=1
fi fi
if grep -Eq '^ time_sync_poll_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/hecate.yaml" \ if grep -Eq '^ time_sync_poll_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/ananke.yaml" \
&& ! grep -Eq '^ time_sync_mode:[[:space:]]*(strict|quorum)' "${CONF_DIR}/hecate.yaml"; then && ! grep -Eq '^ time_sync_mode:[[:space:]]*(strict|quorum)' "${CONF_DIR}/ananke.yaml"; then
sed -Ei '/^ time_sync_poll_seconds:[[:space:]]*[0-9]+/a\ time_sync_mode: quorum\n time_sync_quorum: 2' "${CONF_DIR}/hecate.yaml" sed -Ei '/^ time_sync_poll_seconds:[[:space:]]*[0-9]+/a\ time_sync_mode: quorum\n time_sync_quorum: 2' "${CONF_DIR}/ananke.yaml"
echo "[install] added startup time sync quorum defaults" echo "[install] added startup time sync quorum defaults"
changed=1 changed=1
fi fi
if grep -Eq '^ etcd_restore_control_plane:[[:space:]]*[A-Za-z0-9._-]+' "${CONF_DIR}/hecate.yaml" \ if grep -Eq '^ etcd_restore_control_plane:[[:space:]]*[A-Za-z0-9._-]+' "${CONF_DIR}/ananke.yaml" \
&& ! grep -Eq '^ require_storage_ready:[[:space:]]*(true|false)' "${CONF_DIR}/hecate.yaml"; then && ! grep -Eq '^ require_storage_ready:[[:space:]]*(true|false)' "${CONF_DIR}/ananke.yaml"; then
sed -Ei '/^ etcd_restore_control_plane:[[:space:]]*[A-Za-z0-9._-]+/a\ require_storage_ready: true\n storage_ready_wait_seconds: 420\n storage_ready_poll_seconds: 5\n storage_min_ready_nodes: 2\n storage_critical_pvcs:\n - vault/data-vault-0\n - postgres/postgres-data-postgres-0\n - gitea/gitea-data\n - sso/keycloak-data' "${CONF_DIR}/hecate.yaml" sed -Ei '/^ etcd_restore_control_plane:[[:space:]]*[A-Za-z0-9._-]+/a\ require_storage_ready: true\n storage_ready_wait_seconds: 420\n storage_ready_poll_seconds: 5\n storage_min_ready_nodes: 2\n storage_critical_pvcs:\n - vault/data-vault-0\n - postgres/postgres-data-postgres-0\n - gitea/gitea-data\n - sso/keycloak-data' "${CONF_DIR}/ananke.yaml"
echo "[install] added startup storage readiness defaults" echo "[install] added startup storage readiness defaults"
changed=1 changed=1
fi fi
if grep -Eq '^ storage_critical_pvcs:[[:space:]]*$' "${CONF_DIR}/hecate.yaml" \ if grep -Eq '^ storage_critical_pvcs:[[:space:]]*$' "${CONF_DIR}/ananke.yaml" \
&& ! grep -Eq '^ require_post_start_probes:[[:space:]]*(true|false)' "${CONF_DIR}/hecate.yaml"; then && ! grep -Eq '^ require_post_start_probes:[[:space:]]*(true|false)' "${CONF_DIR}/ananke.yaml"; then
sed -Ei '/^ - sso\/keycloak-data$/a\ require_post_start_probes: true\n post_start_probe_wait_seconds: 240\n post_start_probe_poll_seconds: 5\n post_start_probes:\n - https://scm.bstein.dev/user/login\n - https://metrics.bstein.dev/login\n vault_unseal_key_file: /var/lib/hecate/vault-unseal.key' "${CONF_DIR}/hecate.yaml" sed -Ei '/^ - sso\/keycloak-data$/a\ require_post_start_probes: true\n post_start_probe_wait_seconds: 240\n post_start_probe_poll_seconds: 5\n post_start_probes:\n - https://scm.bstein.dev/api/healthz\n - https://metrics.bstein.dev/api/health\n require_service_checklist: true\n service_checklist_wait_seconds: 420\n service_checklist_poll_seconds: 5\n service_checklist_stability_seconds: 120\n service_checklist:\n - name: gitea-api\n url: https://scm.bstein.dev/api/healthz\n accepted_statuses: [200]\n body_contains: pass\n timeout_seconds: 12\n - name: grafana-api\n url: https://metrics.bstein.dev/api/health\n accepted_statuses: [200]\n body_contains: '\''\"database\":\"ok\"'\''\n timeout_seconds: 12\n vault_unseal_key_file: /var/lib/ananke/vault-unseal.key' "${CONF_DIR}/ananke.yaml"
echo "[install] added startup post-start probe + vault key fallback defaults" echo "[install] added startup post-start probe + vault key fallback defaults"
changed=1 changed=1
fi fi
if grep -Eq '^ - https://sso.bstein.dev/realms/atlas/.well-known/openid-configuration$' "${CONF_DIR}/hecate.yaml"; then if grep -Eq '^ - https://sso.bstein.dev/realms/atlas/.well-known/openid-configuration$' "${CONF_DIR}/ananke.yaml"; then
sed -Ei '/^ - https:\/\/sso\.bstein\.dev\/realms\/atlas\/\.well-known\/openid-configuration$/d' "${CONF_DIR}/hecate.yaml" sed -Ei '/^ - https:\/\/sso\.bstein\.dev\/realms\/atlas\/\.well-known\/openid-configuration$/d' "${CONF_DIR}/ananke.yaml"
echo "[install] removed sso OIDC probe from startup.post_start_probes (returns 404 in current deployment)" echo "[install] removed sso OIDC probe from startup.post_start_probes (returns 404 in current deployment)"
changed=1 changed=1
fi fi
if ! grep -Eq '^ vault_unseal_key_file:[[:space:]]*/var/lib/hecate/vault-unseal.key' "${CONF_DIR}/hecate.yaml"; then if ! grep -Eq '^ vault_unseal_key_file:[[:space:]]*/var/lib/ananke/vault-unseal.key' "${CONF_DIR}/ananke.yaml"; then
if grep -Eq '^startup:[[:space:]]*$' "${CONF_DIR}/hecate.yaml" && grep -Eq '^ post_start_probes:[[:space:]]*$' "${CONF_DIR}/hecate.yaml"; then if grep -Eq '^startup:[[:space:]]*$' "${CONF_DIR}/ananke.yaml" && grep -Eq '^ post_start_probes:[[:space:]]*$' "${CONF_DIR}/ananke.yaml"; then
sed -Ei '/^ - https:\/\/metrics\.bstein\.dev\/login$/a\ vault_unseal_key_file: /var/lib/hecate/vault-unseal.key' "${CONF_DIR}/hecate.yaml" sed -Ei '/^ - https:\/\/metrics\.bstein\.dev\/api\/health$/a\ vault_unseal_key_file: /var/lib/ananke/vault-unseal.key' "${CONF_DIR}/ananke.yaml"
echo "[install] added startup.vault_unseal_key_file default" echo "[install] added startup.vault_unseal_key_file default"
changed=1 changed=1
fi fi
fi fi
if ! grep -Eq '^ vault_unseal_breakglass_timeout_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/hecate.yaml"; then if ! grep -Eq '^ vault_unseal_breakglass_timeout_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/ananke.yaml"; then
if grep -Eq '^ vault_unseal_key_file:[[:space:]]*/var/lib/hecate/vault-unseal.key' "${CONF_DIR}/hecate.yaml"; then if grep -Eq '^ vault_unseal_key_file:[[:space:]]*/var/lib/ananke/vault-unseal.key' "${CONF_DIR}/ananke.yaml"; then
sed -Ei '/^ vault_unseal_key_file:[[:space:]]*\/var\/lib\/hecate\/vault-unseal.key$/a\ vault_unseal_breakglass_command: ""\n vault_unseal_breakglass_timeout_seconds: 15' "${CONF_DIR}/hecate.yaml" sed -Ei '/^ vault_unseal_key_file:[[:space:]]*\/var\/lib\/ananke\/vault-unseal.key$/a\ vault_unseal_breakglass_command: ""\n vault_unseal_breakglass_timeout_seconds: 15' "${CONF_DIR}/ananke.yaml"
echo "[install] added startup break-glass fallback defaults" echo "[install] added startup break-glass fallback defaults"
changed=1 changed=1
fi fi
fi fi
local role local role
role="$(read_hecate_role)" role="$(read_ananke_role)"
local inventory_block local inventory_block
local managed_block local managed_block
local workers_block local workers_block
@ -428,43 +428,43 @@ migrate_hecate_config() {
fi fi
if [[ -n "${inventory_block}" ]]; then if [[ -n "${inventory_block}" ]]; then
if grep -Eq '^ssh_node_hosts:[[:space:]]*\{\}[[:space:]]*$' "${CONF_DIR}/hecate.yaml"; then if grep -Eq '^ssh_node_hosts:[[:space:]]*\{\}[[:space:]]*$' "${CONF_DIR}/ananke.yaml"; then
perl -0pi -e 's#ssh_node_hosts:\s*\{\}\n#'"${inventory_block}"'\n#s' "${CONF_DIR}/hecate.yaml" perl -0pi -e 's#ssh_node_hosts:\s*\{\}\n#'"${inventory_block}"'\n#s' "${CONF_DIR}/ananke.yaml"
echo "[install] hydrated ssh_node_hosts inventory for role=${role}" echo "[install] hydrated ssh_node_hosts inventory for role=${role}"
changed=1 changed=1
fi fi
fi fi
if grep -Eq '^workers:[[:space:]]*\[\][[:space:]]*$' "${CONF_DIR}/hecate.yaml"; then if grep -Eq '^workers:[[:space:]]*\[\][[:space:]]*$' "${CONF_DIR}/ananke.yaml"; then
perl -0pi -e 's#workers:\s*\[\]\n#'"${workers_block}"'\n#s' "${CONF_DIR}/hecate.yaml" perl -0pi -e 's#workers:\s*\[\]\n#'"${workers_block}"'\n#s' "${CONF_DIR}/ananke.yaml"
echo "[install] hydrated workers inventory for startup/shutdown orchestration" echo "[install] hydrated workers inventory for startup/shutdown orchestration"
changed=1 changed=1
fi fi
if [[ -n "${managed_block}" ]]; then if [[ -n "${managed_block}" ]]; then
if grep -Eq '^ssh_managed_nodes:[[:space:]]*\[\][[:space:]]*$' "${CONF_DIR}/hecate.yaml"; then if grep -Eq '^ssh_managed_nodes:[[:space:]]*\[\][[:space:]]*$' "${CONF_DIR}/ananke.yaml"; then
perl -0pi -e 's#ssh_managed_nodes:\s*\[\]\n#'"${managed_block}"'\n#s' "${CONF_DIR}/hecate.yaml" perl -0pi -e 's#ssh_managed_nodes:\s*\[\]\n#'"${managed_block}"'\n#s' "${CONF_DIR}/ananke.yaml"
echo "[install] hydrated ssh_managed_nodes inventory for role=${role}" echo "[install] hydrated ssh_managed_nodes inventory for role=${role}"
changed=1 changed=1
fi fi
if ! grep -Eq '^ - titan-04$' "${CONF_DIR}/hecate.yaml" || ! grep -Eq '^ - titan-21$' "${CONF_DIR}/hecate.yaml"; then if ! grep -Eq '^ - titan-04$' "${CONF_DIR}/ananke.yaml" || ! grep -Eq '^ - titan-21$' "${CONF_DIR}/ananke.yaml"; then
perl -0pi -e 's#ssh_managed_nodes:\n(?: - [^\n]*\n)*#'"${managed_block}"'\n#s' "${CONF_DIR}/hecate.yaml" perl -0pi -e 's#ssh_managed_nodes:\n(?: - [^\n]*\n)*#'"${managed_block}"'\n#s' "${CONF_DIR}/ananke.yaml"
echo "[install] refreshed ssh_managed_nodes coverage for role=${role}" echo "[install] refreshed ssh_managed_nodes coverage for role=${role}"
changed=1 changed=1
fi fi
fi fi
if [[ "${role}" == "peer" ]]; then if [[ "${role}" == "peer" ]]; then
if grep -Eq '^ssh_managed_nodes:[[:space:]]*$' "${CONF_DIR}/hecate.yaml" \ if grep -Eq '^ssh_managed_nodes:[[:space:]]*$' "${CONF_DIR}/ananke.yaml" \
&& grep -Eq '^ - titan-db$' "${CONF_DIR}/hecate.yaml" \ && grep -Eq '^ - titan-db$' "${CONF_DIR}/ananke.yaml" \
&& grep -Eq '^ - titan-24$' "${CONF_DIR}/hecate.yaml" \ && grep -Eq '^ - titan-24$' "${CONF_DIR}/ananke.yaml" \
&& ! grep -Eq '^ - titan-0a$' "${CONF_DIR}/hecate.yaml"; then && ! grep -Eq '^ - titan-0a$' "${CONF_DIR}/ananke.yaml"; then
perl -0pi -e 's#ssh_managed_nodes:\n - titan-db\n - titan-24\n#ssh_managed_nodes:\n - titan-db\n - titan-0a\n - titan-0b\n - titan-0c\n - titan-04\n - titan-05\n - titan-06\n - titan-07\n - titan-08\n - titan-09\n - titan-10\n - titan-11\n - titan-12\n - titan-13\n - titan-14\n - titan-15\n - titan-17\n - titan-18\n - titan-19\n - titan-20\n - titan-21\n - titan-22\n - titan-24\n#s' "${CONF_DIR}/hecate.yaml" perl -0pi -e 's#ssh_managed_nodes:\n - titan-db\n - titan-24\n#ssh_managed_nodes:\n - titan-db\n - titan-0a\n - titan-0b\n - titan-0c\n - titan-04\n - titan-05\n - titan-06\n - titan-07\n - titan-08\n - titan-09\n - titan-10\n - titan-11\n - titan-12\n - titan-13\n - titan-14\n - titan-15\n - titan-17\n - titan-18\n - titan-19\n - titan-20\n - titan-21\n - titan-22\n - titan-24\n#s' "${CONF_DIR}/ananke.yaml"
echo "[install] expanded peer ssh_managed_nodes for bootstrap fallback coverage" echo "[install] expanded peer ssh_managed_nodes for bootstrap fallback coverage"
changed=1 changed=1
fi fi
if ! grep -Eq '^ - services/keycloak$' "${CONF_DIR}/hecate.yaml" || ! grep -Eq '^ - infrastructure/cert-manager$' "${CONF_DIR}/hecate.yaml" || ! grep -Eq '^ - services/oauth2-proxy$' "${CONF_DIR}/hecate.yaml"; then if ! grep -Eq '^ - services/keycloak$' "${CONF_DIR}/ananke.yaml" || ! grep -Eq '^ - infrastructure/cert-manager$' "${CONF_DIR}/ananke.yaml" || ! grep -Eq '^ - services/oauth2-proxy$' "${CONF_DIR}/ananke.yaml"; then
perl -0pi -e 's#local_bootstrap_paths:\n(?: - [^\n]*\n)*#local_bootstrap_paths:\n - infrastructure/core\n - clusters/atlas/flux-system\n - infrastructure/sources/helm\n - infrastructure/metallb\n - infrastructure/traefik\n - infrastructure/cert-manager\n - infrastructure/vault-csi\n - infrastructure/vault-injector\n - services/vault\n - infrastructure/postgres\n - services/gitea\n - services/keycloak\n - services/oauth2-proxy\n#s' "${CONF_DIR}/hecate.yaml" perl -0pi -e 's#local_bootstrap_paths:\n(?: - [^\n]*\n)*#local_bootstrap_paths:\n - infrastructure/core\n - clusters/atlas/flux-system\n - infrastructure/sources/helm\n - infrastructure/metallb\n - infrastructure/traefik\n - infrastructure/cert-manager\n - infrastructure/vault-csi\n - infrastructure/vault-injector\n - services/vault\n - infrastructure/postgres\n - services/gitea\n - services/keycloak\n - services/oauth2-proxy\n#s' "${CONF_DIR}/ananke.yaml"
echo "[install] refreshed peer local_bootstrap_paths for full fallback bootstrap parity" echo "[install] refreshed peer local_bootstrap_paths for full fallback bootstrap parity"
changed=1 changed=1
fi fi
@ -472,7 +472,7 @@ migrate_hecate_config() {
fi fi
if [[ "${changed}" -eq 1 ]]; then if [[ "${changed}" -eq 1 ]]; then
chmod 0640 "${CONF_DIR}/hecate.yaml" || true chmod 0640 "${CONF_DIR}/ananke.yaml" || true
fi fi
} }
@ -527,9 +527,97 @@ ensure_dependencies() {
install_kubectl_if_missing install_kubectl_if_missing
} }
legacy_path_rewrite() {
local src="$1"
local dst="$2"
sed \
-e 's#/etc/hecate#/etc/ananke#g' \
-e 's#/var/lib/hecate#/var/lib/ananke#g' \
-e 's#/usr/local/bin/hecate#/usr/local/bin/ananke#g' \
-e 's#hecate\.lock#ananke.lock#g' \
-e 's#/etc/hecate/hecate.yaml#/etc/ananke/ananke.yaml#g' \
-e 's#/etc/hecate/kubeconfig#/etc/ananke/kubeconfig#g' \
-e 's#/var/lib/hecate/vault-unseal.key#/var/lib/ananke/vault-unseal.key#g' \
"${src}" > "${dst}"
}
migrate_legacy_hecate_install() {
local legacy_conf_dir="/etc/hecate"
local legacy_state_dir="/var/lib/hecate"
local legacy_systemd_dir="/etc/systemd/system"
install -d -m 0750 "${CONF_DIR}"
install -d -m 0750 "${STATE_DIR}"
if [[ ! -f "${CONF_DIR}/ananke.yaml" && -f "${legacy_conf_dir}/hecate.yaml" ]]; then
echo "[install] migrating legacy config ${legacy_conf_dir}/hecate.yaml -> ${CONF_DIR}/ananke.yaml"
legacy_path_rewrite "${legacy_conf_dir}/hecate.yaml" "${CONF_DIR}/ananke.yaml"
chmod 0640 "${CONF_DIR}/ananke.yaml"
fi
if [[ ! -f "${CONF_DIR}/kubeconfig" && -f "${legacy_conf_dir}/kubeconfig" ]]; then
echo "[install] migrating legacy kubeconfig ${legacy_conf_dir}/kubeconfig -> ${CONF_DIR}/kubeconfig"
install -m 0600 "${legacy_conf_dir}/kubeconfig" "${CONF_DIR}/kubeconfig"
fi
if [[ ! -f "${STATE_DIR}/vault-unseal.key" && -f "${legacy_state_dir}/vault-unseal.key" ]]; then
echo "[install] migrating legacy vault key ${legacy_state_dir}/vault-unseal.key -> ${STATE_DIR}/vault-unseal.key"
install -m 0600 "${legacy_state_dir}/vault-unseal.key" "${STATE_DIR}/vault-unseal.key"
fi
if [[ ! -f "${STATE_DIR}/runs.json" && -f "${legacy_state_dir}/runs.json" ]]; then
echo "[install] migrating legacy run history ${legacy_state_dir}/runs.json -> ${STATE_DIR}/runs.json"
install -m 0640 "${legacy_state_dir}/runs.json" "${STATE_DIR}/runs.json"
fi
if [[ ! -f "${STATE_DIR}/intent.json" && -f "${legacy_state_dir}/intent.json" ]]; then
echo "[install] migrating legacy intent state ${legacy_state_dir}/intent.json -> ${STATE_DIR}/intent.json"
install -m 0640 "${legacy_state_dir}/intent.json" "${STATE_DIR}/intent.json"
fi
if [[ ! -f "${STATE_DIR}/ananke.lock" && -f "${legacy_state_dir}/hecate.lock" ]]; then
echo "[install] migrating legacy lock ${legacy_state_dir}/hecate.lock -> ${STATE_DIR}/ananke.lock"
install -m 0640 "${legacy_state_dir}/hecate.lock" "${STATE_DIR}/ananke.lock"
fi
if [[ -d "${legacy_systemd_dir}" ]]; then
if ls "${legacy_systemd_dir}"/hecate*.service >/dev/null 2>&1 || ls "${legacy_systemd_dir}"/hecate*.timer >/dev/null 2>&1; then
echo "[install] detected legacy hecate systemd unit files; will retire after ananke install"
fi
fi
}
retire_legacy_hecate_install() {
local ts backup_dir
ts="$(date +%Y%m%d%H%M%S)"
backup_dir="/var/backups/ananke-legacy-hecate-${ts}"
systemctl disable --now hecate.service hecate-bootstrap.service hecate-update.timer >/dev/null 2>&1 || true
systemctl stop hecate-update.service >/dev/null 2>&1 || true
if [[ -d /etc/hecate || -d /var/lib/hecate || -d /usr/local/lib/hecate ]]; then
install -d -m 0750 "${backup_dir}"
[[ -d /etc/hecate ]] && cp -a /etc/hecate "${backup_dir}/" || true
[[ -d /var/lib/hecate ]] && cp -a /var/lib/hecate "${backup_dir}/" || true
[[ -d /usr/local/lib/hecate ]] && cp -a /usr/local/lib/hecate "${backup_dir}/" || true
[[ -f /usr/local/bin/hecate ]] && install -m 0755 /usr/local/bin/hecate "${backup_dir}/hecate.bin" || true
echo "[install] backed up legacy hecate assets to ${backup_dir}"
fi
rm -f \
/etc/systemd/system/hecate.service \
/etc/systemd/system/hecate-bootstrap.service \
/etc/systemd/system/hecate-update.service \
/etc/systemd/system/hecate-update.timer
rm -f /usr/local/bin/hecate
rm -rf /usr/local/lib/hecate
rm -rf /etc/hecate
rm -rf /var/lib/hecate
}
configure_nut() { configure_nut() {
if [[ "${MANAGE_NUT}" != "1" ]]; then if [[ "${MANAGE_NUT}" != "1" ]]; then
echo "[install] skipping NUT configuration (HECATE_MANAGE_NUT=${MANAGE_NUT})" echo "[install] skipping NUT configuration (ANANKE_MANAGE_NUT=${MANAGE_NUT})"
return 0 return 0
fi fi
@ -573,8 +661,8 @@ DEADTIME 15
POWERDOWNFLAG /etc/killpower POWERDOWNFLAG /etc/killpower
EOF EOF
cat > /etc/udev/rules.d/99-hecate-ups.rules <<EOF cat > /etc/udev/rules.d/99-ananke-ups.rules <<EOF
# Managed by Hecate install.sh: ensure UPS USB HID devices are readable by NUT # 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" ACTION=="add|change", SUBSYSTEM=="usb", ATTR{idVendor}=="${NUT_VENDOR_ID}", ATTR{idProduct}=="${NUT_PRODUCT_ID}", MODE:="0660", GROUP:="nut"
EOF EOF
@ -588,15 +676,16 @@ EOF
} }
ensure_dependencies ensure_dependencies
migrate_legacy_hecate_install
echo "[install] building hecate" echo "[install] building ananke"
cd "${REPO_DIR}" cd "${REPO_DIR}"
mkdir -p dist mkdir -p dist
go build -o dist/hecate ./cmd/hecate go build -o dist/ananke ./cmd/ananke
echo "[install] installing binary" echo "[install] installing binary"
install -d -m 0755 "${BIN_DIR}" install -d -m 0755 "${BIN_DIR}"
install -m 0755 dist/hecate "${BIN_DIR}/hecate" install -m 0755 dist/ananke "${BIN_DIR}/ananke"
echo "[install] installing config + state dirs" echo "[install] installing config + state dirs"
install -d -m 0750 "${CONF_DIR}" install -d -m 0750 "${CONF_DIR}"
@ -606,64 +695,67 @@ install -d -m 0755 "${LIB_DIR}"
if [[ -n "${FORCE_CONFIG_TEMPLATE}" ]]; then if [[ -n "${FORCE_CONFIG_TEMPLATE}" ]]; then
case "${FORCE_CONFIG_TEMPLATE}" in case "${FORCE_CONFIG_TEMPLATE}" in
coordinator) coordinator)
install -m 0640 configs/hecate.titan-db.yaml "${CONF_DIR}/hecate.yaml" install -m 0640 configs/ananke.titan-db.yaml "${CONF_DIR}/ananke.yaml"
echo "[install] forced config template: coordinator" echo "[install] forced config template: coordinator"
;; ;;
peer) peer)
install -m 0640 configs/hecate.tethys.yaml "${CONF_DIR}/hecate.yaml" install -m 0640 configs/ananke.tethys.yaml "${CONF_DIR}/ananke.yaml"
echo "[install] forced config template: peer" echo "[install] forced config template: peer"
;; ;;
example) example)
install -m 0640 configs/hecate.example.yaml "${CONF_DIR}/hecate.yaml" install -m 0640 configs/ananke.example.yaml "${CONF_DIR}/ananke.yaml"
echo "[install] forced config template: example" echo "[install] forced config template: example"
;; ;;
*) *)
echo "[install] unknown HECATE_FORCE_CONFIG_TEMPLATE value: ${FORCE_CONFIG_TEMPLATE}" >&2 echo "[install] unknown ANANKE_FORCE_CONFIG_TEMPLATE value: ${FORCE_CONFIG_TEMPLATE}" >&2
exit 1 exit 1
;; ;;
esac esac
elif [[ ! -f "${CONF_DIR}/hecate.yaml" ]]; then elif [[ ! -f "${CONF_DIR}/ananke.yaml" ]]; then
install -m 0640 configs/hecate.example.yaml "${CONF_DIR}/hecate.yaml" install -m 0640 configs/ananke.example.yaml "${CONF_DIR}/ananke.yaml"
echo "[install] wrote default config to ${CONF_DIR}/hecate.yaml" echo "[install] wrote default config to ${CONF_DIR}/ananke.yaml"
else else
echo "[install] keeping existing config at ${CONF_DIR}/hecate.yaml" echo "[install] keeping existing config at ${CONF_DIR}/ananke.yaml"
fi fi
migrate_hecate_config migrate_ananke_config
ensure_hecate_ssh_identity ensure_ananke_ssh_identity
ensure_hecate_kubeconfig ensure_ananke_kubeconfig
echo "[install] installing systemd units" echo "[install] installing systemd units"
install -m 0644 deploy/systemd/hecate.service "${SYSTEMD_DIR}/hecate.service" install -m 0644 deploy/systemd/ananke.service "${SYSTEMD_DIR}/ananke.service"
install -m 0644 deploy/systemd/hecate-bootstrap.service "${SYSTEMD_DIR}/hecate-bootstrap.service" install -m 0644 deploy/systemd/ananke-bootstrap.service "${SYSTEMD_DIR}/ananke-bootstrap.service"
install -m 0644 deploy/systemd/hecate-update.service "${SYSTEMD_DIR}/hecate-update.service" install -m 0644 deploy/systemd/ananke-update.service "${SYSTEMD_DIR}/ananke-update.service"
install -m 0644 deploy/systemd/hecate-update.timer "${SYSTEMD_DIR}/hecate-update.timer" install -m 0644 deploy/systemd/ananke-update.timer "${SYSTEMD_DIR}/ananke-update.timer"
install -m 0755 scripts/hecate-self-update.sh "${LIB_DIR}/hecate-self-update.sh" install -m 0755 scripts/ananke-self-update.sh "${LIB_DIR}/ananke-self-update.sh"
resolve_nut_ups_name resolve_nut_ups_name
configure_nut configure_nut
systemctl daemon-reload systemctl daemon-reload
systemctl enable hecate.service hecate-update.timer systemctl enable ananke.service ananke-update.timer
if [[ "${ENABLE_BOOTSTRAP}" == "1" ]]; then if [[ "${ENABLE_BOOTSTRAP}" == "1" ]]; then
systemctl enable hecate-bootstrap.service systemctl enable ananke-bootstrap.service
elif [[ "${ENABLE_BOOTSTRAP}" == "0" ]]; then elif [[ "${ENABLE_BOOTSTRAP}" == "0" ]]; then
systemctl disable hecate-bootstrap.service >/dev/null 2>&1 || true systemctl disable ananke-bootstrap.service >/dev/null 2>&1 || true
else else
role="$(read_hecate_role)" role="$(read_ananke_role)"
systemctl enable hecate-bootstrap.service systemctl enable ananke-bootstrap.service
echo "[install] auto-enabled hecate-bootstrap.service for role=${role}" echo "[install] auto-enabled ananke-bootstrap.service for role=${role}"
fi fi
if [[ "${START_NOW}" -eq 1 ]]; then if [[ "${START_NOW}" -eq 1 ]]; then
systemctl restart hecate.service systemctl restart ananke.service
systemctl restart hecate-update.timer systemctl restart ananke-update.timer
echo "[install] hecate.service restarted" echo "[install] ananke.service restarted"
fi fi
retire_legacy_hecate_install
systemctl daemon-reload
echo "[install] done" echo "[install] done"
echo "Next steps:" echo "Next steps:"
echo " 1. Edit /etc/hecate/hecate.yaml" echo " 1. Edit /etc/ananke/ananke.yaml"
echo " 2. Run: hecate status --config /etc/hecate/hecate.yaml" echo " 2. Run: ananke status --config /etc/ananke/ananke.yaml"
echo " 3. Test dry run: hecate startup --config /etc/hecate/hecate.yaml" echo " 3. Test dry run: ananke startup --config /etc/ananke/ananke.yaml"
echo " 4. Trigger bootstrap now (db host): systemctl start hecate-bootstrap.service" echo " 4. Trigger bootstrap now (db host): systemctl start ananke-bootstrap.service"
echo " 5. Trigger self-update now: systemctl start hecate-update.service" echo " 5. Trigger self-update now: systemctl start ananke-update.service"