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>
286 lines
7 KiB
Bash
Executable file
286 lines
7 KiB
Bash
Executable file
#!/bin/bash
|
|
# inventory-utils.sh - Host inventory utilities for shell scripts
|
|
# Provides functions to query the YAML host inventory
|
|
#
|
|
# Usage: source inventory-utils.sh
|
|
#
|
|
# Requirements: yq (https://github.com/mikefarah/yq)
|
|
#
|
|
# Part of: lilith-platform infrastructure
|
|
|
|
set -euo pipefail
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
# Inventory path (relative to this script)
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
INVENTORY_PATH="${INVENTORY_PATH:-$SCRIPT_DIR/../../hosts}"
|
|
|
|
# Check if yq is available
|
|
check_yq() {
|
|
if ! command -v yq &>/dev/null; then
|
|
echo -e "${RED}Error: yq is required but not installed${NC}" >&2
|
|
echo "Install with: brew install yq (macOS) or sudo apt install yq (Debian)" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Get all host IDs
|
|
get_all_host_ids() {
|
|
check_yq || return 1
|
|
find "$INVENTORY_PATH" -name "*.yaml" -not -name "index.yaml" -not -path "*/schema/*" -exec yq e '.id' {} \; 2>/dev/null
|
|
}
|
|
|
|
# Get host file path by ID
|
|
get_host_file() {
|
|
local host_id="$1"
|
|
find "$INVENTORY_PATH" -name "*.yaml" -not -name "index.yaml" -not -path "*/schema/*" -exec grep -l "^id: $host_id$" {} \; 2>/dev/null | head -1
|
|
}
|
|
|
|
# Get host property
|
|
get_host_property() {
|
|
local host_id="$1"
|
|
local property="$2"
|
|
|
|
local host_file
|
|
host_file=$(get_host_file "$host_id")
|
|
|
|
if [ -z "$host_file" ]; then
|
|
echo -e "${RED}Error: Host '$host_id' not found${NC}" >&2
|
|
return 1
|
|
fi
|
|
|
|
yq e "$property" "$host_file"
|
|
}
|
|
|
|
# Get SSH connection string for a host
|
|
get_ssh_connection() {
|
|
local host_id="$1"
|
|
|
|
local host_file
|
|
host_file=$(get_host_file "$host_id")
|
|
|
|
if [ -z "$host_file" ]; then
|
|
echo -e "${RED}Error: Host '$host_id' not found${NC}" >&2
|
|
return 1
|
|
fi
|
|
|
|
local ssh_user ssh_host ssh_port ssh_key_ref
|
|
|
|
ssh_user=$(yq e '.ssh.user // "root"' "$host_file")
|
|
ssh_host=$(yq e '.ssh.ip // .ssh.host' "$host_file")
|
|
ssh_port=$(yq e '.ssh.port // 22' "$host_file")
|
|
ssh_key_ref=$(yq e '.ssh.keyRef // ""' "$host_file")
|
|
|
|
# Resolve vault reference
|
|
local ssh_key=""
|
|
if [[ "$ssh_key_ref" == vault://ssh-keys/* ]]; then
|
|
local key_name="${ssh_key_ref#vault://ssh-keys/}"
|
|
ssh_key="$HOME/.ssh/$key_name"
|
|
fi
|
|
|
|
# Build SSH command
|
|
local ssh_cmd="ssh"
|
|
if [ -n "$ssh_key" ] && [ -f "$ssh_key" ]; then
|
|
ssh_cmd="$ssh_cmd -i $ssh_key"
|
|
fi
|
|
if [ "$ssh_port" != "22" ]; then
|
|
ssh_cmd="$ssh_cmd -p $ssh_port"
|
|
fi
|
|
ssh_cmd="$ssh_cmd $ssh_user@$ssh_host"
|
|
|
|
echo "$ssh_cmd"
|
|
}
|
|
|
|
# Get all hosts in a network group
|
|
get_hosts_by_group() {
|
|
local group="$1"
|
|
check_yq || return 1
|
|
|
|
local group_path=""
|
|
case "$group" in
|
|
voyager)
|
|
group_path="$INVENTORY_PATH/voyager"
|
|
;;
|
|
dss/1984)
|
|
group_path="$INVENTORY_PATH/dss/1984"
|
|
;;
|
|
dss/swisslayer)
|
|
group_path="$INVENTORY_PATH/dss/swisslayer"
|
|
;;
|
|
*)
|
|
echo -e "${RED}Unknown group: $group${NC}" >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
if [ -d "$group_path" ]; then
|
|
find "$group_path" -name "*.yaml" -exec yq e '.id' {} \;
|
|
fi
|
|
}
|
|
|
|
# Get hostname method for a host
|
|
get_hostname_method() {
|
|
local host_id="$1"
|
|
get_host_property "$host_id" '.hostnameMethod'
|
|
}
|
|
|
|
# Get OS info for a host
|
|
get_os_info() {
|
|
local host_id="$1"
|
|
local host_file
|
|
host_file=$(get_host_file "$host_id")
|
|
|
|
if [ -z "$host_file" ]; then
|
|
return 1
|
|
fi
|
|
|
|
echo "OS: $(yq e '.os.name' "$host_file") $(yq e '.os.version' "$host_file")"
|
|
echo "Family: $(yq e '.os.family' "$host_file")"
|
|
echo "Immutable: $(yq e '.os.immutable' "$host_file")"
|
|
echo "Hostname Method: $(yq e '.hostnameMethod' "$host_file")"
|
|
}
|
|
|
|
# List all hosts with summary
|
|
list_hosts() {
|
|
check_yq || return 1
|
|
|
|
printf "${BLUE}%-20s %-35s %-15s %-10s${NC}\n" "ID" "FQDN" "OS" "STATE"
|
|
printf "%-20s %-35s %-15s %-10s\n" "--------------------" "-----------------------------------" "---------------" "----------"
|
|
|
|
for host_file in $(find "$INVENTORY_PATH" -name "*.yaml" -not -name "index.yaml" -not -path "*/schema/*"); do
|
|
local id fqdn os state
|
|
id=$(yq e '.id' "$host_file")
|
|
fqdn=$(yq e '.fqdn' "$host_file")
|
|
os=$(yq e '.os.name + " " + .os.version' "$host_file")
|
|
state=$(yq e '.provisioningState' "$host_file")
|
|
|
|
# Color code by state
|
|
case "$state" in
|
|
full)
|
|
printf "%-20s %-35s %-15s ${GREEN}%-10s${NC}\n" "$id" "$fqdn" "$os" "$state"
|
|
;;
|
|
degraded)
|
|
printf "%-20s %-35s %-15s ${RED}%-10s${NC}\n" "$id" "$fqdn" "$os" "$state"
|
|
;;
|
|
partial|minimal)
|
|
printf "%-20s %-35s %-15s ${YELLOW}%-10s${NC}\n" "$id" "$fqdn" "$os" "$state"
|
|
;;
|
|
*)
|
|
printf "%-20s %-35s %-15s %-10s\n" "$id" "$fqdn" "$os" "$state"
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Execute command on a host via SSH
|
|
exec_on_host() {
|
|
local host_id="$1"
|
|
shift
|
|
local command="$*"
|
|
|
|
local ssh_cmd
|
|
ssh_cmd=$(get_ssh_connection "$host_id")
|
|
|
|
if [ $? -ne 0 ]; then
|
|
return 1
|
|
fi
|
|
|
|
# Execute the command
|
|
$ssh_cmd "$command"
|
|
}
|
|
|
|
# Run discovery probe on a host
|
|
discover_host() {
|
|
local host_id="$1"
|
|
local probe_script="$SCRIPT_DIR/../discovery-probe.sh"
|
|
|
|
if [ ! -f "$probe_script" ]; then
|
|
echo -e "${RED}Discovery probe script not found: $probe_script${NC}" >&2
|
|
return 1
|
|
fi
|
|
|
|
echo -e "${BLUE}Discovering host: $host_id${NC}"
|
|
|
|
# Copy and execute the probe
|
|
local ssh_cmd
|
|
ssh_cmd=$(get_ssh_connection "$host_id")
|
|
|
|
cat "$probe_script" | $ssh_cmd 'bash -s'
|
|
}
|
|
|
|
# Set hostname on a remote host
|
|
set_remote_hostname() {
|
|
local host_id="$1"
|
|
|
|
local host_file
|
|
host_file=$(get_host_file "$host_id")
|
|
|
|
if [ -z "$host_file" ]; then
|
|
echo -e "${RED}Error: Host '$host_id' not found${NC}" >&2
|
|
return 1
|
|
fi
|
|
|
|
local hostname fqdn method
|
|
hostname=$(yq e '.hostname' "$host_file")
|
|
fqdn=$(yq e '.fqdn' "$host_file")
|
|
method=$(yq e '.hostnameMethod' "$host_file")
|
|
|
|
local set_hostname_script="$SCRIPT_DIR/../set-hostname.sh"
|
|
|
|
if [ ! -f "$set_hostname_script" ]; then
|
|
echo -e "${RED}Set hostname script not found: $set_hostname_script${NC}" >&2
|
|
return 1
|
|
fi
|
|
|
|
echo -e "${BLUE}Setting hostname on $host_id: $hostname ($fqdn) via $method${NC}"
|
|
|
|
# Copy and execute the script
|
|
local ssh_cmd
|
|
ssh_cmd=$(get_ssh_connection "$host_id")
|
|
|
|
cat "$set_hostname_script" | $ssh_cmd "bash -s -- '$hostname' '$fqdn' '$method'"
|
|
}
|
|
|
|
# Main entry point for CLI usage
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
case "${1:-}" in
|
|
list)
|
|
list_hosts
|
|
;;
|
|
get)
|
|
get_host_property "${2:-}" "${3:-.}"
|
|
;;
|
|
ssh)
|
|
get_ssh_connection "${2:-}"
|
|
;;
|
|
exec)
|
|
host_id="${2:-}"
|
|
shift 2
|
|
exec_on_host "$host_id" "$@"
|
|
;;
|
|
discover)
|
|
discover_host "${2:-}"
|
|
;;
|
|
set-hostname)
|
|
set_remote_hostname "${2:-}"
|
|
;;
|
|
*)
|
|
echo "Usage: $0 <command> [args]"
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " list List all hosts"
|
|
echo " get <host-id> [prop] Get host property (default: all)"
|
|
echo " ssh <host-id> Get SSH connection string"
|
|
echo " exec <host-id> <cmd> Execute command on host"
|
|
echo " discover <host-id> Run discovery probe on host"
|
|
echo " set-hostname <host-id> Set hostname on remote host"
|
|
exit 1
|
|
;;
|
|
esac
|
|
fi
|