platform-deployments/provisioning/set-hostname.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

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 "$@"