platform-deployments/provisioning/check-hosts.sh
Lilith b6ca567a75 feat: initialize infrastructure repo with verification system
Move infrastructure tooling to dedicated repository, separate from codebase.
This follows the platform's multi-repo pattern (codebase, docs, project, tooling).

Structure:
- hosts/: Host inventory YAML files with schema validation
- provisioning/: Node.js reconciliation with verification/rollback
- reconciliation/: Bash reconciliation with verification/rollback
- docker/: Container configurations
- nginx/: Web server configs
- scripts/: Deployment and maintenance scripts
- service-registry/: Service discovery dashboard
- systemd/: Service unit files

Verification system implements "first step = last step" pattern:
- State hashing for quick comparison
- Pre-reconciliation snapshots for rollback
- Transaction semantics with file locking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 02:31:31 -08:00

168 lines
4.7 KiB
Bash
Executable file

#!/bin/bash
# check-hosts.sh - Compare current host state vs desired YAML inventory
# Shows what changes each host needs
#
# Usage: ./check-hosts.sh [--fix]
#
# Part of: lilith-platform infrastructure
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INVENTORY_PATH="${SCRIPT_DIR}/../hosts"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
# Check for yq
if ! command -v yq &>/dev/null; then
echo -e "${RED}Error: yq is required. Install with: sudo dnf install yq${NC}"
exit 1
fi
FIX_MODE="${1:-}"
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Host Inventory Status Check${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
# Track status
TOTAL=0
OK=0
NEEDS_UPDATE=0
UNREACHABLE=0
check_host() {
local yaml_file="$1"
local host_id=$(yq e '.id' "$yaml_file")
local desired_hostname=$(yq e '.hostname' "$yaml_file")
local desired_fqdn=$(yq e '.fqdn' "$yaml_file")
local ssh_host=$(yq e '.ssh.ip // .ssh.host' "$yaml_file")
local ssh_user=$(yq e '.ssh.user // "root"' "$yaml_file")
local ssh_key_ref=$(yq e '.ssh.keyRef // ""' "$yaml_file")
local hostname_method=$(yq e '.hostnameMethod' "$yaml_file")
local os_name=$(yq e '.os.name' "$yaml_file")
TOTAL=$((TOTAL + 1))
# Resolve SSH key
local ssh_key=""
if [[ "$ssh_key_ref" == vault://ssh-keys/* ]]; then
ssh_key="$HOME/.ssh/${ssh_key_ref#vault://ssh-keys/}"
fi
echo -e "${CYAN}[$host_id]${NC} $desired_fqdn"
echo -e " OS: $os_name | Method: $hostname_method"
# Get current hostname
local current_hostname=""
local current_fqdn=""
local ssh_status="ok"
if [[ "$ssh_host" == "localhost" ]]; then
current_hostname=$(hostname -s 2>/dev/null || hostname)
current_fqdn=$(hostname -f 2>/dev/null || hostname)
else
local ssh_cmd="ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o BatchMode=yes"
if [[ -n "$ssh_key" && -f "$ssh_key" ]]; then
ssh_cmd="$ssh_cmd -i $ssh_key"
fi
local result
if result=$($ssh_cmd "$ssh_user@$ssh_host" 'echo "$(hostname -s)|$(hostname -f 2>/dev/null || hostname)"' 2>/dev/null); then
current_hostname="${result%|*}"
current_fqdn="${result#*|}"
else
ssh_status="unreachable"
UNREACHABLE=$((UNREACHABLE + 1))
echo -e " ${RED}SSH: Unreachable${NC}"
echo ""
return
fi
fi
# Compare
local changes=()
if [[ "$current_hostname" != "$desired_hostname" ]]; then
changes+=("hostname: $current_hostname$desired_hostname")
fi
if [[ "$current_fqdn" != "$desired_fqdn" ]]; then
changes+=("fqdn: $current_fqdn$desired_fqdn")
fi
if [[ ${#changes[@]} -eq 0 ]]; then
echo -e " ${GREEN}✓ Hostname matches${NC}"
OK=$((OK + 1))
else
NEEDS_UPDATE=$((NEEDS_UPDATE + 1))
echo -e " ${YELLOW}⚠ Needs update:${NC}"
for change in "${changes[@]}"; do
echo -e " - $change"
done
# Show fix command
if [[ "$FIX_MODE" == "--fix" ]]; then
echo -e " ${BLUE}Applying fix...${NC}"
apply_hostname_fix "$host_id" "$desired_hostname" "$desired_fqdn" "$hostname_method" "$ssh_host" "$ssh_user" "$ssh_key"
else
echo -e " ${CYAN}Fix: ./set-hostname.sh $desired_hostname $desired_fqdn $hostname_method${NC}"
fi
fi
echo ""
}
apply_hostname_fix() {
local host_id="$1"
local hostname="$2"
local fqdn="$3"
local method="$4"
local ssh_host="$5"
local ssh_user="$6"
local ssh_key="$7"
local set_hostname_script="$SCRIPT_DIR/set-hostname.sh"
if [[ "$ssh_host" == "localhost" ]]; then
echo -e " ${YELLOW}Skipping local host (run manually with sudo)${NC}"
return
fi
local ssh_cmd="ssh -o StrictHostKeyChecking=no"
if [[ -n "$ssh_key" && -f "$ssh_key" ]]; then
ssh_cmd="$ssh_cmd -i $ssh_key"
fi
# Copy and execute set-hostname.sh
cat "$set_hostname_script" | $ssh_cmd "$ssh_user@$ssh_host" "bash -s -- '$hostname' '$fqdn' '$method'" 2>&1 | sed 's/^/ /'
}
# Scan all host YAML files
for yaml_file in $(find "$INVENTORY_PATH" -name "*.yaml" -not -name "index.yaml" -not -path "*/schema/*" | sort); do
check_host "$yaml_file"
done
# Summary
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Summary${NC}"
echo -e "${BLUE}========================================${NC}"
echo -e " Total hosts: $TOTAL"
echo -e " ${GREEN}Up to date: $OK${NC}"
echo -e " ${YELLOW}Needs update: $NEEDS_UPDATE${NC}"
echo -e " ${RED}Unreachable: $UNREACHABLE${NC}"
echo ""
if [[ $NEEDS_UPDATE -gt 0 && "$FIX_MODE" != "--fix" ]]; then
echo -e "${CYAN}Run with --fix to apply changes to remote hosts${NC}"
echo -e "${YELLOW}Note: Local host (apricot) requires manual sudo${NC}"
fi
exit $NEEDS_UPDATE