#!/usr/bin/env bash # ATVM VM Setup Script # Combined script that: # 0. Validates target host identity using expected IP + hostname # 1. Fixes repository configuration (removes DVD/CD-ROM entries) # 2. Configures Ubuntu root SSH/password workflow # 3. Sets Oracle Linux default kernel to non-UEK # 4. Disables Ubuntu unattended auto-upgrades # 5. Installs cross-distro packages # 6. Disables SELinux (Red Hat-based systems only) # 7. Configures Static IP as the final step (All distributions) # 8. Reboots SELinux-capable distros and verifies SELinux after boot # 9. Keeps client powered on until controller-side log copy + hash verification complete # 10. Requires controller-side power-off only after successful verification set -euo pipefail ATVM_ROOT_SSH_PASSWORD="${ATVM_TARGET_PASSWORD:-}" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # No Color # Global variables to track actions SUDO_CMD="" SUDO_INSTALLED=false MULTIPATH_REMOVED=() ISCSI_REMOVED=() KERNEL_HEADERS_INSTALLED=false PACKAGES_INSTALLED=() DISTRO="" PM="" REPO_FIX_COMPLETED=false PACKAGE_INSTALL_COMPLETED=false AUTO_UPGRADES_DISABLED=false SELINUX_DISABLED=false SELINUX_WAS_ENFORCING=false NETWORKMANAGER_COMPLETED=false ROOT_SSH_CONFIGURED=false HOST_IDENTITY_VALIDATED=false INTERFACE="" STATIC_IP_METHOD="not-set" ORACLE_KERNEL_STEP_COMPLETED=false ORACLE_KERNEL_DEFAULT_CHANGED=false ORACLE_KERNEL_REBOOT_REQUIRED=false ORACLE_KERNEL_TARGET="" ORACLE_KERNEL_DEFAULT_BEFORE="" ORACLE_KERNEL_DEFAULT_AFTER="" ORACLE_TARGET_KERNEL_VERSION="" ATVM_SCRIPT_DIR="" LOG_FILE="atvm_setup_script.log" WARNINGS_ENCOUNTERED=() ERRORS_ENCOUNTERED=() WORKAROUNDS_USED=() SELINUX_POST_REBOOT_VERIFIED=false SELINUX_POST_REBOOT_STATUS="not-run" AUTO_REBOOT_FOR_SELINUX=false EXPECTED_IP="" EXPECTED_HOSTNAME="" ACTUAL_HOSTNAME="" #============================================================================== # UTILITY FUNCTIONS #============================================================================== # Function to log to file (without color codes) log_to_file() { echo "$1" | sed 's/\x1b\[[0-9;]*m//g' >> "$LOG_FILE" } # Function to print colored output print_info() { echo -e "${GREEN}[INFO]${NC} $1" log_to_file "[INFO] $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" log_to_file "[ERROR] $1" ERRORS_ENCOUNTERED+=("$1") } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" log_to_file "[WARNING] $1" WARNINGS_ENCOUNTERED+=("$1") } record_workaround() { WORKAROUNDS_USED+=("$1") print_info "Workaround used: $1" } is_selinux_distro() { [[ "$DISTRO" == "rhel" || "$DISTRO" == "centos" || "$DISTRO" == "rocky" || \ "$DISTRO" == "almalinux" || "$DISTRO" == "fedora" || "$DISTRO" == "ol" ]] } setup_completed_without_errors() { [[ "$HOST_IDENTITY_VALIDATED" == true ]] && \ [[ "$REPO_FIX_COMPLETED" == true ]] && \ [[ "$PACKAGE_INSTALL_COMPLETED" == true ]] && \ [[ ${#ERRORS_ENCOUNTERED[@]} -eq 0 ]] } setup_selinux_post_reboot_verifier() { local verify_script="/usr/local/sbin/atvm_selinux_post_reboot_verify.sh" local verify_service="/etc/systemd/system/atvm-selinux-postreboot.service" if ! command -v systemctl &>/dev/null; then print_warning "systemctl not available - cannot configure post-reboot SELinux verification" return 1 fi ${SUDO_CMD} bash -c "cat > '$verify_script' <<'EOF' #!/usr/bin/env bash set -euo pipefail LOG_FILE_PATH='${LOG_FILE}' RESULT='unavailable' HAS_SUCCESS_MARKER='false' HAS_ERRORS='false' if command -v getenforce >/dev/null 2>&1; then RESULT=\$(getenforce 2>/dev/null || echo unavailable) fi if grep -Fq 'SUCCESS: ATVM VM Setup Complete!' "\$LOG_FILE_PATH" 2>/dev/null; then HAS_SUCCESS_MARKER='true' fi if grep -Eq '^\\[ERROR\\]' "\$LOG_FILE_PATH" 2>/dev/null; then HAS_ERRORS='true' fi { echo '[INFO] Post-reboot SELinux verification: '\$RESULT echo '[INFO] Verification timestamp: '\"\$(date '+%Y-%m-%d %H:%M:%S %Z')\" if [[ "\$HAS_SUCCESS_MARKER" == 'true' && "\$HAS_ERRORS" == 'false' ]]; then echo '[INFO] Setup completed without logged errors - keeping client powered on for controller log collection.' echo '[INFO] Required next step: after controller log copy + SHA256 verification succeed, power off the client from controller.' else echo '[WARNING] Setup reported errors or missing success marker - keeping client powered on for manual inspection.' fi } >> \"\$LOG_FILE_PATH\" systemctl disable atvm-selinux-postreboot.service >/dev/null 2>&1 || true rm -f /etc/systemd/system/atvm-selinux-postreboot.service rm -f /usr/local/sbin/atvm_selinux_post_reboot_verify.sh systemctl daemon-reload >/dev/null 2>&1 || true EOF" ${SUDO_CMD} chmod 700 "$verify_script" ${SUDO_CMD} bash -c "cat > '$verify_service' <<'EOF' [Unit] Description=ATVM SELinux Post-Reboot Verification After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/local/sbin/atvm_selinux_post_reboot_verify.sh [Install] WantedBy=multi-user.target EOF" ${SUDO_CMD} systemctl daemon-reload ${SUDO_CMD} systemctl enable atvm-selinux-postreboot.service >/dev/null return 0 } reboot_and_verify_selinux_if_needed() { print_section "STEP 9: SELinux Reboot And Verification" if ! is_selinux_distro; then print_info "SELinux reboot/verification is not applicable to this distro" return 0 fi if [[ "$SELINUX_DISABLED" != true ]]; then print_info "SELinux was not changed to disabled - no reboot required for SELinux verification" return 0 fi if setup_completed_without_errors; then print_info "Run is currently error-free; post-reboot verifier will keep client on for controller log collection" else print_warning "Errors detected in this run; post-reboot verifier will keep client powered on for manual inspection" fi if setup_selinux_post_reboot_verifier; then print_info "Configured one-time post-reboot SELinux verification" else print_warning "Proceeding with reboot without automated post-reboot verification service" fi AUTO_REBOOT_FOR_SELINUX=true print_warning "Rebooting now to apply SELinux disabled state" print_warning "After reboot, post-reboot verification will log SELinux runtime status" sync sleep 2 ${SUDO_CMD} reboot } poweroff_client_if_successful() { print_section "STEP 10: Final Power State" if setup_completed_without_errors; then print_info "Setup completed without errors - keeping client powered on for controller log collection" print_info "Required next step: after controller copies log and confirms SHA256 match, power off the client" else print_warning "Setup has errors or incomplete required steps - keeping client powered on for manual inspection" fi } print_section() { echo "" echo -e "${BLUE}========================================${NC}" echo -e "${BLUE}$1${NC}" echo -e "${BLUE}========================================${NC}" echo "" log_to_file "" log_to_file "========================================" log_to_file "$1" log_to_file "========================================" log_to_file "" } print_usage() { cat <<'EOF' Usage: atvm_setup_script.sh --expected-ip --expected-hostname Required: --expected-ip Expected target host IPv4 address (the SSH destination) --expected-hostname Expected target hostname for safety verification (must be operator-provided; do not infer) If hostname was not explicitly given by the operator, do not run setup. Connect directly to the operator-provided target IP. Do not pre-probe alternate "likely" IP addresses. EOF } parse_args() { while [[ $# -gt 0 ]]; do case "$1" in --expected-ip) EXPECTED_IP="${2:-}" shift 2 ;; --expected-hostname) EXPECTED_HOSTNAME="${2:-}" shift 2 ;; -h|--help) print_usage exit 0 ;; *) print_error "Unknown argument: $1" print_usage exit 1 ;; esac done if [[ -z "$EXPECTED_IP" || -z "$EXPECTED_HOSTNAME" ]]; then print_error "Safety check requires both --expected-ip and --expected-hostname" print_error "Expected hostname must be explicitly operator-provided. Do not infer hostname from the target." print_usage exit 1 fi } validate_target_host_identity() { print_section "STEP 0: Target Host Identity Validation" local expected_norm actual_short actual_fqdn expected_norm="$(echo "$EXPECTED_HOSTNAME" | tr '[:upper:]' '[:lower:]')" actual_short="$(hostname -s 2>/dev/null || hostname)" actual_fqdn="$(hostname -f 2>/dev/null || hostname)" ACTUAL_HOSTNAME="$actual_short" print_info "Expected IP: $EXPECTED_IP" print_info "Expected hostname: $EXPECTED_HOSTNAME" print_info "Detected hostname (short): $actual_short" print_info "Detected hostname (fqdn): $actual_fqdn" if ! ip -o -4 addr show 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | grep -Fxq "$EXPECTED_IP"; then print_error "Expected IP $EXPECTED_IP is not assigned to this machine. Aborting for safety." exit 1 fi if [[ "$expected_norm" != "$(echo "$actual_short" | tr '[:upper:]' '[:lower:]')" ]] && \ [[ "$expected_norm" != "$(echo "$actual_fqdn" | tr '[:upper:]' '[:lower:]')" ]]; then print_error "Hostname mismatch. Expected '$EXPECTED_HOSTNAME' but detected '$actual_short'. Aborting." exit 1 fi HOST_IDENTITY_VALIDATED=true print_info "Target host identity validation passed" } #============================================================================== # SECTION 1: REPOSITORY FIX #============================================================================== fix_repositories() { print_section "STEP 1: Fixing Repository Configuration" print_info "Detecting Linux distribution..." if [ -f /etc/os-release ]; then . /etc/os-release DISTRO=$ID else print_error "Cannot detect distribution" exit 1 fi print_info "Detected: $DISTRO" case $DISTRO in ubuntu|debian) print_info "Processing Debian/Ubuntu repositories..." REPO_FILE="/etc/apt/sources.list" if [ -f "$REPO_FILE" ]; then ${SUDO_CMD} cp $REPO_FILE ${REPO_FILE}.backup.$(date +%Y%m%d_%H%M%S) print_info "Backup created: ${REPO_FILE}.backup.$(date +%Y%m%d_%H%M%S)" ${SUDO_CMD} sed -i 's/^deb cdrom:/#deb cdrom:/g' $REPO_FILE ${SUDO_CMD} sed -i 's/^deb-src cdrom:/#deb-src cdrom:/g' $REPO_FILE fi if [ -d /etc/apt/sources.list.d/ ]; then for file in /etc/apt/sources.list.d/*.list; do if [ -f "$file" ]; then ${SUDO_CMD} sed -i 's/^deb cdrom:/#deb cdrom:/g' "$file" ${SUDO_CMD} sed -i 's/^deb-src cdrom:/#deb-src cdrom:/g' "$file" fi done fi print_info "CD-ROM/DVD entries commented out" print_info "Updating package lists..." ${SUDO_CMD} apt-get update ;; rhel|centos|rocky|almalinux|ol) print_info "Processing Red Hat/CentOS/Oracle Linux repositories..." REPO_DIR="/etc/yum.repos.d" for file in $REPO_DIR/*.repo; do if [ -f "$file" ]; then if grep -q "file:///media\|cdrom\|dvd" "$file"; then print_info "Processing: $file" ${SUDO_CMD} cp "$file" "${file}.backup.$(date +%Y%m%d_%H%M%S)" ${SUDO_CMD} sed -i 's/^baseurl=file:\/\/\/media/#baseurl=file:\/\/\/media/g' "$file" ${SUDO_CMD} sed -i 's/^baseurl=.*cdrom/#&/g' "$file" ${SUDO_CMD} sed -i 's/^baseurl=.*dvd/#&/g' "$file" ${SUDO_CMD} sed -i '/\[.*media.*\]/,/^\[/ s/^enabled=1/enabled=0/' "$file" fi fi done print_info "CD-ROM/DVD entries commented out" print_info "Cleaning and updating cache..." ${SUDO_CMD} yum clean all ${SUDO_CMD} yum makecache ;; fedora) print_info "Processing Fedora repositories..." REPO_DIR="/etc/yum.repos.d" for file in $REPO_DIR/*.repo; do if [ -f "$file" ]; then if grep -q "file:///media\|cdrom\|dvd" "$file"; then print_info "Processing: $file" ${SUDO_CMD} cp "$file" "${file}.backup.$(date +%Y%m%d_%H%M%S)" ${SUDO_CMD} sed -i 's/^baseurl=file:\/\/\/media/#baseurl=file:\/\/\/media/g' "$file" ${SUDO_CMD} sed -i 's/^baseurl=.*cdrom/#&/g' "$file" ${SUDO_CMD} sed -i 's/^baseurl=.*dvd/#&/g' "$file" ${SUDO_CMD} sed -i '/\[.*media.*\]/,/^\[/ s/^enabled=1/enabled=0/' "$file" fi fi done print_info "CD-ROM/DVD entries commented out" print_info "Cleaning and updating cache..." ${SUDO_CMD} dnf clean all ${SUDO_CMD} dnf makecache ;; opensuse*|sles) print_info "Processing openSUSE/SLES repositories..." print_info "Disabling CD/DVD repositories..." for repo in $(${SUDO_CMD} zypper lr | grep -i "cd\|dvd" | awk '{print $1}'); do print_info "Disabling repository: $repo" ${SUDO_CMD} zypper mr -d $repo done print_info "Refreshing repositories..." ${SUDO_CMD} zypper refresh ;; *) print_warning "Unsupported distribution for repo fix: $DISTRO" print_warning "Continuing with installation..." ;; esac print_info "Repository configuration updated" REPO_FIX_COMPLETED=true } #============================================================================== # SECTION 1B: UBUNTU ROOT SSH WORKFLOW #============================================================================== configure_ubuntu_root_ssh_access() { print_section "STEP 2: Ubuntu Root SSH Access Configuration" if [[ "$DISTRO" != "ubuntu" ]]; then print_info "Ubuntu root SSH workflow is not applicable to this distro" return 0 fi print_warning "Ubuntu-specific workflow: configuring root account for SSH password login" if [[ -z "$ATVM_ROOT_SSH_PASSWORD" ]]; then print_error "ATVM_TARGET_PASSWORD must be set before running the Ubuntu root SSH workflow" exit 1 fi echo "root:${ATVM_ROOT_SSH_PASSWORD}" | ${SUDO_CMD} chpasswd ${SUDO_CMD} passwd -u root >/dev/null 2>&1 || true print_info "Root password set to configured workflow value" ${SUDO_CMD} install -d -m 755 /etc/ssh/sshd_config.d ${SUDO_CMD} bash -c "cat > /etc/ssh/sshd_config.d/99-atvm-root-login.conf <<'EOF' PermitRootLogin yes PasswordAuthentication yes KbdInteractiveAuthentication yes UsePAM yes EOF" ${SUDO_CMD} sshd -t if ${SUDO_CMD} systemctl restart ssh 2>/dev/null; then print_info "Restarted ssh service" elif ${SUDO_CMD} systemctl restart sshd 2>/dev/null; then print_info "Restarted sshd service" else print_warning "Could not restart ssh/sshd service automatically" fi local sshd_effective sshd_effective="$(${SUDO_CMD} sshd -T 2>/dev/null | egrep 'permitrootlogin|passwordauthentication|kbdinteractiveauthentication|usepam' || true)" if [[ -n "$sshd_effective" ]]; then print_info "Effective sshd settings:" while IFS= read -r line; do print_info " $line" done <<< "$sshd_effective" fi ROOT_SSH_CONFIGURED=true print_info "Root SSH/password workflow configured for Ubuntu" print_info "Next operator step: reconnect as root using the ATVM_TARGET_PASSWORD value" } #============================================================================== # SECTION 2: STATIC IP CONFIGURATION #============================================================================== # All check functions use ONLY: command -v, [ -d ], [ -f ], rpm -q. # NO grep-in-pipe — those return exit code 1 on no-match and kill the script # under `set -euo pipefail`. # Interface is AUTO-DETECTED from the system rather than hardcoded. #============================================================================== check_netplan() { command -v netplan &>/dev/null && return 0 [ -d /etc/netplan ] && [ -n "$(ls -A /etc/netplan 2>/dev/null)" ] && return 0 return 1 } check_networkmanager() { command -v nmcli &>/dev/null && return 0 [ -d /etc/NetworkManager ] && return 0 [ -d /etc/NetworkManager/system-connections ] && return 0 [ -f /usr/lib/systemd/system/NetworkManager.service ] && return 0 [ -f /lib/systemd/system/NetworkManager.service ] && return 0 command -v rpm &>/dev/null && rpm -q NetworkManager &>/dev/null 2>&1 && return 0 return 1 } check_wicked() { command -v wicked &>/dev/null && return 0 [ -f /usr/lib/systemd/system/wicked.service ] && return 0 [ -f /lib/systemd/system/wicked.service ] && return 0 return 1 } check_ifcfg() { [ -d /etc/sysconfig/network-scripts ] && \ ls /etc/sysconfig/network-scripts/ifcfg-* &>/dev/null 2>&1 && return 0 [ -f /etc/sysconfig/network ] && return 0 return 1 } # Detect the active non-loopback interface that currently has an IP. # Priority: interface with default route > interface with an IP > first UP non-lo interface. detect_active_interface() { local iface="" # 1. Interface carrying the default route (most reliable) iface=$(ip route 2>/dev/null | awk '/^default/ {print $5; exit}') if [ -n "$iface" ] && ip link show "$iface" &>/dev/null 2>&1; then echo "$iface"; return 0 fi # 2. First non-loopback interface with an IPv4 address assigned iface=$(ip -4 addr show 2>/dev/null \ | awk '/^[0-9]+:/ {gsub(/:$/,"",$2); iface=$2} /inet / && iface != "lo" {print iface; exit}') if [ -n "$iface" ] && ip link show "$iface" &>/dev/null 2>&1; then echo "$iface"; return 0 fi # 3. First non-loopback interface that is UP (may not have IP yet) iface=$(ip link show 2>/dev/null \ | awk -F': ' '/^[0-9]+: / && $2 != "lo" {print $2; exit}') if [ -n "$iface" ] && ip link show "$iface" &>/dev/null 2>&1; then echo "$iface"; return 0 fi return 1 } # Shared helper: write a NetworkManager keyfile and activate it. write_nm_keyfile() { local nm_conn_dir="/etc/NetworkManager/system-connections" local keyfile="${nm_conn_dir}/${INTERFACE}.nmconnection" local uuid if command -v uuidgen &>/dev/null; then uuid=$(uuidgen) else uuid=$(cat /proc/sys/kernel/random/uuid) fi print_info "Writing NM keyfile: ${keyfile}" ${SUDO_CMD} mkdir -p "${nm_conn_dir}" [ -f "${keyfile}" ] && \ ${SUDO_CMD} cp "${keyfile}" "${keyfile}.backup.$(date +%Y%m%d_%H%M%S)" ${SUDO_CMD} bash -c "printf '%s\n' \ '[connection]' \ 'id=${INTERFACE}' \ 'uuid=${uuid}' \ 'type=ethernet' \ 'interface-name=${INTERFACE}' \ 'autoconnect=true' \ '' \ '[ethernet]' \ '' \ '[ipv4]' \ 'method=manual' \ 'address1=${IP_ADDRESS}/${NETMASK},${GATEWAY}' \ 'dns=${DNS1};${DNS2};' \ '' \ '[ipv6]' \ 'method=disabled' \ '' \ '[proxy]' \ > '${keyfile}'" ${SUDO_CMD} chmod 600 "${keyfile}" print_info "Keyfile written (permissions 600)" ${SUDO_CMD} systemctl enable NetworkManager 2>/dev/null || true ${SUDO_CMD} systemctl start NetworkManager 2>/dev/null || true sleep 2 if command -v nmcli &>/dev/null; then ${SUDO_CMD} nmcli connection reload 2>/dev/null || true ${SUDO_CMD} nmcli connection up "${INTERFACE}" 2>/dev/null || true print_info "Connection activated via nmcli" else local nm_pid nm_pid=$(systemctl show -p MainPID NetworkManager 2>/dev/null | cut -d= -f2 || true) [ -n "${nm_pid:-}" ] && [ "${nm_pid}" != "0" ] && \ ${SUDO_CMD} kill -HUP "$nm_pid" 2>/dev/null || true print_info "NM signalled to reload (will activate on next reboot if nmcli unavailable)" fi echo "" print_info "Network verification:" ip addr show "${INTERFACE}" 2>/dev/null | grep "inet " || true ip route 2>/dev/null | grep default || true STATIC_IP_METHOD="NetworkManager keyfile" } configure_static_ip() { print_section "STEP 8: Static IP Configuration" IP_ADDRESS="192.168.3.191" NETMASK="22" GATEWAY="192.168.0.1" DNS1="8.8.8.8" DNS2="8.8.4.4" # Auto-detect the active network interface — no hardcoded name print_info "Auto-detecting active network interface..." INTERFACE=$(detect_active_interface 2>/dev/null || true) if [ -z "${INTERFACE:-}" ]; then print_warning "Could not detect an active network interface — skipping static IP" print_info "Available interfaces:" ip link show 2>/dev/null | awk -F': ' '/^[0-9]+: / {print " - " $2}' return 0 fi print_info "Detected active interface: ${INTERFACE}" print_info "Configuring static IP for $DISTRO..." print_info "Interface: $INTERFACE | IP: $IP_ADDRESS/$NETMASK | Gateway: $GATEWAY" echo "" # Check for network managers print_info "Checking for network managers..." FOUND_MANAGER=false if check_netplan; then print_info "Found: netplan"; FOUND_MANAGER=true; fi if check_networkmanager; then print_info "Found: NetworkManager"; FOUND_MANAGER=true; fi if check_wicked; then print_info "Found: wicked"; FOUND_MANAGER=true; fi if check_ifcfg; then print_info "Found: legacy ifcfg"; FOUND_MANAGER=true; fi if [[ "$FOUND_MANAGER" == false ]]; then print_warning "No network manager detected — skipping static IP" print_warning "Fix: dnf install -y NetworkManager && systemctl enable --now NetworkManager" return 0 fi print_info "Network manager detected — proceeding with configuration" echo "" case $DISTRO in ubuntu|debian) configure_debian_static_ip ;; rhel|centos|rocky|almalinux|fedora|ol) configure_redhat_static_ip ;; opensuse*|sles) configure_suse_static_ip ;; arch) configure_arch_static_ip ;; *) print_warning "Unsupported distribution for static IP: $DISTRO" return 0 ;; esac } configure_debian_static_ip() { if check_netplan; then local netplan_file="/etc/netplan/99-atvm-static.yaml" [ -f "$netplan_file" ] && \ ${SUDO_CMD} cp "$netplan_file" "${netplan_file}.backup.$(date +%Y%m%d_%H%M%S)" ${SUDO_CMD} bash -c "cat > '$netplan_file' < /etc/network/interfaces" ${SUDO_CMD} systemctl restart networking 2>/dev/null || true print_info "/etc/network/interfaces configured" STATIC_IP_METHOD="ifupdown interfaces" fi elif command -v nmcli &>/dev/null; then write_nm_keyfile else [ -f /etc/network/interfaces ] && \ ${SUDO_CMD} cp /etc/network/interfaces \ "/etc/network/interfaces.backup.$(date +%Y%m%d_%H%M%S)" ${SUDO_CMD} bash -c "printf '%s\n' \ 'auto lo' \ 'iface lo inet loopback' \ '' \ 'auto ${INTERFACE}' \ 'iface ${INTERFACE} inet static' \ ' address ${IP_ADDRESS}' \ ' netmask 255.255.252.0' \ ' gateway ${GATEWAY}' \ ' dns-nameservers ${DNS1} ${DNS2}' \ > /etc/network/interfaces" ${SUDO_CMD} systemctl restart networking 2>/dev/null || true print_info "/etc/network/interfaces configured" STATIC_IP_METHOD="ifupdown interfaces" fi echo "" print_info "Network verification:" ip addr show "${INTERFACE}" 2>/dev/null | grep "inet " || true ip route 2>/dev/null | grep default || true NETWORKMANAGER_COMPLETED=true } configure_redhat_static_ip() { # Check NM service state safely (no pipe) local nm_active=false systemctl is-active --quiet NetworkManager 2>/dev/null && nm_active=true if command -v nmcli &>/dev/null && [[ "$nm_active" == true ]]; then print_info "Using nmcli (NetworkManager is running)" # Delete stale connections one at a time — nmcli delete takes ONE argument ${SUDO_CMD} nmcli connection delete "${INTERFACE}" 2>/dev/null || true ${SUDO_CMD} nmcli connection delete "Wired connection 1" 2>/dev/null || true ${SUDO_CMD} nmcli connection delete "System ${INTERFACE}" 2>/dev/null || true if ${SUDO_CMD} nmcli connection add \ type ethernet \ con-name "${INTERFACE}" \ ifname "${INTERFACE}" \ ipv4.method manual \ ipv4.addresses "${IP_ADDRESS}/${NETMASK}" \ ipv4.gateway "${GATEWAY}" \ ipv4.dns "${DNS1} ${DNS2}" \ connection.autoconnect yes 2>/dev/null; then ${SUDO_CMD} nmcli connection up "${INTERFACE}" 2>/dev/null || \ print_warning "Could not activate now — will apply on reboot" print_info "NetworkManager configured via nmcli" STATIC_IP_METHOD="NetworkManager (nmcli)" else print_warning "nmcli connection add failed — falling back to keyfile" record_workaround "nmcli connection add failed; generated NetworkManager keyfile fallback" write_nm_keyfile fi elif command -v nmcli &>/dev/null && [[ "$nm_active" == false ]]; then print_info "NetworkManager installed but not running — starting it and writing keyfile" record_workaround "NetworkManager was installed but inactive; started service and wrote keyfile" ${SUDO_CMD} systemctl enable NetworkManager 2>/dev/null || true ${SUDO_CMD} systemctl start NetworkManager 2>/dev/null || true sleep 3 write_nm_keyfile else # nmcli not in PATH — write keyfile directly (Rocky/OL/RHEL 9 minimal install) print_info "nmcli not found — writing NM keyfile directly" record_workaround "nmcli not available; generated NetworkManager keyfile directly" write_nm_keyfile fi echo "" print_info "Network verification:" ip addr show "${INTERFACE}" 2>/dev/null | grep "inet " || true ip route 2>/dev/null | grep default || true NETWORKMANAGER_COMPLETED=true } configure_suse_static_ip() { if command -v wicked &>/dev/null; then local ifcfg="/etc/sysconfig/network/ifcfg-${INTERFACE}" [ -f "$ifcfg" ] && ${SUDO_CMD} cp "$ifcfg" "${ifcfg}.backup.$(date +%Y%m%d_%H%M%S)" ${SUDO_CMD} bash -c "printf '%s\n' \ \"BOOTPROTO='static'\" \ \"IPADDR='${IP_ADDRESS}/${NETMASK}'\" \ \"STARTMODE='auto'\" \ > '${ifcfg}'" ${SUDO_CMD} bash -c "echo 'default ${GATEWAY} - -' \ > /etc/sysconfig/network/ifroute-${INTERFACE}" ${SUDO_CMD} wicked ifreload "${INTERFACE}" 2>/dev/null || \ ${SUDO_CMD} systemctl restart wicked 2>/dev/null || true print_info "Wicked configured" STATIC_IP_METHOD="wicked" NETWORKMANAGER_COMPLETED=true else print_warning "wicked not found — cannot configure static IP on this SUSE system" fi } configure_arch_static_ip() { local nm_active=false systemctl is-active --quiet NetworkManager 2>/dev/null && nm_active=true if command -v nmcli &>/dev/null && [[ "$nm_active" == true ]]; then ${SUDO_CMD} nmcli connection delete "${INTERFACE}" 2>/dev/null || true ${SUDO_CMD} nmcli connection add \ type ethernet con-name "${INTERFACE}" ifname "${INTERFACE}" \ ipv4.method manual \ ipv4.addresses "${IP_ADDRESS}/${NETMASK}" \ ipv4.gateway "${GATEWAY}" \ ipv4.dns "${DNS1} ${DNS2}" \ connection.autoconnect yes 2>/dev/null || true ${SUDO_CMD} nmcli connection up "${INTERFACE}" 2>/dev/null || true print_info "NetworkManager configured" STATIC_IP_METHOD="NetworkManager (nmcli)" else local netfile="/etc/systemd/network/20-${INTERFACE}.network" [ -f "$netfile" ] && ${SUDO_CMD} cp "$netfile" "${netfile}.backup.$(date +%Y%m%d_%H%M%S)" ${SUDO_CMD} bash -c "printf '%s\n' \ '[Match]' \ 'Name=${INTERFACE}' \ '' \ '[Network]' \ 'Address=${IP_ADDRESS}/${NETMASK}' \ 'Gateway=${GATEWAY}' \ 'DNS=${DNS1}' \ 'DNS=${DNS2}' \ > '${netfile}'" ${SUDO_CMD} systemctl enable systemd-networkd 2>/dev/null || true ${SUDO_CMD} systemctl restart systemd-networkd 2>/dev/null || true print_info "systemd-networkd configured" STATIC_IP_METHOD="systemd-networkd" fi NETWORKMANAGER_COMPLETED=true } # SECTION 3: PACKAGE INSTALLATION #============================================================================== detect_package_manager() { if command -v apt-get &> /dev/null; then echo "apt" elif command -v dnf &> /dev/null; then echo "dnf" elif command -v yum &> /dev/null; then echo "yum" elif command -v zypper &> /dev/null; then echo "zypper" elif command -v pacman &> /dev/null; then echo "pacman" elif command -v apk &> /dev/null; then echo "apk" else echo "unknown" fi } check_sudo() { if command -v sudo &> /dev/null; then SUDO_CMD="sudo" print_info "sudo is available" return 0 fi if [[ $EUID -eq 0 ]]; then SUDO_CMD="" print_info "Running as root - will install sudo after fixing repositories" return 0 fi print_error "sudo is not installed and you are not root." print_error "Please run as root: su -c './atvm_vm_setup.sh'" exit 1 } install_sudo_if_needed() { # If sudo already exists, nothing to do if command -v sudo &> /dev/null; then return 0 fi # Must be root to install sudo if [[ $EUID -ne 0 ]]; then print_error "Cannot install sudo without root privileges" exit 1 fi print_info "sudo not found - installing now..." local pm=$(detect_package_manager) case $pm in apt) apt-get update apt-get install -y sudo ;; dnf) dnf install -y sudo ;; yum) yum install -y sudo ;; zypper) zypper install -y sudo ;; pacman) pacman -Sy --noconfirm sudo ;; apk) apk add sudo ;; *) print_error "Cannot install sudo: unknown package manager" exit 1 ;; esac if command -v sudo &> /dev/null; then SUDO_INSTALLED=true SUDO_CMD="sudo" print_info "sudo has been installed successfully" # If we were running as root, we now want to use sudo for consistency if [[ $EUID -eq 0 ]]; then print_info "Will use sudo for all commands for consistency" fi else print_error "Failed to install sudo" exit 1 fi } install_kernel_headers() { local pm=$1 local kernel_ver local devel_pkg="" local headers_pkg="" kernel_ver=$(uname -r) # On Oracle Linux, prefer headers for the selected non-UEK target kernel. if [[ "$DISTRO" == "ol" ]] && [[ -n "${ORACLE_TARGET_KERNEL_VERSION:-}" ]]; then kernel_ver="$ORACLE_TARGET_KERNEL_VERSION" fi print_info "Installing kernel headers for kernel version: $kernel_ver" if [[ "$DISTRO" == "ol" && "$kernel_ver" == *"uek"* ]]; then devel_pkg="kernel-uek-devel-${kernel_ver}" headers_pkg="kernel-headers-${kernel_ver}" else devel_pkg="kernel-devel-${kernel_ver}" headers_pkg="kernel-headers-${kernel_ver}" fi case $pm in apt) ${SUDO_CMD} apt-get update ${SUDO_CMD} apt-get install -y "linux-headers-${kernel_ver}" ;; dnf) if ! ${SUDO_CMD} dnf install -y "$devel_pkg" "$headers_pkg"; then print_warning "Version-locked kernel headers not found; falling back to latest kernel header packages" if [[ "$DISTRO" == "ol" && "$kernel_ver" == *"uek"* ]]; then ${SUDO_CMD} dnf install -y kernel-uek-devel kernel-headers else ${SUDO_CMD} dnf install -y kernel-devel kernel-headers fi fi ;; yum) if ! ${SUDO_CMD} yum install -y "$devel_pkg" "$headers_pkg"; then print_warning "Version-locked kernel headers not found; falling back to latest kernel header packages" if [[ "$DISTRO" == "ol" && "$kernel_ver" == *"uek"* ]]; then ${SUDO_CMD} yum install -y kernel-uek-devel kernel-headers else ${SUDO_CMD} yum install -y kernel-devel kernel-headers fi fi ;; zypper) ${SUDO_CMD} zypper install -n -y --type pattern devel_basis ${SUDO_CMD} zypper install -n -y -f "kernel-devel=${kernel_ver}" "kernel-source=${kernel_ver}" libselinux-devel ;; pacman) local pacman_ver=$(echo "$kernel_ver" | sed 's/-/./1') ${SUDO_CMD} pacman -Sy --noconfirm "linux-headers=${pacman_ver}" || ${SUDO_CMD} pacman -Sy --noconfirm linux-headers ;; apk) local flavor=$(echo "$kernel_ver" | grep -oP '\d+-\K[^-]+$' || echo "") if [[ -n "$flavor" ]]; then ${SUDO_CMD} apk add "linux-${flavor}-dev" || ${SUDO_CMD} apk add linux-headers else ${SUDO_CMD} apk add linux-headers fi ;; *) print_error "Unknown package manager. Cannot install kernel headers." exit 1 ;; esac print_info "Kernel headers installed successfully" KERNEL_HEADERS_INSTALLED=true } remove_multipath_iscsi() { local pm=$1 print_info "Checking for multipath and iSCSI packages..." case $pm in apt) local packages_to_remove=() dpkg -l 2>/dev/null | grep -q "multipath-tools" && packages_to_remove+=("multipath-tools") && MULTIPATH_REMOVED+=("multipath-tools") dpkg -l 2>/dev/null | grep -q "open-iscsi" && packages_to_remove+=("open-iscsi") && ISCSI_REMOVED+=("open-iscsi") if [ ${#packages_to_remove[@]} -gt 0 ]; then print_info "Removing packages: ${packages_to_remove[*]}" ${SUDO_CMD} systemctl stop multipathd iscsid open-iscsi 2>/dev/null || true ${SUDO_CMD} apt-get remove -y "${packages_to_remove[@]}" 2>/dev/null || true ${SUDO_CMD} apt-get autoremove -y 2>/dev/null || true else print_info "No multipath or iSCSI packages found" fi ;; dnf|yum) local packages_to_remove=() rpm -qa 2>/dev/null | grep -q "device-mapper-multipath" && packages_to_remove+=("device-mapper-multipath") && MULTIPATH_REMOVED+=("device-mapper-multipath") rpm -qa 2>/dev/null | grep -q "iscsi-initiator-utils" && packages_to_remove+=("iscsi-initiator-utils") && ISCSI_REMOVED+=("iscsi-initiator-utils") if [ ${#packages_to_remove[@]} -gt 0 ]; then print_info "Removing packages: ${packages_to_remove[*]}" ${SUDO_CMD} systemctl stop multipathd iscsid 2>/dev/null || true [[ "$pm" == "dnf" ]] && ${SUDO_CMD} dnf remove -y "${packages_to_remove[@]}" 2>/dev/null || ${SUDO_CMD} yum remove -y "${packages_to_remove[@]}" 2>/dev/null || true else print_info "No multipath or iSCSI packages found" fi ;; *) print_info "No multipath or iSCSI packages found" ;; esac } map_package_name() { local pm=$1 local pkg=$2 case $pm in apt) case $pkg in scsitools) echo "sg3-utils" ;; elfutils-libelf-devel) echo "libelf-dev" ;; *) echo "$pkg" ;; esac ;; dnf|yum) case $pkg in scsitools) echo "sg3_utils" ;; *) echo "$pkg" ;; esac ;; zypper) case $pkg in scsitools) echo "sg3_utils" ;; net-tools) echo "net-tools-deprecated" ;; elfutils-libelf-devel) echo "libelf-devel" ;; *) echo "$pkg" ;; esac ;; pacman) case $pkg in scsitools) echo "sg3_utils" ;; elfutils-libelf-devel) echo "libelf" ;; python3) echo "python" ;; *) echo "$pkg" ;; esac ;; apk) case $pkg in scsitools) echo "sg3_utils" ;; elfutils-libelf-devel) echo "elfutils-dev" ;; *) echo "$pkg" ;; esac ;; *) echo "$pkg" ;; esac } install_packages() { local pm=$1 shift local packages=("$@") local mapped_packages=() for pkg in "${packages[@]}"; do mapped_packages+=("$(map_package_name "$pm" "$pkg")") done PACKAGES_INSTALLED=("${mapped_packages[@]}") print_info "Using package manager: $pm" print_info "Installing packages: ${mapped_packages[*]}" case $pm in apt) ${SUDO_CMD} apt-get update ${SUDO_CMD} apt-get install -y "${mapped_packages[@]}" ;; dnf) ${SUDO_CMD} dnf install -y "${mapped_packages[@]}" ;; yum) ${SUDO_CMD} yum install -y "${mapped_packages[@]}" ;; zypper) ${SUDO_CMD} zypper refresh ${SUDO_CMD} zypper install -y "${mapped_packages[@]}" ;; pacman) ${SUDO_CMD} pacman -Sy --noconfirm "${mapped_packages[@]}" ;; apk) ${SUDO_CMD} apk update ${SUDO_CMD} apk add "${mapped_packages[@]}" ;; *) print_error "Unknown package manager. Cannot install packages." exit 1 ;; esac } run_package_installation() { print_section "STEP 6: Package Installation" PM=$(detect_package_manager) if [[ "$PM" == "unknown" ]]; then print_error "Could not detect package manager." exit 1 fi remove_multipath_iscsi "$PM" install_kernel_headers "$PM" PACKAGES=("curl" "wget" "git" "vim" "perl" "gdb" "scsitools" "net-tools" "parted" "fio" "ca-certificates" "python3" "elfutils-libelf-devel") install_packages "$PM" "${PACKAGES[@]}" print_info "Package installation completed" PACKAGE_INSTALL_COMPLETED=true } #============================================================================== # SECTION 4: UBUNTU AUTO-UPGRADE DISABLE #============================================================================== disable_ubuntu_auto_upgrades() { print_section "STEP 5: Ubuntu Auto Upgrade Configuration" if [[ "$DISTRO" != "ubuntu" ]]; then print_info "Auto-upgrade configuration is only applied on Ubuntu" return 0 fi local auto_upgrade_file="/etc/apt/apt.conf.d/20auto-upgrades" print_info "Disabling Ubuntu periodic package list updates and unattended upgrades..." local backup_dir="/var/backups/atvm" local backup_file="${backup_dir}/20auto-upgrades.backup.$(date +%Y%m%d_%H%M%S)" if [ -f "$auto_upgrade_file" ]; then ${SUDO_CMD} mkdir -p "$backup_dir" ${SUDO_CMD} cp "$auto_upgrade_file" "$backup_file" print_info "Backup created: ${backup_file}" fi ${SUDO_CMD} bash -c "cat > '$auto_upgrade_file' <<'EOF' APT::Periodic::Update-Package-Lists \"0\"; APT::Periodic::Unattended-Upgrade \"0\"; EOF" print_info "Updated auto-upgrade settings:" echo -e "${CYAN}----------------------------------------${NC}" ${SUDO_CMD} cat "$auto_upgrade_file" echo -e "${CYAN}----------------------------------------${NC}" AUTO_UPGRADES_DISABLED=true } #============================================================================== # SECTION 5: ORACLE KERNEL DEFAULT (NON-UEK) #============================================================================== configure_oracle_non_uek_kernel() { print_section "STEP 3: Oracle Kernel Default Configuration" if [[ "$DISTRO" != "ol" ]]; then print_info "Oracle kernel configuration only applies to Oracle Linux" return 0 fi if ! command -v grubby &> /dev/null; then print_warning "grubby not found - cannot change default kernel" return 0 fi ORACLE_KERNEL_DEFAULT_BEFORE=$(grubby --default-kernel 2>/dev/null || true) ORACLE_KERNEL_TARGET=$(grubby --info=ALL 2>/dev/null \ | awk -F'"' '/^kernel=/{print $2}' \ | grep -v 'uek' \ | head -n 1 || true) if [[ -z "${ORACLE_KERNEL_TARGET:-}" ]]; then print_warning "No non-UEK kernel found in boot entries - leaving current default" return 0 fi ORACLE_TARGET_KERNEL_VERSION=$(basename "$ORACLE_KERNEL_TARGET" | sed 's/^vmlinuz-//') print_info "Current default kernel: ${ORACLE_KERNEL_DEFAULT_BEFORE:-unknown}" print_info "Target non-UEK kernel: $ORACLE_KERNEL_TARGET" if [[ "$ORACLE_KERNEL_DEFAULT_BEFORE" != "$ORACLE_KERNEL_TARGET" ]]; then print_info "Setting non-UEK kernel as default boot entry..." ${SUDO_CMD} grubby --set-default "$ORACLE_KERNEL_TARGET" ORACLE_KERNEL_DEFAULT_CHANGED=true else print_info "Default kernel is already set to non-UEK" fi ORACLE_KERNEL_DEFAULT_AFTER=$(grubby --default-kernel 2>/dev/null || true) print_info "New default kernel: ${ORACLE_KERNEL_DEFAULT_AFTER:-unknown}" if [[ "$ORACLE_KERNEL_DEFAULT_AFTER" == "$ORACLE_KERNEL_TARGET" ]] && \ [[ "$ORACLE_KERNEL_DEFAULT_AFTER" != "/boot/vmlinuz-$(uname -r)" ]]; then ORACLE_KERNEL_REBOOT_REQUIRED=true print_warning "Kernel default changed but running kernel is still $(uname -r)" print_warning "Reboot required to boot into non-UEK kernel" fi ORACLE_KERNEL_STEP_COMPLETED=true } #============================================================================== # SECTION 6: SELINUX DISABLE #============================================================================== disable_selinux() { print_section "STEP 7: SELinux Configuration" if [[ "$DISTRO" != "rhel" && "$DISTRO" != "centos" && "$DISTRO" != "rocky" && "$DISTRO" != "almalinux" && "$DISTRO" != "fedora" && "$DISTRO" != "ol" ]]; then print_info "SELinux configuration is only applicable to Red Hat-based systems" return 0 fi if ! command -v getenforce &> /dev/null; then print_warning "SELinux tools not found" return 0 fi print_info "Checking current SELinux status..." CURRENT_SELINUX=$(getenforce) print_info "Current SELinux status: $CURRENT_SELINUX" echo "" if [[ "$CURRENT_SELINUX" == "Disabled" ]]; then print_info "SELinux is already disabled" return 0 fi [[ "$CURRENT_SELINUX" == "Enforcing" ]] && SELINUX_WAS_ENFORCING=true if [ ! -f /etc/selinux/config ]; then print_warning "SELinux config file not found" return 0 fi print_info "Disabling SELinux..." ${SUDO_CMD} cp /etc/selinux/config /etc/selinux/config.backup.$(date +%Y%m%d_%H%M%S) ${SUDO_CMD} sed -i 's/=enforcing/=disabled/g; s/=permissive/=disabled/g' /etc/selinux/config echo "" print_info "Updated SELinux configuration:" echo -e "${CYAN}----------------------------------------${NC}" ${SUDO_CMD} grep "^SELINUX=" /etc/selinux/config echo -e "${CYAN}----------------------------------------${NC}" echo "" print_warning "SELinux disabled - REBOOT REQUIRED" SELINUX_DISABLED=true } #============================================================================== # FINAL SUMMARY #============================================================================== print_final_summary() { local summary_hostname summary_safe_hostname summary_ts local local_copy_target local_copy_cmd local_hash_cmd summary_hostname="$(hostname)" summary_safe_hostname="$(echo "$summary_hostname" | sed 's/[^A-Za-z0-9._-]/_/g')" summary_ts="$(date +%Y%m%d_%H%M%S)" local_copy_target="/home/aw/code/atvm/log/atvm_configuration_${summary_safe_hostname}_${summary_ts}.log" local_copy_cmd="scp root@:${LOG_FILE} ${local_copy_target}" local_hash_cmd="sha256sum ${local_copy_target}" local summary_output="" summary_output+=" " summary_output+="======================================== " summary_output+=" FINAL INSTALLATION SUMMARY " summary_output+="======================================== " summary_output+=" " summary_output+="*** System Information *** " summary_output+=" * Distribution: $DISTRO " summary_output+=" * Package Manager: $PM " summary_output+=" * Kernel: $(uname -r) " summary_output+=" * Hostname: $(hostname) " summary_output+=" " summary_output+="*** Actions Performed *** " summary_output+=" " if [[ "$HOST_IDENTITY_VALIDATED" == true ]]; then summary_output+="[OK] Step 0: Target Host Identity Validation " summary_output+=" * Expected IP matched: ${EXPECTED_IP} " summary_output+=" * Expected hostname matched: ${EXPECTED_HOSTNAME} " else summary_output+="[SKIP] Step 0: Target Host Identity Validation " fi summary_output+=" " if [[ "$REPO_FIX_COMPLETED" == true ]]; then summary_output+="[OK] Step 1: Repository Configuration " summary_output+=" * CD-ROM/DVD repository entries commented out " summary_output+=" * Package lists updated " summary_output+=" * Backup files created with timestamps " else summary_output+="[SKIP] Step 1: Repository Configuration " fi summary_output+=" " if [[ "$NETWORKMANAGER_COMPLETED" == true ]]; then summary_output+="[OK] Step 8: Static IP Configuration " summary_output+=" * Interface: ${INTERFACE:-unknown} " summary_output+=" * IP Address: 192.168.3.191/22 " summary_output+=" * Gateway: 192.168.0.1 " summary_output+=" * DNS: 8.8.8.8, 8.8.4.4 " summary_output+=" * Method: ${STATIC_IP_METHOD} " else summary_output+="[SKIP] Step 8: Static IP Configuration " summary_output+=" * No network manager found or interface not available " fi summary_output+=" " if [[ "$SUDO_INSTALLED" == true ]]; then summary_output+="[OK] sudo Installation " summary_output+=" * sudo package installed successfully " summary_output+=" " fi if [[ "$PACKAGE_INSTALL_COMPLETED" == true ]]; then summary_output+="[OK] Step 6: Package Management " if [ ${#MULTIPATH_REMOVED[@]} -gt 0 ]; then summary_output+=" * Removed multipath: ${MULTIPATH_REMOVED[*]} " fi if [ ${#ISCSI_REMOVED[@]} -gt 0 ]; then summary_output+=" * Removed iSCSI: ${ISCSI_REMOVED[*]} " fi summary_output+=" * Kernel headers installed: $(uname -r) " summary_output+=" * Installed ${#PACKAGES_INSTALLED[@]} packages " else summary_output+="[SKIP] Step 6: Package Management " fi summary_output+=" " if [[ "$DISTRO" == "ol" ]]; then if [[ "$ORACLE_KERNEL_STEP_COMPLETED" == true ]]; then summary_output+="[OK] Step 3: Oracle Kernel Default Configuration " summary_output+=" * Previous default: ${ORACLE_KERNEL_DEFAULT_BEFORE:-unknown} " summary_output+=" * Target non-UEK kernel: ${ORACLE_KERNEL_TARGET} " summary_output+=" * Active default: ${ORACLE_KERNEL_DEFAULT_AFTER:-unknown} " if [[ "$ORACLE_KERNEL_DEFAULT_CHANGED" == true ]]; then summary_output+=" * Default kernel was changed during this run " else summary_output+=" * Default kernel was already non-UEK " fi if [[ "$ORACLE_KERNEL_REBOOT_REQUIRED" == true ]]; then summary_output+=" * REBOOT REQUIRED to boot into non-UEK kernel " fi else summary_output+="[SKIP] Step 3: Oracle Kernel Default Configuration " summary_output+=" * Not applicable or non-UEK kernel not available " fi summary_output+=" " fi if [[ "$DISTRO" == "ubuntu" ]]; then if [[ "$ROOT_SSH_CONFIGURED" == true ]]; then summary_output+="[OK] Step 2: Ubuntu Root SSH Access Configuration " summary_output+=" * Root password set to workflow value " summary_output+=" * PermitRootLogin enabled " summary_output+=" * PasswordAuthentication enabled " summary_output+=" * Reconnect as root using the ATVM_TARGET_PASSWORD value for root-only workflow " else summary_output+="[SKIP] Step 2: Ubuntu Root SSH Access Configuration " summary_output+=" * Not applied " fi summary_output+=" " if [[ "$AUTO_UPGRADES_DISABLED" == true ]]; then summary_output+="[OK] Step 5: Ubuntu Auto Upgrade Configuration " summary_output+=" * Updated /etc/apt/apt.conf.d/20auto-upgrades " summary_output+=" * APT::Periodic::Update-Package-Lists set to 0 " summary_output+=" * APT::Periodic::Unattended-Upgrade set to 0 " else summary_output+="[SKIP] Step 5: Ubuntu Auto Upgrade Configuration " summary_output+=" * Not applied " fi summary_output+=" " fi if [[ "$DISTRO" == "rhel" || "$DISTRO" == "centos" || "$DISTRO" == "rocky" || "$DISTRO" == "almalinux" || "$DISTRO" == "fedora" || "$DISTRO" == "ol" ]]; then if [[ "$SELINUX_DISABLED" == true ]]; then summary_output+="[OK] Step 7: SELinux Configuration " [[ "$SELINUX_WAS_ENFORCING" == true ]] && summary_output+=" * Previous status: Enforcing " summary_output+=" * SELinux disabled in configuration " summary_output+=" * Backup: /etc/selinux/config.backup.* " summary_output+=" * REBOOT REQUIRED for changes to take effect " else summary_output+="[SKIP] Step 7: SELinux Configuration " summary_output+=" * Already disabled or not applicable " fi summary_output+=" " fi if is_selinux_distro; then if [[ "$SELINUX_DISABLED" == true ]]; then summary_output+="[OK] Step 9: SELinux Reboot And Verification " summary_output+=" * Automatic reboot will be triggered at end of script " summary_output+=" * Post-reboot verification service configured " else summary_output+="[SKIP] Step 9: SELinux Reboot And Verification " summary_output+=" * SELinux change not pending reboot " fi summary_output+=" " fi if setup_completed_without_errors; then summary_output+="[OK] Step 10: Final Power State " summary_output+=" * Client remains powered on for controller log collection " summary_output+=" * Required: power off from controller only after log copy + SHA256 verification succeeds " else summary_output+="[WARNING] Step 10: Final Power State " summary_output+=" * Client will remain powered on because errors were encountered " fi summary_output+=" " summary_output+="======================================== " if setup_completed_without_errors; then summary_output+=" SUCCESS: ATVM VM Setup Complete! " else summary_output+=" WARNING: Setup completed with issues " fi summary_output+="======================================== " if [[ "$SELINUX_DISABLED" == true ]] || [[ "$NETWORKMANAGER_COMPLETED" == true ]] || [[ "$ORACLE_KERNEL_REBOOT_REQUIRED" == true ]]; then summary_output+=" " summary_output+="======================================== " summary_output+=" REBOOT RECOMMENDED " summary_output+="======================================== " summary_output+=" " [[ "$SELINUX_DISABLED" == true ]] && summary_output+=" * SELinux changes require reboot " [[ "$NETWORKMANAGER_COMPLETED" == true ]] && summary_output+=" * Network changes may benefit from reboot " [[ "$ORACLE_KERNEL_REBOOT_REQUIRED" == true ]] && summary_output+=" * Oracle default kernel changed to non-UEK " summary_output+=" " summary_output+="To reboot: sudo reboot " summary_output+=" " fi summary_output+=" " summary_output+="Log saved to: $LOG_FILE " summary_output+="Note: this script runs on the client VM and cannot directly write files to controller filesystem paths. " summary_output+=" Controller-side required step (run from controller host): " summary_output+=" Preferred: /home/aw/code/atvm/run_atvm_setup_and_collect_log.sh --collect-after-complete " summary_output+=" If a prior run-and-collect session was interrupted, run --collect-after-complete only (do not rerun setup). " summary_output+=" (Wrapper checks REMOTE_IP_PRIMARY and REMOTE_IP_SECONDARY; defaults are 192.168.0.121 and 192.168.3.191) " summary_output+=" ${local_copy_cmd} " summary_output+=" Optional integrity check: " summary_output+=" ssh root@ 'sha256sum ${LOG_FILE}' " summary_output+=" ${local_hash_cmd} " summary_output+=" After hash match is confirmed on controller: " summary_output+=" Preferred: run_atvm_setup_and_collect_log.sh handles automatic power-off when no [ERROR] is present " summary_output+=" Manual fallback: ssh root@ 'shutdown -h now' " # Print to screen with colors echo "" echo -e "${CYAN}========================================${NC}" echo -e "${CYAN} FINAL INSTALLATION SUMMARY${NC}" echo -e "${CYAN}========================================${NC}" echo "" echo -e "${BLUE}*** System Information ***${NC}" echo " * Distribution: $DISTRO" echo " * Package Manager: $PM" echo " * Kernel: $(uname -r)" echo " * Hostname: $(hostname)" echo "" echo -e "${BLUE}*** Actions Performed ***${NC}" echo "" if [[ "$HOST_IDENTITY_VALIDATED" == true ]]; then echo -e "${GREEN}[OK] Step 0: Target Host Identity Validation${NC}" echo " * Expected IP matched: ${EXPECTED_IP}" echo " * Expected hostname matched: ${EXPECTED_HOSTNAME}" else echo -e "${YELLOW}[SKIP] Step 0: Target Host Identity Validation${NC}" fi echo "" if [[ "$REPO_FIX_COMPLETED" == true ]]; then echo -e "${GREEN}[OK] Step 1: Repository Configuration${NC}" echo " * CD-ROM/DVD repository entries commented out" echo " * Package lists updated" echo " * Backup files created with timestamps" else echo -e "${YELLOW}[SKIP] Step 1: Repository Configuration${NC}" fi echo "" if [[ "$NETWORKMANAGER_COMPLETED" == true ]]; then echo -e "${GREEN}[OK] Step 8: Static IP Configuration${NC}" echo " * Interface: ${INTERFACE:-unknown}" echo " * IP Address: 192.168.3.191/22" echo " * Gateway: 192.168.0.1" echo " * DNS: 8.8.8.8, 8.8.4.4" echo " * Method: ${STATIC_IP_METHOD}" else echo -e "${YELLOW}[SKIP] Step 8: Static IP Configuration${NC}" echo " * No network manager found or interface not available" fi echo "" if [[ "$SUDO_INSTALLED" == true ]]; then echo -e "${GREEN}[OK] sudo Installation${NC}" echo " * sudo package installed successfully" echo "" fi if [[ "$PACKAGE_INSTALL_COMPLETED" == true ]]; then echo -e "${GREEN}[OK] Step 6: Package Management${NC}" [ ${#MULTIPATH_REMOVED[@]} -gt 0 ] && echo " * Removed multipath: ${MULTIPATH_REMOVED[*]}" [ ${#ISCSI_REMOVED[@]} -gt 0 ] && echo " * Removed iSCSI: ${ISCSI_REMOVED[*]}" echo " * Kernel headers installed: $(uname -r)" echo " * Installed ${#PACKAGES_INSTALLED[@]} packages" else echo -e "${YELLOW}[SKIP] Step 6: Package Management${NC}" fi echo "" if [[ "$DISTRO" == "ol" ]]; then if [[ "$ORACLE_KERNEL_STEP_COMPLETED" == true ]]; then echo -e "${GREEN}[OK] Step 3: Oracle Kernel Default Configuration${NC}" echo " * Previous default: ${ORACLE_KERNEL_DEFAULT_BEFORE:-unknown}" echo " * Target non-UEK kernel: ${ORACLE_KERNEL_TARGET}" echo " * Active default: ${ORACLE_KERNEL_DEFAULT_AFTER:-unknown}" if [[ "$ORACLE_KERNEL_DEFAULT_CHANGED" == true ]]; then echo " * Default kernel was changed during this run" else echo " * Default kernel was already non-UEK" fi [[ "$ORACLE_KERNEL_REBOOT_REQUIRED" == true ]] && echo -e " ${YELLOW}* REBOOT REQUIRED to boot into non-UEK kernel${NC}" else echo -e "${YELLOW}[SKIP] Step 3: Oracle Kernel Default Configuration${NC}" echo " * Not applicable or non-UEK kernel not available" fi echo "" fi if [[ "$DISTRO" == "ubuntu" ]]; then if [[ "$ROOT_SSH_CONFIGURED" == true ]]; then echo -e "${GREEN}[OK] Step 2: Ubuntu Root SSH Access Configuration${NC}" echo " * Root password set to workflow value" echo " * PermitRootLogin enabled" echo " * PasswordAuthentication enabled" echo " * Reconnect as root using the ATVM_TARGET_PASSWORD value for root-only workflow" else echo -e "${YELLOW}[SKIP] Step 2: Ubuntu Root SSH Access Configuration${NC}" echo " * Not applied" fi echo "" if [[ "$AUTO_UPGRADES_DISABLED" == true ]]; then echo -e "${GREEN}[OK] Step 5: Ubuntu Auto Upgrade Configuration${NC}" echo " * Updated /etc/apt/apt.conf.d/20auto-upgrades" echo " * APT::Periodic::Update-Package-Lists \"0\"" echo " * APT::Periodic::Unattended-Upgrade \"0\"" else echo -e "${YELLOW}[SKIP] Step 5: Ubuntu Auto Upgrade Configuration${NC}" echo " * Not applied" fi echo "" fi if [[ "$DISTRO" == "rhel" || "$DISTRO" == "centos" || "$DISTRO" == "rocky" || "$DISTRO" == "almalinux" || "$DISTRO" == "fedora" || "$DISTRO" == "ol" ]]; then if [[ "$SELINUX_DISABLED" == true ]]; then echo -e "${GREEN}[OK] Step 7: SELinux Configuration${NC}" [[ "$SELINUX_WAS_ENFORCING" == true ]] && echo " * Previous status: Enforcing" echo " * SELinux disabled in configuration" echo " * Backup: /etc/selinux/config.backup.*" echo -e " ${YELLOW}* REBOOT REQUIRED for changes to take effect${NC}" else echo -e "${YELLOW}[SKIP] Step 7: SELinux Configuration${NC}" echo " * Already disabled or not applicable" fi echo "" fi if is_selinux_distro; then if [[ "$SELINUX_DISABLED" == true ]]; then echo -e "${GREEN}[OK] Step 9: SELinux Reboot And Verification${NC}" echo " * Automatic reboot will be triggered at end of script" echo " * Post-reboot verification service configured" else echo -e "${YELLOW}[SKIP] Step 9: SELinux Reboot And Verification${NC}" echo " * SELinux change not pending reboot" fi echo "" fi if setup_completed_without_errors; then echo -e "${GREEN}[OK] Step 10: Final Power State${NC}" echo " * Client remains powered on for controller log collection" echo " * Required: power off from controller only after log copy + SHA256 verification succeeds" else echo -e "${YELLOW}[WARNING] Step 10: Final Power State${NC}" echo " * Client will remain powered on because errors were encountered" fi echo "" echo -e "${CYAN}========================================${NC}" if setup_completed_without_errors; then echo -e "${GREEN} SUCCESS: ATVM VM Setup Complete!${NC}" else echo -e "${YELLOW} WARNING: Setup completed with issues${NC}" fi echo -e "${CYAN}========================================${NC}" if [[ "$SELINUX_DISABLED" == true ]] || [[ "$NETWORKMANAGER_COMPLETED" == true ]] || [[ "$ORACLE_KERNEL_REBOOT_REQUIRED" == true ]]; then echo "" echo -e "${YELLOW}========================================${NC}" echo -e "${YELLOW} REBOOT RECOMMENDED${NC}" echo -e "${YELLOW}========================================${NC}" echo "" [[ "$SELINUX_DISABLED" == true ]] && echo -e "${YELLOW} * SELinux changes require reboot${NC}" [[ "$NETWORKMANAGER_COMPLETED" == true ]] && echo -e "${YELLOW} * Network changes may benefit from reboot${NC}" [[ "$ORACLE_KERNEL_REBOOT_REQUIRED" == true ]] && echo -e "${YELLOW} * Oracle default kernel changed to non-UEK${NC}" echo "" echo -e "${YELLOW}To reboot: sudo reboot${NC}" echo "" fi echo "" echo -e "${GREEN}Log saved to: $LOG_FILE${NC}" echo -e "${YELLOW}Note:${NC} this script runs on the client VM and cannot directly write to controller filesystem paths." echo -e "${GREEN}Create local identical copy on controller:${NC}" echo " Preferred: /home/aw/code/atvm/run_atvm_setup_and_collect_log.sh --collect-after-complete" echo " If a prior run-and-collect session was interrupted, run --collect-after-complete only (do not rerun setup)." echo " (Wrapper checks REMOTE_IP_PRIMARY and REMOTE_IP_SECONDARY; defaults are 192.168.0.121 and 192.168.3.191)" echo " ${local_copy_cmd}" echo -e "${GREEN}Optional integrity check:${NC}" echo " ssh root@ 'sha256sum ${LOG_FILE}'" echo " ${local_hash_cmd}" echo "" echo -e "${GREEN}After hash match is confirmed on controller:${NC}" echo " Preferred: run_atvm_setup_and_collect_log.sh handles automatic power-off when no [ERROR] is present" echo " Manual fallback: ssh root@ 'shutdown -h now'" # Write to log file (without colors) echo "$summary_output" >> "$LOG_FILE" } #============================================================================== # MAIN #============================================================================== main() { ATVM_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [[ $EUID -eq 0 ]]; then LOG_FILE="/root/atvm_setup_script.log" else LOG_FILE="${ATVM_SCRIPT_DIR}/atvm_setup_script.log" fi echo "ATVM VM Setup Script - $(date)" > "$LOG_FILE" echo "========================================" >> "$LOG_FILE" echo -e "${CYAN}========================================${NC}" echo -e "${CYAN} ATVM VM Setup Script${NC}" echo -e "${CYAN}========================================${NC}" echo "" parse_args "$@" validate_target_host_identity # Initial privilege check - verify we can run (as root or with sudo) check_sudo # Step 1: Fix repositories FIRST (before installing anything including sudo) fix_repositories # Step 2: Ubuntu-specific root SSH/password workflow configure_ubuntu_root_ssh_access # Step 3: Install sudo if needed (after repos are fixed) if ! command -v sudo &> /dev/null; then print_section "Installing sudo" install_sudo_if_needed # Verify sudo is now available if ! command -v sudo &> /dev/null && [[ $EUID -ne 0 ]]; then print_error "CRITICAL: sudo installation failed" exit 1 fi print_info "sudo is now available - proceeding with setup" echo "" fi # Step 4: On Oracle Linux, set default boot kernel to non-UEK if available configure_oracle_non_uek_kernel # Step 5: Disable Ubuntu unattended auto-upgrades disable_ubuntu_auto_upgrades # Step 6: Install packages run_package_installation # Step 7: Disable SELinux disable_selinux # Step 8: Configure static IP at the very end (can interrupt SSH session) configure_static_ip # Final summary print_final_summary # Step 9: On SELinux distros, reboot and verify SELinux runtime state after boot reboot_and_verify_selinux_if_needed # Step 10: Keep client on so controller can collect/verify logs before manual power-off poweroff_client_if_successful } main "$@"