life-tooling/scripts/setup-hosts.sh
2026-03-20 09:32:40 -07:00

132 lines
4.2 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
# Life Manager — /etc/hosts setup
# Ensures all 3 hosts can resolve each other by name for Node's getaddrinfo.
# Run on each host: sudo ./scripts/setup-hosts.sh
#
# Why: curl resolves .local via mDNS/Avahi, but Node.js uses getaddrinfo which
# doesn't query mDNS. Without /etc/hosts entries, Node fetch() fails with ENOTFOUND.
MARKER_START="# >>> Life Manager — managed by scripts/setup-hosts.sh"
MARKER_END="# <<< Life Manager"
HOSTS_FILE="/etc/hosts"
# ── Host registry ──────────────────────────────────────────────────────────────
# Format: hostname LAN-IP aliases...
declare -A HOST_IPS=(
[apricot]="10.0.0.116"
[black]="10.0.0.11"
[plum]="10.0.0.123"
)
# Aliases: each host gets lm.<host>.local + <host>.local
# The current host gets 127.0.0.1 for its own aliases (loopback)
generate_entries() {
local current_host
current_host="$(hostname -s)"
local entries=""
for host in apricot black plum; do
local ip="${HOST_IPS[$host]}"
local aliases="lm.${host}.local ${host}.local"
# Use loopback for self
if [[ "$host" == "$current_host" ]]; then
ip="127.0.0.1"
fi
entries+="${ip} ${aliases}\n"
done
echo -e "$entries"
}
# ── Install ────────────────────────────────────────────────────────────────────
install_hosts() {
if [[ $EUID -ne 0 ]]; then
echo "Error: must run as root (sudo ./scripts/setup-hosts.sh)"
exit 1
fi
local new_block
new_block="$(generate_entries)"
# Remove existing managed block if present
if grep -q "$MARKER_START" "$HOSTS_FILE" 2>/dev/null; then
# Delete between markers (inclusive)
sed -i "/$MARKER_START/,/$MARKER_END/d" "$HOSTS_FILE"
echo "Removed previous Life Manager hosts block."
fi
# Append new block
{
echo ""
echo "$MARKER_START"
echo "# Auto-generated. Do not edit manually."
echo "#"
echo -e "$new_block"
echo "$MARKER_END"
} >> "$HOSTS_FILE"
echo "Installed Life Manager hosts on $(hostname -s):"
echo ""
echo -e "$new_block"
# Verify
echo "Verification:"
for host in apricot black plum; do
local resolved
resolved="$(getent hosts "lm.${host}.local" 2>/dev/null | awk '{print $1}')" || resolved="FAILED"
echo " lm.${host}.local → ${resolved}"
done
}
# ── Remote install ─────────────────────────────────────────────────────────────
install_remote() {
local target="$1"
local script_path
script_path="$(realpath "$0")"
local run_user="${SUDO_USER:-$USER}"
echo "=== Installing on ${target} ==="
# Copy script as the real user (SSH keys belong to them, not root)
su "$run_user" -c "scp -q '$script_path' '${target}:/tmp/setup-hosts.sh'"
su "$run_user" -c "ssh -t '${target}' 'sudo bash /tmp/setup-hosts.sh && rm /tmp/setup-hosts.sh'"
echo ""
}
# ── Main ───────────────────────────────────────────────────────────────────────
case "${1:-}" in
--all)
# Run locally first
echo "=== Installing on $(hostname -s) (local) ==="
install_hosts
echo ""
# Then remote hosts
current="$(hostname -s)"
for host in apricot black plum; do
[[ "$host" == "$current" ]] && continue
install_remote "$host" || echo "WARNING: Failed to install on $host"
done
echo "Done — all hosts configured."
;;
--remote)
shift
for target in "$@"; do
install_remote "$target"
done
;;
""|--local)
install_hosts
;;
*)
echo "Usage: sudo $0 [--all | --local | --remote <host> ...]"
echo ""
echo " --local Install on this host only (default)"
echo " --all Install on all 3 hosts (apricot, black, plum)"
echo " --remote Install on specific remote hosts"
exit 1
;;
esac