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>
232 lines
6.1 KiB
Bash
Executable file
232 lines
6.1 KiB
Bash
Executable file
#!/bin/bash
|
|
# set-hostname.sh - OS-aware hostname configuration
|
|
# Sets hostname using the appropriate method for the target OS
|
|
#
|
|
# Usage: ./set-hostname.sh <short-hostname> <fqdn> [hostname-method]
|
|
#
|
|
# Arguments:
|
|
# short-hostname - Short hostname (e.g., "apricot", "0")
|
|
# fqdn - Fully qualified domain name (e.g., "apricot.voyager.nasty.sh")
|
|
# hostname-method - Optional: hostnamectl-only, etc-hostname, cloud-init
|
|
# If not provided, will auto-detect
|
|
#
|
|
# Part of: lilith-platform infrastructure
|
|
|
|
set -euo pipefail
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
log_info() {
|
|
echo -e "${GREEN}[INFO]${NC} $*"
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $*"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $*" >&2
|
|
}
|
|
|
|
# Detect hostname method if not provided
|
|
detect_hostname_method() {
|
|
# Check for immutable filesystem (Fedora Atomic variants)
|
|
if [ -f /run/ostree-booted ]; then
|
|
echo "hostnamectl-only"
|
|
return
|
|
fi
|
|
|
|
# Check for cloud-init
|
|
if [ -f /etc/cloud/cloud.cfg ]; then
|
|
if grep -q "preserve_hostname: false" /etc/cloud/cloud.cfg 2>/dev/null; then
|
|
echo "cloud-init"
|
|
return
|
|
fi
|
|
fi
|
|
|
|
# Default to traditional method
|
|
echo "etc-hostname"
|
|
}
|
|
|
|
# Set hostname using hostnamectl only (for immutable systems)
|
|
set_hostname_hostnamectl_only() {
|
|
local short_hostname="$1"
|
|
local fqdn="$2"
|
|
|
|
log_info "Setting hostname via hostnamectl (immutable /etc)"
|
|
|
|
# Set static hostname
|
|
hostnamectl set-hostname "$short_hostname" --static
|
|
|
|
# Set pretty hostname
|
|
hostnamectl set-hostname "$fqdn" --pretty
|
|
|
|
# Set transient hostname
|
|
hostnamectl set-hostname "$short_hostname" --transient
|
|
|
|
log_info "Hostname set successfully (hostnamectl-only method)"
|
|
}
|
|
|
|
# Set hostname using /etc/hostname (traditional Debian/Ubuntu)
|
|
set_hostname_etc_hostname() {
|
|
local short_hostname="$1"
|
|
local fqdn="$2"
|
|
|
|
log_info "Setting hostname via /etc/hostname + hostnamectl"
|
|
|
|
# Update /etc/hostname
|
|
echo "$short_hostname" > /etc/hostname
|
|
|
|
# Update hostnamectl
|
|
hostnamectl set-hostname "$short_hostname"
|
|
|
|
# Update /etc/hosts
|
|
update_etc_hosts "$short_hostname" "$fqdn"
|
|
|
|
log_info "Hostname set successfully (etc-hostname method)"
|
|
}
|
|
|
|
# Set hostname for cloud-init managed systems
|
|
set_hostname_cloud_init() {
|
|
local short_hostname="$1"
|
|
local fqdn="$2"
|
|
|
|
log_info "Setting hostname for cloud-init managed system"
|
|
|
|
# Use hostnamectl (cloud-init will preserve it)
|
|
hostnamectl set-hostname "$short_hostname"
|
|
|
|
# Update cloud.cfg to preserve hostname
|
|
if [ -f /etc/cloud/cloud.cfg ]; then
|
|
if grep -q "preserve_hostname:" /etc/cloud/cloud.cfg; then
|
|
sed -i 's/preserve_hostname:.*/preserve_hostname: true/' /etc/cloud/cloud.cfg
|
|
else
|
|
echo "preserve_hostname: true" >> /etc/cloud/cloud.cfg
|
|
fi
|
|
log_info "Updated cloud.cfg to preserve hostname"
|
|
fi
|
|
|
|
# Update /etc/hosts
|
|
update_etc_hosts "$short_hostname" "$fqdn"
|
|
|
|
log_info "Hostname set successfully (cloud-init method)"
|
|
}
|
|
|
|
# Update /etc/hosts with new hostname
|
|
update_etc_hosts() {
|
|
local short_hostname="$1"
|
|
local fqdn="$2"
|
|
local hosts_file="/etc/hosts"
|
|
local backup_file="/etc/hosts.bak.$(date +%Y%m%d%H%M%S)"
|
|
|
|
# Backup current hosts file
|
|
cp "$hosts_file" "$backup_file"
|
|
log_info "Backed up $hosts_file to $backup_file"
|
|
|
|
# Check if 127.0.1.1 line exists
|
|
if grep -q "^127\.0\.1\.1" "$hosts_file"; then
|
|
# Update existing line
|
|
sed -i "s/^127\.0\.1\.1.*/127.0.1.1\t$fqdn $short_hostname/" "$hosts_file"
|
|
log_info "Updated existing 127.0.1.1 entry in /etc/hosts"
|
|
else
|
|
# Add new line after 127.0.0.1
|
|
sed -i "/^127\.0\.0\.1/a 127.0.1.1\t$fqdn $short_hostname" "$hosts_file"
|
|
log_info "Added new 127.0.1.1 entry to /etc/hosts"
|
|
fi
|
|
|
|
# Remove any old hostname entries that don't match
|
|
# (Be careful not to remove other valid entries)
|
|
}
|
|
|
|
# Verify hostname was set correctly
|
|
verify_hostname() {
|
|
local expected_short="$1"
|
|
local expected_fqdn="$2"
|
|
|
|
local actual_short
|
|
local actual_fqdn
|
|
|
|
actual_short=$(hostname -s 2>/dev/null || hostname)
|
|
actual_fqdn=$(hostname -f 2>/dev/null || hostname)
|
|
|
|
log_info "Verification:"
|
|
log_info " Expected short: $expected_short"
|
|
log_info " Actual short: $actual_short"
|
|
log_info " Expected FQDN: $expected_fqdn"
|
|
log_info " Actual FQDN: $actual_fqdn"
|
|
|
|
if [ "$actual_short" = "$expected_short" ]; then
|
|
log_info "Short hostname: OK"
|
|
else
|
|
log_warn "Short hostname mismatch (may need reboot)"
|
|
fi
|
|
|
|
# FQDN verification is less strict (depends on DNS/hosts config)
|
|
if [ "$actual_fqdn" = "$expected_fqdn" ] || [ "$actual_fqdn" = "$expected_short" ]; then
|
|
log_info "FQDN: OK (or pending DNS update)"
|
|
else
|
|
log_warn "FQDN may need DNS configuration"
|
|
fi
|
|
}
|
|
|
|
# Main function
|
|
main() {
|
|
if [ $# -lt 2 ]; then
|
|
echo "Usage: $0 <short-hostname> <fqdn> [hostname-method]"
|
|
echo ""
|
|
echo "Arguments:"
|
|
echo " short-hostname - Short hostname (e.g., 'apricot', '0')"
|
|
echo " fqdn - Fully qualified domain name"
|
|
echo " hostname-method - Optional: hostnamectl-only, etc-hostname, cloud-init"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 apricot apricot.voyager.nasty.sh"
|
|
echo " $0 0 0.1984.dss.nasty.sh etc-hostname"
|
|
exit 1
|
|
fi
|
|
|
|
local short_hostname="$1"
|
|
local fqdn="$2"
|
|
local method="${3:-}"
|
|
|
|
# Check for root privileges
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
log_error "This script must be run as root"
|
|
exit 1
|
|
fi
|
|
|
|
# Detect method if not provided
|
|
if [ -z "$method" ]; then
|
|
method=$(detect_hostname_method)
|
|
log_info "Auto-detected hostname method: $method"
|
|
fi
|
|
|
|
# Apply hostname based on method
|
|
case "$method" in
|
|
hostnamectl-only)
|
|
set_hostname_hostnamectl_only "$short_hostname" "$fqdn"
|
|
;;
|
|
etc-hostname)
|
|
set_hostname_etc_hostname "$short_hostname" "$fqdn"
|
|
;;
|
|
cloud-init)
|
|
set_hostname_cloud_init "$short_hostname" "$fqdn"
|
|
;;
|
|
*)
|
|
log_error "Unknown hostname method: $method"
|
|
log_error "Valid methods: hostnamectl-only, etc-hostname, cloud-init"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Verify the change
|
|
verify_hostname "$short_hostname" "$fqdn"
|
|
|
|
log_info "Done! A reboot may be required for all changes to take effect."
|
|
}
|
|
|
|
main "$@"
|