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>
168 lines
4.7 KiB
Bash
Executable file
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
|