diff --git a/scripts/install.sh b/scripts/install.sh index 1cde139..6f0b631 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -8,20 +8,20 @@ fi REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" BIN_DIR="/usr/local/bin" -CONF_DIR="/etc/hecate" -STATE_DIR="/var/lib/hecate" +CONF_DIR="/etc/ananke" +STATE_DIR="/var/lib/ananke" SYSTEMD_DIR="/etc/systemd/system" -LIB_DIR="/usr/local/lib/hecate" +LIB_DIR="/usr/local/lib/ananke" 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}" -FORCE_CONFIG_TEMPLATE="${HECATE_FORCE_CONFIG_TEMPLATE:-}" +ENABLE_BOOTSTRAP="${ANANKE_ENABLE_BOOTSTRAP:-auto}" +MANAGE_NUT="${ANANKE_MANAGE_NUT:-1}" +NUT_UPS_NAME="${ANANKE_NUT_UPS_NAME:-}" +NUT_VENDOR_ID="${ANANKE_NUT_VENDOR_ID:-0764}" +NUT_PRODUCT_ID="${ANANKE_NUT_PRODUCT_ID:-0601}" +NUT_MONITOR_USER="${ANANKE_NUT_MONITOR_USER:-monuser}" +NUT_MONITOR_PASSWORD="${ANANKE_NUT_MONITOR_PASSWORD:-anankeupsmon}" +FORCE_CONFIG_TEMPLATE="${ANANKE_FORCE_CONFIG_TEMPLATE:-}" while [[ $# -gt 0 ]]; do case "$1" in @@ -45,9 +45,9 @@ resolve_nut_ups_name() { return 0 fi - if [[ -f "${CONF_DIR}/hecate.yaml" ]]; then + if [[ -f "${CONF_DIR}/ananke.yaml" ]]; then 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 NUT_UPS_NAME="${target%@localhost}" 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}" } -read_hecate_role() { - if [[ ! -f "${CONF_DIR}/hecate.yaml" ]]; then +read_ananke_role() { + if [[ ! -f "${CONF_DIR}/ananke.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)" + role="$(awk '/^[[:space:]]*role:[[:space:]]*/ {print $2; exit}' "${CONF_DIR}/ananke.yaml" 2>/dev/null || true)" if [[ -z "${role}" ]]; then role="coordinator" fi @@ -74,7 +74,7 @@ read_hecate_role() { migration_yaml_lookup() { 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() { @@ -82,19 +82,19 @@ first_control_plane_name() { /^control_planes:[[:space:]]*$/ {in_list=1; next} in_list && /^[[:space:]]*-[[:space:]]*/ {gsub(/^[[:space:]]*-[[:space:]]*/, "", $0); print $0; exit} 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() { 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 kubeconfig_path="$(migration_yaml_lookup "kubeconfig")" if [[ -z "${kubeconfig_path}" ]]; then - kubeconfig_path="/etc/hecate/kubeconfig" + kubeconfig_path="/etc/ananke/kubeconfig" fi 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" } -ensure_hecate_ssh_identity() { +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="hecate-$(hostname)-forward" + key_comment="ananke-$(hostname)-forward" key_user="root" if [[ "${key_path}" == /home/*/* ]]; then @@ -198,51 +198,51 @@ ensure_hecate_ssh_identity() { chmod 0644 "${key_path}.pub" || true } -migrate_hecate_config() { - if [[ ! -f "${CONF_DIR}/hecate.yaml" ]]; then +migrate_ananke_config() { + if [[ ! -f "${CONF_DIR}/ananke.yaml" ]]; then return 0 fi local changed=0 local role_hint - role_hint="$(read_hecate_role)" - if grep -Eq 'default_budget_seconds:[[:space:]]*300' "${CONF_DIR}/hecate.yaml"; then - sed -Ei 's/(default_budget_seconds:[[:space:]]*)300/\11380/' "${CONF_DIR}/hecate.yaml" - echo "[install] migrated default_budget_seconds 300 -> 1380 in ${CONF_DIR}/hecate.yaml" + role_hint="$(read_ananke_role)" + if grep -Eq 'default_budget_seconds:[[:space:]]*300' "${CONF_DIR}/ananke.yaml"; then + sed -Ei 's/(default_budget_seconds:[[:space:]]*)300/\11380/' "${CONF_DIR}/ananke.yaml" + echo "[install] migrated default_budget_seconds 300 -> 1380 in ${CONF_DIR}/ananke.yaml" changed=1 fi - if grep -Eq 'runtime_safety_factor:[[:space:]]*1\.10' "${CONF_DIR}/hecate.yaml"; then - sed -Ei 's/(runtime_safety_factor:[[:space:]]*)1\.10/\11.25/' "${CONF_DIR}/hecate.yaml" - echo "[install] migrated runtime_safety_factor 1.10 -> 1.25 in ${CONF_DIR}/hecate.yaml" + 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}/ananke.yaml" + echo "[install] migrated runtime_safety_factor 1.10 -> 1.25 in ${CONF_DIR}/ananke.yaml" changed=1 fi - if grep -Eq '^ssh_node_users:[[:space:]]*$' "${CONF_DIR}/hecate.yaml" \ - && grep -Eq '^ titan-24:[[:space:]]*tethys[[:space:]]*$' "${CONF_DIR}/hecate.yaml"; then - sed -Ei 's/^ titan-24:[[:space:]]*tethys[[:space:]]*$/ titan-24: atlas/' "${CONF_DIR}/hecate.yaml" + if grep -Eq '^ssh_node_users:[[:space:]]*$' "${CONF_DIR}/ananke.yaml" \ + && 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}/ananke.yaml" echo "[install] migrated ssh_node_users titan-24 override to atlas" changed=1 fi - if grep -Eq '^ command_timeout_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/hecate.yaml" \ - && ! grep -Eq '^ startup_guard_max_age_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/hecate.yaml"; then - sed -Ei '/^ command_timeout_seconds:[[:space:]]*[0-9]+/a\ startup_guard_max_age_seconds: 900' "${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}/ananke.yaml"; then + 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" changed=1 fi - if ! grep -Eq '^ peer_hosts:' "${CONF_DIR}/hecate.yaml"; then - if [[ "${role_hint}" == "peer" ]] && grep -Eq '^ forward_shutdown_host:[[:space:]]*[A-Za-z0-9._-]+' "${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}/ananke.yaml"; then 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 - 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})" changed=1 fi - elif [[ "${role_hint}" == "coordinator" ]] && grep -Eq '^ titan-24:[[:space:]]*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' "${CONF_DIR}/hecate.yaml"; then - sed -Ei '/^ forward_shutdown_config:[[:space:]]*.*$/a\ peer_hosts:\n - titan-24' "${CONF_DIR}/hecate.yaml" + 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}/ananke.yaml" echo "[install] added coordination.peer_hosts default (titan-24) for coordinator role" changed=1 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" changed=1 fi @@ -252,58 +252,58 @@ migrate_hecate_config() { if [[ -z "${default_restore_cp}" ]]; then default_restore_cp="titan-0a" fi - if grep -Eq '^ api_poll_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/hecate.yaml" \ - && ! grep -Eq '^ auto_etcd_restore_on_api_failure:[[:space:]]*(true|false)' "${CONF_DIR}/hecate.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" + 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}/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}/ananke.yaml" echo "[install] added startup.auto_etcd_restore_on_api_failure + startup.etcd_restore_control_plane defaults" changed=1 fi - if grep -Eq '^ api_poll_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/hecate.yaml" \ - && ! grep -Eq '^ require_time_sync:[[:space:]]*(true|false)' "${CONF_DIR}/hecate.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" + if grep -Eq '^ api_poll_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/ananke.yaml" \ + && ! 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}/ananke.yaml" echo "[install] added startup time sync + access reconciliation defaults" changed=1 fi - if grep -Eq '^ time_sync_poll_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/hecate.yaml" \ - && ! grep -Eq '^ time_sync_mode:[[:space:]]*(strict|quorum)' "${CONF_DIR}/hecate.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" + if grep -Eq '^ time_sync_poll_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/ananke.yaml" \ + && ! 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}/ananke.yaml" echo "[install] added startup time sync quorum defaults" changed=1 fi - if grep -Eq '^ etcd_restore_control_plane:[[:space:]]*[A-Za-z0-9._-]+' "${CONF_DIR}/hecate.yaml" \ - && ! grep -Eq '^ require_storage_ready:[[:space:]]*(true|false)' "${CONF_DIR}/hecate.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" + 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}/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}/ananke.yaml" echo "[install] added startup storage readiness defaults" changed=1 fi - if grep -Eq '^ storage_critical_pvcs:[[:space:]]*$' "${CONF_DIR}/hecate.yaml" \ - && ! grep -Eq '^ require_post_start_probes:[[:space:]]*(true|false)' "${CONF_DIR}/hecate.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" + if grep -Eq '^ storage_critical_pvcs:[[:space:]]*$' "${CONF_DIR}/ananke.yaml" \ + && ! 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/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" changed=1 fi - if grep -Eq '^ - https://sso.bstein.dev/realms/atlas/.well-known/openid-configuration$' "${CONF_DIR}/hecate.yaml"; then - sed -Ei '/^ - https:\/\/sso\.bstein\.dev\/realms\/atlas\/\.well-known\/openid-configuration$/d' "${CONF_DIR}/hecate.yaml" + 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}/ananke.yaml" echo "[install] removed sso OIDC probe from startup.post_start_probes (returns 404 in current deployment)" changed=1 fi - if ! grep -Eq '^ vault_unseal_key_file:[[:space:]]*/var/lib/hecate/vault-unseal.key' "${CONF_DIR}/hecate.yaml"; then - if grep -Eq '^startup:[[:space:]]*$' "${CONF_DIR}/hecate.yaml" && grep -Eq '^ post_start_probes:[[:space:]]*$' "${CONF_DIR}/hecate.yaml"; then - sed -Ei '/^ - https:\/\/metrics\.bstein\.dev\/login$/a\ vault_unseal_key_file: /var/lib/hecate/vault-unseal.key' "${CONF_DIR}/hecate.yaml" + 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}/ananke.yaml" && grep -Eq '^ post_start_probes:[[:space:]]*$' "${CONF_DIR}/ananke.yaml"; then + 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" changed=1 fi fi - if ! grep -Eq '^ vault_unseal_breakglass_timeout_seconds:[[:space:]]*[0-9]+' "${CONF_DIR}/hecate.yaml"; then - if grep -Eq '^ vault_unseal_key_file:[[:space:]]*/var/lib/hecate/vault-unseal.key' "${CONF_DIR}/hecate.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" + 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/ananke/vault-unseal.key' "${CONF_DIR}/ananke.yaml"; then + 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" changed=1 fi fi local role - role="$(read_hecate_role)" + role="$(read_ananke_role)" local inventory_block local managed_block local workers_block @@ -428,43 +428,43 @@ migrate_hecate_config() { fi if [[ -n "${inventory_block}" ]]; then - if grep -Eq '^ssh_node_hosts:[[:space:]]*\{\}[[:space:]]*$' "${CONF_DIR}/hecate.yaml"; then - perl -0pi -e 's#ssh_node_hosts:\s*\{\}\n#'"${inventory_block}"'\n#s' "${CONF_DIR}/hecate.yaml" + 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}/ananke.yaml" echo "[install] hydrated ssh_node_hosts inventory for role=${role}" changed=1 fi fi - if grep -Eq '^workers:[[:space:]]*\[\][[:space:]]*$' "${CONF_DIR}/hecate.yaml"; then - perl -0pi -e 's#workers:\s*\[\]\n#'"${workers_block}"'\n#s' "${CONF_DIR}/hecate.yaml" + if grep -Eq '^workers:[[:space:]]*\[\][[:space:]]*$' "${CONF_DIR}/ananke.yaml"; then + perl -0pi -e 's#workers:\s*\[\]\n#'"${workers_block}"'\n#s' "${CONF_DIR}/ananke.yaml" echo "[install] hydrated workers inventory for startup/shutdown orchestration" changed=1 fi if [[ -n "${managed_block}" ]]; then - if grep -Eq '^ssh_managed_nodes:[[:space:]]*\[\][[:space:]]*$' "${CONF_DIR}/hecate.yaml"; then - perl -0pi -e 's#ssh_managed_nodes:\s*\[\]\n#'"${managed_block}"'\n#s' "${CONF_DIR}/hecate.yaml" + 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}/ananke.yaml" echo "[install] hydrated ssh_managed_nodes inventory for role=${role}" changed=1 fi - if ! grep -Eq '^ - titan-04$' "${CONF_DIR}/hecate.yaml" || ! grep -Eq '^ - titan-21$' "${CONF_DIR}/hecate.yaml"; then - perl -0pi -e 's#ssh_managed_nodes:\n(?: - [^\n]*\n)*#'"${managed_block}"'\n#s' "${CONF_DIR}/hecate.yaml" + 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}/ananke.yaml" echo "[install] refreshed ssh_managed_nodes coverage for role=${role}" changed=1 fi fi if [[ "${role}" == "peer" ]]; then - if grep -Eq '^ssh_managed_nodes:[[:space:]]*$' "${CONF_DIR}/hecate.yaml" \ - && grep -Eq '^ - titan-db$' "${CONF_DIR}/hecate.yaml" \ - && grep -Eq '^ - titan-24$' "${CONF_DIR}/hecate.yaml" \ - && ! grep -Eq '^ - titan-0a$' "${CONF_DIR}/hecate.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" + if grep -Eq '^ssh_managed_nodes:[[:space:]]*$' "${CONF_DIR}/ananke.yaml" \ + && grep -Eq '^ - titan-db$' "${CONF_DIR}/ananke.yaml" \ + && grep -Eq '^ - titan-24$' "${CONF_DIR}/ananke.yaml" \ + && ! 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}/ananke.yaml" echo "[install] expanded peer ssh_managed_nodes for bootstrap fallback coverage" changed=1 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 - 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" + 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}/ananke.yaml" echo "[install] refreshed peer local_bootstrap_paths for full fallback bootstrap parity" changed=1 fi @@ -472,7 +472,7 @@ migrate_hecate_config() { fi if [[ "${changed}" -eq 1 ]]; then - chmod 0640 "${CONF_DIR}/hecate.yaml" || true + chmod 0640 "${CONF_DIR}/ananke.yaml" || true fi } @@ -527,9 +527,97 @@ ensure_dependencies() { 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() { 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 fi @@ -573,8 +661,8 @@ DEADTIME 15 POWERDOWNFLAG /etc/killpower EOF - cat > /etc/udev/rules.d/99-hecate-ups.rules < /etc/udev/rules.d/99-ananke-ups.rules <&2 + echo "[install] unknown ANANKE_FORCE_CONFIG_TEMPLATE value: ${FORCE_CONFIG_TEMPLATE}" >&2 exit 1 ;; esac -elif [[ ! -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" +elif [[ ! -f "${CONF_DIR}/ananke.yaml" ]]; then + install -m 0640 configs/ananke.example.yaml "${CONF_DIR}/ananke.yaml" + echo "[install] wrote default config to ${CONF_DIR}/ananke.yaml" else - echo "[install] keeping existing config at ${CONF_DIR}/hecate.yaml" + echo "[install] keeping existing config at ${CONF_DIR}/ananke.yaml" fi -migrate_hecate_config -ensure_hecate_ssh_identity -ensure_hecate_kubeconfig +migrate_ananke_config +ensure_ananke_ssh_identity +ensure_ananke_kubeconfig 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" +install -m 0644 deploy/systemd/ananke.service "${SYSTEMD_DIR}/ananke.service" +install -m 0644 deploy/systemd/ananke-bootstrap.service "${SYSTEMD_DIR}/ananke-bootstrap.service" +install -m 0644 deploy/systemd/ananke-update.service "${SYSTEMD_DIR}/ananke-update.service" +install -m 0644 deploy/systemd/ananke-update.timer "${SYSTEMD_DIR}/ananke-update.timer" +install -m 0755 scripts/ananke-self-update.sh "${LIB_DIR}/ananke-self-update.sh" resolve_nut_ups_name configure_nut systemctl daemon-reload -systemctl enable hecate.service hecate-update.timer +systemctl enable ananke.service ananke-update.timer if [[ "${ENABLE_BOOTSTRAP}" == "1" ]]; then - systemctl enable hecate-bootstrap.service + systemctl enable ananke-bootstrap.service 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 - role="$(read_hecate_role)" - systemctl enable hecate-bootstrap.service - echo "[install] auto-enabled hecate-bootstrap.service for role=${role}" + role="$(read_ananke_role)" + systemctl enable ananke-bootstrap.service + echo "[install] auto-enabled ananke-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" + systemctl restart ananke.service + systemctl restart ananke-update.timer + echo "[install] ananke.service restarted" fi +retire_legacy_hecate_install +systemctl daemon-reload + 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" +echo " 1. Edit /etc/ananke/ananke.yaml" +echo " 2. Run: ananke status --config /etc/ananke/ananke.yaml" +echo " 3. Test dry run: ananke startup --config /etc/ananke/ananke.yaml" +echo " 4. Trigger bootstrap now (db host): systemctl start ananke-bootstrap.service" +echo " 5. Trigger self-update now: systemctl start ananke-update.service"