Skip to main content

Switch Over Instructions

Server Setup Script - copy and paste into terminal to execute the script

Script with options
#!/bin/bash

# --- Single, Robust Command to Mount, Copy, Execute, and Unmount ---
#
# This command is designed to be safely copied and pasted into any server terminal.
# It handles all the necessary steps to run the main setup script from your fileserver.
# It will prompt you to select which mode to run the main script in.
#
# Password for the mount command is included. Ensure this is run in a secure environment.

# --- Configuration ---
MOUNT_POINT="/mnt/fileserver"
SERVER_PATH="//172.16.21.16/fileserver2"
SCRIPT_SOURCE_PATH="${MOUNT_POINT}/General/IT FILES/script3.sh"
SCRIPT_DEST_PATH="/tmp/script3.sh"
MOUNT_USER="Cipher.m21"
MOUNT_PASS=")\1y;634'NJ%i+"

# --- Logic ---

# Ensure the mount point is unmounted on script exit (success or failure)
trap "echo 'Unmounting fileserver...'; sudo umount '${MOUNT_POINT}' &>/dev/null || true" EXIT

# Check if already mounted. If not, create directory and mount.
if ! grep -qs "${MOUNT_POINT}" /proc/mounts; then
    echo "Mounting fileserver..."
    sudo mkdir -p "${MOUNT_POINT}"
    sudo mount -t cifs "${SERVER_PATH}" "${MOUNT_POINT}" -o username="${MOUNT_USER}",password="${MOUNT_PASS}"
fi

echo "Copying script (overwriting if exists)..."
sudo cp "${SCRIPT_SOURCE_PATH}" "${SCRIPT_DEST_PATH}"

echo "Making script executable..."
sudo chmod +x "${SCRIPT_DEST_PATH}"

# --- Interactive Mode Selection ---
echo ""
echo "Please choose which setup to run:"
echo "  1) Full Setup (default)"
echo "  2) Development Stack Only (--dev)"
echo "  3) Security Hardening Only (--security)"
echo "  4) Shell & UX Setup Only (--shell)"
echo "  5) System Updates Only (--updates)"
read -rp "Enter your choice [1-5]: " run_choice

EXECUTION_FLAG="--full" # Default value

case "$run_choice" in
    2) EXECUTION_FLAG="--dev" ;;
    3) EXECUTION_FLAG="--security" ;;
    4) EXECUTION_FLAG="--shell" ;;
    5) EXECUTION_FLAG="--updates" ;;
    1) EXECUTION_FLAG="--full" ;;
    *) # Default to full for any other input
       echo "Invalid choice or no choice entered. Defaulting to Full Setup."
       EXECUTION_FLAG="--full"
       ;;
esac

echo "Executing the main setup script with flag: ${EXECUTION_FLAG}..."
sudo "${SCRIPT_DEST_PATH}" "${EXECUTION_FLAG}"

# The trap will handle the unmount automatically.
echo "Script execution finished. Unmounting is handled automatically."
Simple script
sudo mkdir -p /mnt/fileserver && \
sudo mount -t cifs //172.16.21.16/fileserver2 /mnt/fileserver -o username="Cipher.m21",password=")\1y;634'NJ%i+" && \
cp /mnt/fileserver/General/IT\ FILES//script3.sh /tmp/ && \
sudo chmod +x /tmp/script3.sh && \
sudo /tmp/script3.sh --full && \
sudo umount /mnt/fileserver

Server Setup Script
#!/usr/bin/env bash
##########################
#
####
#######################
#
# Comprehensive Domain Join & Configuration Script
#
# Version: 6.5 (Phoenix - The Final Cut)
# Last Modified: 2025-07-31
#
# Features
# [CRITICAL FIX] Zsh theme switching and plugin enabling is now fully robust.
# [CRITICAL FIX] Reboot prompt no longer hangs.
# [FIX] Enhanced Nano with persistent status bar and comprehensive syntax highlighting from a dedicated repository.
# [FIX] Vi/Vim syntax highlighting is now guaranteed by installing a full vim package.
# [ENH] Added a pre-configured "Powerline" theme for Starship, showing date, time, and hostname.
# [ENH] Added Powerlevel10k as a Zsh theme option with automatic installation and configuration wizard setup.
# [CRITICAL FIX] Proxy variables are now exported immediately, fixing all subsequent download failures.
# [CRITICAL FIX] Ctrl+C cancellation is now robust and reliably skips optional sections without exiting the script.
#
###############################
#####################################
#---
# CONFIGURATION
# Adjust these variables for your environment!
#---
DOMAIN_FQDN="m21.gov.local"
DOMAIN_NETBIOS="M21" # NetBIOS name of your domain
DC_DNS_IP="172.16.21.161" # Your Domain Controller's IP (for DNS & domain ops)
NTP_SERVER="172.16.121.9" # Your dedicated NTP server IP
FILE_SERVER_IP="172.16.21.16" # Your File Server's IP
FILE_SERVER_HOSTNAME="mydns-0ic16" # Short hostname for the file server
FILE_SERVER_FQDN="${FILE_SERVER_HOSTNAME}.${DOMAIN_FQDN}" # FQDN for the file server

HTTP_PROXY_URL="http://172.40.4.14:8080/" # Set to "" if no proxy
NO_PROXY_INITIAL="127.0.0.1,localhost,localhost.localdomain" # Base no_proxy entries
NO_PROXY_CUSTOM="172.30.0.0/20,172.26.21.0/24,172.16.121.0/24,10.21.0.0/21" # Your custom NO PROXY CIDRS
INSECURE_REGISTRIES='"172.16.121.119:5000", "docker-repo.mydns.gov.tt"' # Comma-separated, quoted Docker insecure registries

TIMEZONE="America/Port_of_Spain" # Your desired timezone
AD_SUDO_GROUP_RAW_NAME="ICT Staff SG" # AD Group for Sudoers (Raw name, spaces are okay here. Script will escape.)
#---
# INITIALIZE SCRIPT
#---
# Color Definitions
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Global flag for reboot
REBOOT_REQUIRED_FLAG=false
# Exit on error for most commands, but we will handle some manually.
set -o pipefail

LOG_FILE="/var/log/setup-domain-$(date +%Y-%m-%d_%H-%M-%S).log"
# Log to file, but keep stderr on the console to see errors immediately.
exec > >(tee -a "$LOG_FILE") 2>&1

echo -e "${GREEN}=== Script started at $(date --iso-8601=seconds) by $(whoami) ===${NC}"
echo -e "${GREEN}=== Logging to ${LOG_FILE} ===${NC}"
#---
# PRELIMINARY CHECKS & GLOBAL VARIABLES
#---
[[ $EUID -ne 0 ]] && { echo -e "${RED}ERROR: This script must be run as root or with sudo.${NC}" >&2; exit 1; }
PKG_MANAGER=""
if command -v dnf &>/dev/null; then PKG_MANAGER="dnf";
elif command -v yum &>/dev/null; then PKG_MANAGER="yum";
elif command -v apt-get &>/dev/null; then PKG_MANAGER="apt";
else echo -e "${RED}ERROR: Neither DNF, YUM, nor APT package manager found. Exiting.${NC}" >&2; exit 1; fi
source /etc/os-release
OS_ID_LOWER=$(echo "$ID" | tr '[:upper:]' '[:lower:]')
OS_VER="${VERSION_ID%%.*}"

HOSTNAME_VAR=$(hostname -f)

#---
# SCRIPT FUNCTIONS
#
log_step() { echo -e "\n${GREEN}--- [STEP $1 on ${PURPLE}${HOSTNAME_VAR}${NC}] $2 ---${NC}"; }

#
# SECTION 0: CREDENTIAL GATHERING
#
gather_credentials() {
    log_step "0/X" "Gathering Credentials (Not Logged)"
    
    local creds_ok=false
    while ! $creds_ok; do
        ( # Start subshell for cancellable read
            trap 'echo -e "\n${RED}Credential entry cancelled. Exiting script.${NC}"; exit 1;' INT
            echo -e "\n${CYAN}--- Domain Credentials (will not be logged) ---${NC}"
            read -rp "$(echo -e "${CYAN}Enter the SERVICE part of the hostname (e.g., mydns-it-c12-1): ${NC}")" SERVICE_NAME_PART < /dev/tty
            if [[ ! "$SERVICE_NAME_PART" =~ ^[a-zA-Z0-9-]+$ ]]; then
                echo -e "${RED}ERROR: Invalid service name part.${NC}" >&2; exit 1;
            fi
            read -rp "$(echo -e "${CYAN}Enter your AD username SUFFIX (the part after 'ent_'): ${NC}")" AD_USER_SUFFIX < /dev/tty
            if [ -z "$AD_USER_SUFFIX" ]; then echo -e "${RED}ERROR: AD username suffix cannot be empty.${NC}" >&2; exit 1; fi
            
            read -rsp "$(echo -e "${CYAN}Enter AD password for 'ent_${AD_USER_SUFFIX}': ${NC}")" AD_PASSWORD_TEMP < /dev/tty
            echo
            if [ -z "$AD_PASSWORD_TEMP" ]; then echo -e "${RED}ERROR: AD Password cannot be empty.${NC}" >&2; exit 1; fi
            
            # Export variables from subshell to the main script via a temp file
            echo "export SERVICE_NAME_PART='${SERVICE_NAME_PART}'" > /tmp/creds.sh
            echo "export AD_USER_SUFFIX='${AD_USER_SUFFIX}'" >> /tmp/creds.sh
            echo "export AD_PASSWORD='${AD_PASSWORD_TEMP}'" >> /tmp/creds.sh
        )
        
        if [ $? -ne 0 ]; then exit 1; fi
        
        source /tmp/creds.sh
        rm /tmp/creds.sh
        creds_ok=true
    done

    TARGET_HOSTNAME_FQDN="${SERVICE_NAME_PART}.${DOMAIN_FQDN}"
    TARGET_HOSTNAME_FQDN_LC=$(echo "$TARGET_HOSTNAME_FQDN" | tr '[:upper:]' '[:lower:]')
    AD_USER_FOR_JOIN="ent_${AD_USER_SUFFIX}"
    echo -e "${BLUE}INFO:${NC} Using full AD username: ${PURPLE}${AD_USER_FOR_JOIN}${NC}"
    
    NO_PROXY_FULL="${NO_PROXY_INITIAL},${DOMAIN_FQDN,,},.${DOMAIN_FQDN,,},${DC_DNS_IP},${NTP_SERVER},${FILE_SERVER_IP}"
    if [[ -n "$NO_PROXY_CUSTOM" ]]; then NO_PROXY_FULL="${NO_PROXY_FULL},${NO_PROXY_CUSTOM}"; fi
    NO_PROXY_FULL=$(echo "$NO_PROXY_FULL" | tr ',' '\n' | sort -u | tr '\n' ',' | sed 's/,$//')
}
#
#SECTION 1: CORE SYSTEM & NETWORK FUNCTIONS
#
change_hostname() {
    log_step "1/X" "Setting Hostname"
    echo -e "${BLUE}INFO:${NC} Setting hostname to ${PURPLE}${TARGET_HOSTNAME_FQDN_LC}${NC}"
    hostnamectl set-hostname "$TARGET_HOSTNAME_FQDN_LC"
    echo -e "${GREEN}SUCCESS:${NC} Hostname set to: ${PURPLE}$(hostnamectl hostname)${NC}"
}
configure_proxy() {
    log_step "2/X" "Configuring System-Wide Proxy"
    if [ -z "$HTTP_PROXY_URL" ]; then
        echo -e "${BLUE}INFO:${NC} HTTP_PROXY_URL is not set. Skipping proxy configuration."
        return
    fi
    
    echo -e "${BLUE}INFO:${NC} Applying proxy for current script session..."
    export http_proxy="${HTTP_PROXY_URL}"
    export https_proxy="${HTTP_PROXY_URL}"
    export ftp_proxy="${HTTP_PROXY_URL}"
    export no_proxy="${NO_PROXY_FULL}"
    export HTTP_PROXY="${HTTP_PROXY_URL}"
    export HTTPS_PROXY="${HTTP_PROXY_URL}"
    export FTP_PROXY="${HTTP_PROXY_URL}"
    export NO_PROXY="${NO_PROXY_FULL}"

    echo -e "${BLUE}INFO:${NC} Configuring proxy for future interactive shells (/etc/profile.d/proxy.sh)..."
    cat > /etc/profile.d/proxy.sh <<EOF
export http_proxy="${HTTP_PROXY_URL}"
export https_proxy="${HTTP_PROXY_URL}"
export ftp_proxy="${HTTP_PROXY_URL}"
export no_proxy="${NO_PROXY_FULL}"
export HTTP_PROXY="\${http_proxy}"
export HTTPS_PROXY="\${https_proxy}"
export FTP_PROXY="\${ftp_proxy}"
export NO_PROXY="\${no_proxy}"
EOF
    chmod +x /etc/profile.d/proxy.sh

    echo -e "${BLUE}INFO:${NC} Configuring system-wide environment file (/etc/environment)..."
    sed -i '/^http_proxy=/d;/^https_proxy=/d;/^ftp_proxy=/d;/^no_proxy=/d' /etc/environment
    sed -i '/^HTTP_PROXY=/d;/^HTTPS_PROXY=/d;/^FTP_PROXY=/d;/^NO_PROXY=/d' /etc/environment
    
    echo "http_proxy=\"${HTTP_PROXY_URL}\"" >> /etc/environment
    echo "https_proxy=\"${HTTP_PROXY_URL}\"" >> /etc/environment
    echo "ftp_proxy=\"${HTTP_PROXY_URL}\"" >> /etc/environment
    echo "no_proxy=\"${NO_PROXY_FULL}\"" >> /etc/environment
    echo "HTTP_PROXY=\"${HTTP_PROXY_URL}\"" >> /etc/environment
    echo "HTTPS_PROXY=\"${HTTP_PROXY_URL}\"" >> /etc/environment
    echo "FTP_PROXY=\"${HTTP_PROXY_URL}\"" >> /etc/environment
    echo "NO_PROXY=\"${NO_PROXY_FULL}\"" >> /etc/environment
    
    echo -e "${BLUE}INFO:${NC} Configuring package manager proxy..."
    case "$PKG_MANAGER" in
        dnf|yum)
            if ! grep -q "proxy=" /etc/dnf/dnf.conf; then
                echo "proxy=${HTTP_PROXY_URL}" >> /etc/dnf/dnf.conf
            fi
            ;;
        apt)
            cat > /etc/apt/apt.conf.d/80proxy <<EOF
Acquire::http::proxy "${HTTP_PROXY_URL}";
Acquire::https::proxy "${HTTP_PROXY_URL}";
Acquire::ftp::proxy "${HTTP_PROXY_URL}";
EOF
            ;;
    esac
    echo -e "${GREEN}SUCCESS:${NC} System-wide proxy configured."
}
configure_dns_and_hosts() {
    log_step "3/X" "Configuring DNS and NetworkManager"
    echo -e "${BLUE}INFO:${NC} Configuring /etc/hosts file..."
    sed -i "/${DOMAIN_FQDN}/d" /etc/hosts
    cat >> /etc/hosts <<EOF
# AD Domain Configuration
${DC_DNS_IP}   ${DOMAIN_FQDN}
${FILE_SERVER_IP}  ${FILE_SERVER_FQDN} ${FILE_SERVER_HOSTNAME}
EOF
    echo -e "${BLUE}INFO:${NC} Configuring DNS via NetworkManager..."
    local conn
    conn=$(nmcli -t -f NAME,DEVICE connection show --active | grep -v "lo$" | head -n1 | cut -d':' -f1)
    if [ -z "$conn" ]; then
        echo -e "${RED}ERROR:${NC} Could not find an active network connection to configure." >&2
        return 1
    fi
    echo -e "${BLUE}INFO:${NC} Modifying connection: ${PURPLE}${conn}${NC}"
    nmcli connection modify "$conn" ipv4.dns "$DC_DNS_IP"
    nmcli connection modify "$conn" ipv4.ignore-auto-dns yes
    nmcli connection up "$conn"
    echo -e "${GREEN}SUCCESS:${NC} DNS configured to ${DC_DNS_IP} and /etc/hosts updated."
}
check_connectivity() {
    log_step "4/X" "Checking Network Connectivity"
    local has_error=0
    echo -e "${BLUE}INFO:${NC} Pinging Domain Controller (${DC_DNS_IP})..."
    if ! ping -c 3 "$DC_DNS_IP"; then
        echo -e "${RED}ERROR:${NC} Domain Controller is not reachable." >&2; has_error=1
    fi
    echo -e "${BLUE}INFO:${NC} Checking DNS resolution for ${DOMAIN_FQDN}..."
    if ! getent hosts "$DOMAIN_FQDN"; then
        echo -e "${RED}ERROR:${NC} Could not resolve domain FQDN." >&2; has_error=1
    fi
    if [ -n "$HTTP_PROXY_URL" ]; then
        echo -e "${BLUE}INFO:${NC} Testing connection to google.com via proxy..."
        if ! curl -s --head --connect-timeout 5 http://www.google.com | head -n 1 | grep "200 OK" > /dev/null; then
            echo -e "${YELLOW}WARNING:${NC} Could not connect to the internet via proxy. External repos may fail."
        fi
    fi
    if [ $has_error -eq 0 ]; then
        echo -e "${GREEN}SUCCESS:${NC} All connectivity checks passed."
    else
        echo -e "${RED}ERROR:${NC} One or more connectivity checks failed. Please review the logs." >&2; exit 1
    fi
}
install_packages() {
    log_step "5/X" "Installing Core & Utility Packages"
    local common_pkgs="nano curl wget htop btop net-tools git zip unzip tar tmux chrony open-vm-tools traceroute ncdu policycoreutils-python-utils logrotate tree bash-completion bat jq fontconfig util-linux-user"
    local pkgs_to_install
    if [[ "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "yum" ]]; then
        common_pkgs+=" bind-utils dnf-utils vim-enhanced"
        echo -e "${BLUE}INFO:${NC} Ensuring core DNF plugins are installed..."
        $PKG_MANAGER -y install dnf-plugins-core
        echo -e "${BLUE}INFO:${NC} Enabling CRB/PowerTools repository..."
        if [[ "$OS_VER" -ge 9 ]]; then
            dnf config-manager --set-enabled crb -y
        else
            dnf config-manager --set-enabled powertools -y || dnf config-manager --set-enabled PowerTools -y
        fi
        echo -e "${BLUE}INFO:${NC} Installing EPEL repository..."
        if ! $PKG_MANAGER -y install epel-release; then
            echo -e "${RED}ERROR: Failed to install EPEL repository. Cannot continue.${NC}" >&2; exit 1;
        fi
        local dnf_base_pkgs="realmd sssd oddjob oddjob-mkhomedir adcli samba-common-tools authselect"
        pkgs_to_install="${dnf_base_pkgs} ${common_pkgs}"
    elif [[ "$PKG_MANAGER" == "apt" ]]; then
        common_pkgs+=" dnsutils debian-goodies vim"
        [[ "$OS_ID_LOWER" == "ubuntu" ]] && common_pkgs=${common_pkgs/bat/batcat}
        local apt_base_pkgs="realmd sssd sssd-tools libnss-sss libpam-sss adcli samba-common-bin oddjob oddjob-mkhomedir packagekit apt-transport-https ca-certificates software-properties-common gnupg lsb-release"
        pkgs_to_install="${apt_base_pkgs} ${common_pkgs}"
        echo -e "${BLUE}INFO:${NC} Updating package lists for APT..."
        apt-get update -qq
    fi
    echo -e "${BLUE}INFO:${NC} Installing main packages..."
    if ! $PKG_MANAGER -y install ${pkgs_to_install}; then
        echo -e "${RED}ERROR: Package installation failed. This is often due to network, proxy, or repository issues.${NC}" >&2; exit 1;
    fi
    if command -v batcat &>/dev/null && ! command -v bat &>/dev/null; then
        ln -sf /usr/bin/batcat /usr/local/bin/bat
    fi
    echo -e "${GREEN}SUCCESS:${NC} Core packages installed."
}
configure_time() {
    log_step "6/X" "Configuring System Time (NTP & Timezone)"
    echo -e "${BLUE}INFO:${NC} Setting timezone to ${TIMEZONE}..."
    timedatectl set-timezone "$TIMEZONE"
    echo -e "${BLUE}INFO:${NC} Configuring chrony to use NTP server ${NTP_SERVER}..."
    sed -i '/^pool/d' /etc/chrony.conf
    sed -i '/^server/d' /etc/chrony.conf
    echo "server ${NTP_SERVER} iburst" >> /etc/chrony.conf
    echo -e "${BLUE}INFO:${NC} Restarting and enabling chronyd service..."
    systemctl restart chronyd
    systemctl enable chronyd
    timedatectl set-ntp true
    echo -e "${GREEN}SUCCESS:${NC} System time configured."
}
#
#SECTION 2: DOMAIN & AUTHENTICATION FUNCTIONS
#
join_ad_domain() {
    log_step "7/X" "Joining Active Directory Domain"
    ( # Start subshell for cancellation
        trap 'echo -e "\n${YELLOW}Operation cancelled. Skipping Domain Join...${NC}"; exit 0;' INT
        
        if realm list | grep -q "$DOMAIN_FQDN"; then
            read -rp "$(echo -e "${YELLOW}WARNING:${NC} Server is already joined to ${DOMAIN_FQDN}. Action? ([S]kip, [R]e-join): ${NC}")" choice < /dev/tty
            case "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" in
                r|re-join)
                    echo -e "${BLUE}INFO:${NC} Leaving the domain first..."
                    if ! realm leave; then
                        echo -e "${RED}ERROR:${NC} Failed to leave the domain. Please check logs. Aborting re-join." >&2
                        exit 1
                    fi
                    echo -e "${GREEN}SUCCESS:${NC} Left the domain."
                    ;;
                *)
                    echo -e "${BLUE}INFO:${NC} Skipping domain join step."
                    exit 0
                    ;;
            esac
        fi

        echo -e "${BLUE}INFO:${NC} Attempting to join domain ${DOMAIN_FQDN} as user ${AD_USER_FOR_JOIN}..."
        if ! echo -n "$AD_PASSWORD" | realm join --user="$AD_USER_FOR_JOIN" "$DOMAIN_FQDN"; then
            echo -e "${RED}ERROR:${NC} Failed to join the Active Directory domain." >&2
            echo -e "${YELLOW}Check username, password, and connectivity to the DC.${NC}" >&2
            exit 1
        fi
        echo -e "${GREEN}SUCCESS:${NC} Successfully joined the domain."
    )
}
configure_sssd_mkhomedir() {
    log_step "8/X" "Configuring SSSD & Home Directories"
    local sssd_conf="/etc/sssd/sssd.conf"
    if [ ! -f "$sssd_conf" ]; then
        echo -e "${RED}ERROR:${NC} SSSD configuration file not found at ${sssd_conf}" >&2; return 1;
    fi
    echo -e "${BLUE}INFO:${NC} Modifying ${sssd_conf}..."
    sed -i '/^use_fully_qualified_names/d' "$sssd_conf"
    sed -i "/\[domain\/${DOMAIN_FQDN,,}\]/a use_fully_qualified_names = False" "$sssd_conf"
    sed -i '/^fallback_homedir/d' "$sssd_conf"
    sed -i "/\[domain\/${DOMAIN_FQDN,,}\]/a fallback_homedir = /home/%u" "$sssd_conf"
    echo -e "${BLUE}INFO:${NC} Enabling automatic home directory creation..."
    authselect enable-feature with-mkhomedir
    systemctl restart sssd oddjobd
    systemctl enable oddjobd
    echo -e "${GREEN}SUCCESS:${NC} SSSD and home directory creation configured."
}
configure_sudoers() {
    log_step "9/X" "Configuring Sudoers for AD Group"
    local escaped_group_name
    escaped_group_name=$(echo "$AD_SUDO_GROUP_RAW_NAME" | sed 's/ /\\ /g')
    local sudoer_file="/etc/sudoers.d/90-ad-admins"
    echo -e "${BLUE}INFO:${NC} Granting sudo rights to AD group '${AD_SUDO_GROUP_RAW_NAME}'..."
    echo "\"%${escaped_group_name}\" ALL=(ALL) ALL" > "$sudoer_file"
    chmod 440 "$sudoer_file"
    echo -e "${GREEN}SUCCESS:${NC} Sudoers configured. Rule added to ${sudoer_file}."
}
#
#SECTION 3: SECURITY HARDENING FUNCTIONS
#
optimize_sshd() {
    log_step "10/X" "Optimizing SSH Daemon for Faster Logins"
    local sshd_config="/etc/ssh/sshd_config"
    echo -e "${BLUE}INFO:${NC} Setting 'UseDNS no' in ${sshd_config}..."
    if grep -q "^#\?UseDNS" "$sshd_config"; then
        sed -i 's/^#\?UseDNS.*/UseDNS no/' "$sshd_config"
    else
        echo "UseDNS no" >> "$sshd_config"
    fi
    systemctl restart sshd
    echo -e "${GREEN}SUCCESS:${NC} SSHD optimized for faster logins."
}
install_fail2ban() {
    log_step "11/X" "Installing Fail2ban (Optional, Ctrl+C to skip)"
    ( # Start subshell for cancellation
        trap 'echo -e "\n${YELLOW}Operation cancelled. Skipping Fail2ban...${NC}"; exit 0;' INT
        
        if [ -f /etc/fail2ban/jail.local ]; then
            read -rp "$(echo -e "${YELLOW}WARNING:${NC} Fail2ban configuration already exists. Action? ([S]kip, [O]verwrite): ${NC}")" choice < /dev/tty
            if [[ "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" != "o" ]]; then
                echo -e "${BLUE}INFO:${NC} Skipping Fail2ban setup."; exit 0;
            fi
        else
            read -rp "$(echo -e "${CYAN}Install and configure Fail2ban for SSH protection? [y/N]: ${NC}")" choice < /dev/tty
            if [[ "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" != "y" ]]; then
                echo -e "${BLUE}INFO:${NC} Skipping Fail2ban installation."; exit 0;
            fi
        fi

        echo -e "${BLUE}INFO:${NC} Installing Fail2ban..."
        $PKG_MANAGER -y install fail2ban
        echo -e "${BLUE}INFO:${NC} Creating local jail configuration for SSHD..."
        cat > /etc/fail2ban/jail.local <<EOF
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
EOF
        systemctl enable --now fail2ban
        echo -e "${GREEN}SUCCESS:${NC} Fail2ban installed and enabled for SSHD."
    )
}
#
#SECTION 4: SHELL & USER EXPERIENCE FUNCTIONS
#
install_nano_syntax() {
    (
        trap 'echo -e "\n${YELLOW}Operation cancelled. Skipping extra Nano syntax...${NC}"; exit 0;' INT
        echo -e "${BLUE}INFO:${NC} Installing enhanced syntax highlighting for Nano..."
        local nano_syntax_dir="/tmp/nanorc"
        if git clone https://github.com/scopatz/nanorc.git "$nano_syntax_dir"; then
            sudo cp -r ${nano_syntax_dir}/*.nanorc /usr/share/nano/
            rm -rf "$nano_syntax_dir"
            echo -e "${GREEN}SUCCESS:${NC} Enhanced Nano syntax installed."
        else
            echo -e "${RED}ERROR:${NC} Failed to download enhanced Nano syntax files."
        fi
    )
}
configure_nano() {
    log_step "12/X" "Configuring Nano Editor"
    echo -e "${BLUE}INFO:${NC} Applying system-wide Nano configuration..."
    cat > /etc/nanorc <<EOF
## Nano Editor Default Configuration
set linenumbers
set softwrap
set tabsize 4
set casesensitive
set constantshow # Always show line/col info

## Include all standard syntax definitions
include "/usr/share/nano/*.nanorc"
EOF
    install_nano_syntax
    echo -e "${GREEN}SUCCESS:${NC} Nano configured with defaults and syntax highlighting."
}
configure_vim() {
    log_step "12.1/X" "Configuring Vim/Vi"
    echo -e "${BLUE}INFO:${NC} Applying system-wide Vim configuration..."
    cat > /etc/vimrc <<EOF
" System-wide .vimrc file
syntax on
set background=dark
set number
set ruler
set showcmd
set incsearch
set wildmenu
EOF
    echo -e "${GREEN}SUCCESS:${NC} Vim configured with syntax highlighting."
}
enhance_bash() {
    log_step "13/X" "Enhancing Bash Experience (Optional, Ctrl+C to skip)"
    ( # Start subshell for cancellation
        trap 'echo -e "\n${YELLOW}Operation cancelled. Skipping Bash enhancements...${NC}"; exit 0;' INT

        read -rp "$(echo -e "${CYAN}Enhance the Bash shell with a better prompt and aliases? [y/N]: ${NC}")" choice < /dev/tty
        if [[ "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" != "y" ]]; then
            echo -e "${BLUE}INFO:${NC} Skipping Bash enhancements."; exit 0;
        fi
        echo -e "${BLUE}INFO:${NC} Creating /etc/profile.d/enhanced_bash.sh..."
        cat > /etc/profile.d/enhanced_bash.sh <<'EOF'
# Custom Bash prompt
PS1='\[\e[32m\]\u@\h \[\e[33m\]\w\[\e[0m\]\n\$ '
# Useful Aliases
alias ls='ls --color=auto'
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
alias grep='grep --color=auto'
alias ..='cd ..'
EOF
        echo -e "${GREEN}SUCCESS:${NC} Bash enhancements will be applied on next login."
    )
}
setup_tmux() {
    log_step "14/X" "Setting up Automated Tmux Environment (Optional, Ctrl+C to skip)"
    ( # Start subshell for cancellation
        trap 'echo -e "\n${YELLOW}Operation cancelled. Skipping Tmux setup...${NC}"; exit 0;' INT

        read -rp "$(echo -e "${CYAN}Set up a default Tmux configuration? [y/N]: ${NC}")" choice < /dev/tty
        if [[ "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" != "y" ]]; then
            echo -e "${BLUE}INFO:${NC} Skipping Tmux setup."; exit 0;
        fi
        echo -e "${BLUE}INFO:${NC} Creating system-wide /etc/tmux.conf..."
        cat > /etc/tmux.conf <<'EOF'
# Set prefix to Ctrl-a
set -g prefix C-a
unbind C-b
bind C-a send-prefix
# Enable mouse mode
set -g mouse on
# Improve status bar
set -g status-bg black
set -g status-fg white
set -g status-left '#[fg=green]#H'
set -g status-right '#[fg=yellow]%Y-%m-%d %H:%M'
EOF
        echo -e "${GREEN}SUCCESS:${NC} Default Tmux configuration created."
    )
}
setup_motd() {
    log_step "15/X" "Setting Up Dynamic MOTD (Optional, Ctrl+C to skip)"
    ( # Start subshell for cancellation
        trap 'echo -e "\n${YELLOW}Operation cancelled. Skipping MOTD setup...${NC}"; exit 0;' INT
        
        if [ -f /etc/profile.d/99-custom-motd.sh ]; then
            read -rp "$(echo -e "${YELLOW}WARNING:${NC} Custom MOTD script already exists. Action? ([S]kip, [O]verwrite): ${NC}")" choice < /dev/tty
            if [[ "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" != "o" ]]; then
                echo -e "${BLUE}INFO:${NC} Skipping MOTD setup."; exit 0;
            fi
        else
            read -rp "$(echo -e "${CYAN}Setup a dynamic MOTD (Message of the Day)? [y/N]: ${NC}")" choice < /dev/tty
            if [[ "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" != "y" ]]; then
                echo -e "${BLUE}INFO:${NC} Skipping MOTD setup."; exit 0;
            fi
        fi
        
        echo -e "${BLUE}INFO:${NC} Creating a dynamic message of the day..."
        chmod -x /etc/update-motd.d/* &>/dev/null || true
        sed -i '/session\s\+optional\s\+pam_motd.so/s/^/#/' /etc/pam.d/sshd 2>/dev/null || true
        cat > /etc/profile.d/99-custom-motd.sh <<'EOF'
# This script runs for interactive shells to display a dynamic MOTD.
if [[ $- == *i* ]] && [[ "${SHLVL:-1}" -le 1 ]]; then
    RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; BLUE='\033[0;34m';
    PURPLE='\033[0;35m'; CYAN='\033[0;36m'; NC='\033[0m';
    echo -e "\nWelcome to ${GREEN}$(hostname -f)${NC}"
    echo -e "System time is: ${CYAN}$(date --iso-8601=seconds)${NC}\n"
    # Display OS Info
    if [ -f /etc/os-release ]; then
        OS_INFO=$(grep PRETTY_NAME /etc/os-release | cut -d'"' -f2)
        echo -e "${PURPLE}System Information${NC}"
        echo -e "  OS: ${YELLOW}${OS_INFO}${NC}"
        echo -e "  Uptime: $(uptime -p | sed 's/up //')\n"
    fi
    # Display Network Information
    echo -e "${PURPLE}Network Information${NC}"
    ip -4 addr | grep -oP '(?<=inet\s)\d+(\.\d+){3}/\d+\s.*\s\K\w+$' | while read -r dev;
    do
        ip_addr=$(ip -4 addr show dev "$dev" | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
        if [[ -n "$ip_addr" ]]; then echo -e "  Interface ${GREEN}$dev${NC}: ${CYAN}$ip_addr${NC}"; fi
    done || echo -e "  ${RED}No active IPv4 interfaces found.${NC}"
    echo
    # Display System Usage
    echo -e "${PURPLE}System Usage${NC}"
    df -h / | awk '$NR==2 {print "  Disk (/): " $2 " total, " $3 " used (" $5 " full), " $4 " free"}'
    free -h | awk '/^Mem:/ {print "  Memory:   " $2 " total, " $3 " used, " $7 " available"}'
    echo -e "  CPU Load: $(uptime | awk -F'load average:' '{ print $2}' | sed 's/ //g')\n"
    # Display Installed Software Versions
    echo -e "${PURPLE}Installed Software${NC}"
    declare -A progs=(
        ["Docker"]="docker --version" ["Podman"]="podman --version"
        ["Nginx"]="nginx -v" ["Apache"]="httpd -v" ["PHP"]="php -v"
        ["Node"]="node -v" ["NPM"]="npm -v" ["Go"]="go version"
        ["Java"]="java -version" ["MySQL"]="mysql --version" ["PostgreSQL"]="psql --version"
    )
    output=""
    for name in "${!progs[@]}"; do
        if command -v ${progs[$name]%% *} &>/dev/null; then
            version=$(${progs[$name]} 2>&1 | grep -oP '(\d+\.){1,}\d+' | head -n1)
            [[ -n "$version" ]] && output+="  ${GREEN}${name}${NC}:${CYAN}${version}${NC}"
        fi
    done
    echo -e "${output:-  None detected}\n"
fi
EOF
        chmod +x /etc/profile.d/99-custom-motd.sh
        echo -e "${GREEN}SUCCESS:${NC} Dynamic MOTD script created."
    )
}
install_nerd_fonts() {
    log_step "16.1/X" "Installing Nerd Fonts (Sub-step)"
    ( # Start subshell for cancellation
        trap 'echo -e "\n${YELLOW}Operation cancelled. Skipping Nerd Fonts...${NC}"; exit 124;' INT

        echo -e "${BLUE}INFO:${NC} Checking for Nerd Fonts..."
        local font_dir="/usr/local/share/fonts/FiraCodeNerdFont"
        if [ -d "$font_dir" ]; then
            echo -e "${BLUE}INFO:${NC} Nerd Font directory already exists. Skipping download."
            exit 0
        fi
        
        read -rp "$(echo -e "${CYAN}Install FiraCode Nerd Font for Zsh themes? [y/N]: ${NC}")" choice < /dev/tty
        if [[ "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" != "y" ]]; then
            echo -e "${BLUE}INFO:${NC} Skipping Nerd Font installation."; exit 0;
        fi
        mkdir -p "$font_dir"
        local tmp_zip="/tmp/FiraCode.zip"
        
        local retries=3; local count=0; local success=false
        until [ $count -ge $retries ]
        do
            echo -e "${BLUE}INFO:${NC} Attempting to download FiraCode Nerd Font (attempt $((count+1))/${retries})..."
            ping -c 1 google.com &>/dev/null || true 
            sleep 1
            curl --connect-timeout 20 -L "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.2.1/FiraCode.zip" -o "$tmp_zip"
            if [ $? -eq 0 ]; then success=true; break; fi
            count=$((count+1))
            echo -e "${YELLOW}WARNING:${NC} Download failed. Retrying in 5 seconds..."
            sleep 5
        done

        if ! $success; then
            echo -e "${RED}ERROR:${NC} Failed to download Nerd Fonts after $retries attempts." >&2; rm -f "$tmp_zip"; exit 1;
        fi

        unzip -o "$tmp_zip" -d "$font_dir"; rm -f "$tmp_zip"
        echo -e "${BLUE}INFO:${NC} Rebuilding font cache..."; fc-cache -fv &>/dev/null
        echo -e "${GREEN}SUCCESS:${NC} Nerd Fonts installed."
    )
    return $?
}
configure_starship() {
    local user=$1
    local home_dir
    home_dir=$(eval echo ~$user)
    local config_dir="${home_dir}/.config"
    local starship_config="${config_dir}/starship.toml"

    echo -e "${BLUE}INFO:${NC} Creating Starship 'Powerline' config for ${user}..."
    sudo -u "$user" mkdir -p "$config_dir"
    sudo -u "$user" tee "$starship_config" > /dev/null <<'EOF'
# Starship "Powerline" configuration
# Shows: [USER@HOST] [DATE TIME] [DIRECTORY] [GIT] [CMD_DURATION]
# >>>

# A minimal left prompt
format = """$username$hostname$time$directory$git_branch$cmd_duration$character"""

# Move the directory to the second line
# format = """$username$hostname$time$directory$git_branch$cmd_duration$fill$character"""

[username]
style_user = "yellow bold"
style_root = "red bold"
format = "[$user]($style_user)@"
show_always = true

[hostname]
style = "green bold"
format = "[$hostname]($style) "
ssh_only = false
disabled = false

[time]
disabled = false
format = '[\[$time\]]($style) '
style = "blue bold"
time_format = "%Y-%m-%d %H:%M:%S"

[directory]
style = "cyan bold"
format = "[$path]($style) "
truncation_length = 4

[git_branch]
style = "bold purple"
format = "[$branch]($style) "

[cmd_duration]
min_time = 500
style = "bold italic yellow"
format = "[$duration]($style) "

[character]
success_symbol = "[>](bold green)"
error_symbol = "[x](bold red)"
EOF
}
install_zsh_omz() {
    log_step "16/X" "Installing Zsh & Oh My Zsh (Optional, Ctrl+C to skip)"
    ( # Start subshell for cancellation
        trap 'echo -e "\n${YELLOW}Operation cancelled. Skipping Zsh setup...${NC}"; exit 124;' INT

        local choice
        if command -v zsh &>/dev/null; then
            read -rp "$(echo -e "${CYAN}Zsh is already installed. Action? ([S]kip, [R]econfigure): ${NC}")" choice < /dev/tty
            if [[ "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" == "s" ]]; then
                echo -e "${BLUE}INFO:${NC} Skipping Zsh setup."; exit 0;
            fi
        else
            read -rp "$(echo -e "${CYAN}Install Zsh and Oh My Zsh? [y/N]: ${NC}")" choice < /dev/tty
            if [[ "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" != "y" ]]; then
                echo -e "${BLUE}INFO:${NC} Skipping Zsh installation."; exit 0;
            fi
        fi
        $PKG_MANAGER -y install zsh git
        
        install_nerd_fonts
        if [ $? -ne 0 ]; then
            echo -e "${YELLOW}WARNING:${NC} Nerd font installation failed or was skipped. Zsh themes may not render correctly."
        fi

        local users_to_configure=()
        users_to_configure+=("root")
        if [[ -n "${SUDO_USER:-}" ]] && [[ "$SUDO_USER" != "root" ]]; then
            users_to_configure+=("$SUDO_USER")
        fi
        local prompt_choice
        read -rp "$(echo -e "${CYAN}Which Zsh prompt? ([1] Oh My Zsh (rkj-repos), [2] Starship, [3] Powerlevel10k): ${NC}")" prompt_choice < /dev/tty
        for user in "${users_to_configure[@]}"; do
            echo -e "${BLUE}INFO:${NC} Configuring Zsh for user ${PURPLE}${user}${NC}..."
            local home_dir; home_dir=$(eval echo ~$user)
            local zsh_dir="${home_dir}/.oh-my-zsh"
            
            if [ ! -d "$zsh_dir" ]; then
                local installer_sh="/tmp/omz_install.sh"
                local retries=3; local count=0; local success=false
                echo -e "${BLUE}INFO:${NC} Downloading Oh My Zsh installer..."
                until [ $count -ge $retries ]; do
                    curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh -o "$installer_sh"
                    if [ $? -eq 0 ]; then success=true; break; fi
                    count=$((count+1)); echo -e "${YELLOW}WARNING:${NC} Download failed. Retrying..."; sleep 3
                done
                
                if $success; then
                    echo -e "${BLUE}INFO:${NC} Running Oh My Zsh installer for ${user}..."
                    sudo -u "$user" sh "$installer_sh" --unattended
                    rm "$installer_sh"
                else
                    echo -e "${RED}ERROR:${NC} Failed to download Oh My Zsh installer for ${user}." >&2; continue
                fi
            fi

            local custom_plugins_dir="${zsh_dir}/custom/plugins"
            if [ ! -d "${custom_plugins_dir}/zsh-autosuggestions" ]; then
                sudo -u "$user" git clone https://github.com/zsh-users/zsh-autosuggestions "$custom_plugins_dir/zsh-autosuggestions"
            fi
            if [ ! -d "${custom_plugins_dir}/zsh-syntax-highlighting" ]; then
                sudo -u "$user" git clone https://github.com/zsh-users/zsh-syntax-highlighting.git "$custom_plugins_dir/zsh-syntax-highlighting"
            fi
            local zshrc_file="${home_dir}/.zshrc"
            
            # FIX: Robustly set plugins
            sed -i 's/^plugins=(.*)$/plugins=(git docker npm nvm zsh-autosuggestions zsh-syntax-highlighting)/' "$zshrc_file"
            
            # FIX: Clean up old theme settings before applying a new one
            sed -i '/^# Init Starship Prompt/d' "$zshrc_file"
            sed -i '/eval "$(starship init zsh)"/d' "$zshrc_file"
            
            case "$prompt_choice" in
                2) # Starship
                    if ! command -v starship &>/dev/null; then
                        local installer_sh="/tmp/starship_install.sh"
                        echo -e "${BLUE}INFO:${NC} Downloading Starship installer..."
                        if curl -sS https://starship.rs/install.sh -o "$installer_sh"; then
                            sh "$installer_sh" -y
                            rm "$installer_sh"
                        else
                             echo -e "${RED}ERROR:${NC} Failed to download starship installer." >&2
                        fi
                    fi
                    echo -e '\n# Init Starship Prompt\neval "$(starship init zsh)"' >> "$zshrc_file"
                    configure_starship "$user"
                    ;;
                3) # Powerlevel10k
                    local p10k_dir="${zsh_dir}/custom/themes/powerlevel10k"
                    if [ ! -d "$p10k_dir" ]; then
                        echo -e "${BLUE}INFO:${NC} Cloning Powerlevel10k theme..."
                        sudo -u "$user" git clone --depth=1 https://github.com/romkatv/powerlevel10k.git "$p10k_dir"
                    fi
                    echo -e "${BLUE}INFO:${NC} Setting Powerlevel10k theme in .zshrc..."
                    sed -i 's|^ZSH_THEME=.*|ZSH_THEME="powerlevel10k/powerlevel10k"|' "$zshrc_file"
                    if ! grep -q 'POWERLEVEL9K_DISABLE_CONFIGURATION_WIZARD=true' "$zshrc_file"; then
                         echo -e '\n# To customize prompt, run `p10k configure` or edit ~/.p10k.zsh.\n[[ ! -f ~/.p10k.zsh ]] && p10k configure' >> "$zshrc_file"
                    fi
                    ;;
                *) # Default to rkj-repos
                    echo -e "${BLUE}INFO:${NC} Setting ZSH_THEME to rkj-repos..."
                    sed -i 's|^ZSH_THEME=.*|ZSH_THEME="rkj-repos"|' "$zshrc_file"
                    ;;
            esac

            if ! grep -q 'setopt EXTENDED_HISTORY' "$zshrc_file"; then
                echo -e '\n# Custom settings by setup script\nsetopt EXTENDED_HISTORY\nHIST_STAMPS="yyyy-mm-dd"\n' >> "$zshrc_file"
            fi
            
            if ! grep -q "alias ls='ls --color=auto'" "$zshrc_file"; then
                echo -e '\n# Color Aliases\nalias ls="ls --color=auto"\nalias grep="grep --color=auto"' >> "$zshrc_file"
            fi
            
            local current_shell; current_shell=$(getent passwd "$user" | cut -d: -f7)
            if [[ "$current_shell" != "$(which zsh)" ]]; then
                echo -e "${BLUE}INFO:${NC} Changing shell for user ${user} to Zsh..."
                chsh -s "$(which zsh)" "$user"
                echo -e "${GREEN}SUCCESS:${NC} Shell changed."
            else
                echo -e "${BLUE}INFO:${NC} Shell for user ${user} is already zsh."
            fi
        done
    )
}
#
#SECTION 5: APPLICATION STACK FUNCTIONS
#
install_dev_stack() {
    log_step "18/X" "Installing Development Stack (Optional, Ctrl+C to skip)"
    ( # Start subshell for cancellation
        trap 'echo -e "\n${YELLOW}Operation cancelled. Skipping Dev Stack...${NC}"; exit 0;' INT

        read -rp "$(echo -e "${CYAN}Install a development stack (Nginx, PHP, Node.js)? [y/N]: ${NC}")" choice < /dev/tty
        if [[ "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" != "y" ]]; then
            echo -e "${BLUE}INFO:${NC} Skipping dev stack installation."; exit 0;
        fi
        echo -e "${BLUE}INFO:${NC} Installing Nginx..."
        $PKG_MANAGER -y install nginx
        systemctl enable nginx; systemctl start nginx
        echo -e "${BLUE}INFO:${NC} Installing Node.js (LTS)..."
        curl -fsSL https://rpm.nodesource.com/setup_lts.x | bash -
        $PKG_MANAGER -y install nodejs
        echo -e "${BLUE}INFO:${NC} Installing PHP..."
        if [[ "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "yum" ]]; then
            $PKG_MANAGER -y install http://rpms.remirepo.net/enterprise/remi-release-8.rpm
            $PKG_MANAGER -y module reset php; $PKG_MANAGER -y module install php:remi-8.1
            $PKG_MANAGER -y install php-cli php-fpm php-mysqlnd php-json php-gd php-mbstring
        else # APT
            add-apt-repository ppa:ondrej/php -y; $PKG_MANAGER update
            $PKG_MANAGER -y install php8.1-cli php8.1-fpm php8.1-mysql php8.1-json php8.1-gd php8.1-mbstring
        fi
        echo -e "${GREEN}SUCCESS:${NC} Development stack installed."
    )
}
install_cockpit() {
    log_step "19/X" "Installing Cockpit Web Console (Optional, Ctrl+C to skip)"
    ( # Start subshell for cancellation
        trap 'echo -e "\n${YELLOW}Operation cancelled. Skipping Cockpit...${NC}"; exit 0;' INT

        read -rp "$(echo -e "${CYAN}Install the Cockpit web administration console? [y/N]: ${NC}")" choice < /dev/tty
        if [[ "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" != "y" ]]; then
            echo -e "${BLUE}INFO:${NC} Skipping Cockpit installation."; exit 0;
        fi
        echo -e "${BLUE}INFO:${NC} Installing Cockpit..."
        $PKG_MANAGER -y install cockpit cockpit-storaged cockpit-networkmanager
        echo -e "${BLUE}INFO:${NC} Starting and enabling Cockpit socket..."
        systemctl enable --now cockpit.socket
        echo -e "${GREEN}SUCCESS:${NC} Cockpit is installed. Access it at https://$(hostname -f):9090"
    )
}
install_container_runtime() {
    log_step "20/X" "Installing Container Runtime (Optional, Ctrl+C to skip)"
    ( # Start subshell for cancellation
        trap 'echo -e "\n${YELLOW}Operation cancelled. Skipping Container Runtime...${NC}"; exit 0;' INT

        read -rp "$(echo -e "${CYAN}Install a container runtime? ([D]ocker, [P]odman, [N]one): ${NC}")" choice < /dev/tty
        case "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" in
            d|docker)
                echo -e "${BLUE}INFO:${NC} Installing Docker..."
                if [[ "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "yum" ]]; then
                    $PKG_MANAGER config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
                    $PKG_MANAGER -y install docker-ce docker-ce-cli containerd.io
                else # APT
                    install -m 0755 -d /etc/apt/keyrings
                    curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
                    chmod a+r /etc/apt/keyrings/docker.asc
                    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" > /etc/apt/sources.list.d/docker.list
                    $PKG_MANAGER update
                    $PKG_MANAGER -y install docker-ce docker-ce-cli containerd.io
                fi
                systemctl enable --now docker
                if [[ -n "${SUDO_USER:-}" ]]; then
                    echo -e "${BLUE}INFO:${NC} Adding user ${SUDO_USER} to the docker group..."
                    usermod -aG docker "$SUDO_USER"
                fi
                echo -e "${GREEN}SUCCESS:${NC} Docker installed."
                ;;
            p|podman)
                echo -e "${BLUE}INFO:${NC} Installing Podman..."
                $PKG_MANAGER -y install podman
                echo -e "${GREEN}SUCCESS:${NC} Podman installed."
                ;;
            *)
                echo -e "${BLUE}INFO:${NC} Skipping container runtime installation.";;
        esac
    )
}
#
#SECTION 6: UTILITY & FINALIZATION FUNCTIONS
#
setup_logrotate() {
    log_step "21/X" "Setting up Logrotate (Optional, Ctrl+C to skip)"
    ( # Start subshell for cancellation
        trap 'echo -e "\n${YELLOW}Operation cancelled. Skipping Logrotate setup...${NC}"; exit 0;' INT

        read -rp "$(echo -e "${CYAN}Configure log rotation for this script's log files? [y/N]: ${NC}")" choice < /dev/tty
        if [[ "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" != "y" ]]; then
            echo -e "${BLUE}INFO:${NC} Skipping logrotate setup."; exit 0;
        fi
        echo -e "${BLUE}INFO:${NC} Creating /etc/logrotate.d/setup-domain..."
        cat > /etc/logrotate.d/setup-domain <<EOF
/var/log/setup-domain-*.log {
    monthly
    rotate 4
    compress
    delaycompress
    missingok
    notifempty
    create 640 root root
}
EOF
        echo -e "${GREEN}SUCCESS:${NC} Logrotate configured."
    )
}
final_summary() {
    log_step "22/X" "Final Setup Summary"
    echo -e "${GREEN}================== SUMMARY ==================${NC}"
    echo -e "  Hostname: ${PURPLE}$(hostname -f)${NC}"
    echo -e "  IP Address: ${CYAN}$(hostname -I | awk '{print $1}')${NC}"
    echo -e "  Timezone: ${CYAN}${TIMEZONE}${NC}"
    echo -e "  Domain Membership: ${PURPLE}${DOMAIN_FQDN}${NC}"
    realm list | grep "configured: yes" &>/dev/null
    if [ $? -eq 0 ]; then
        echo -e "  Domain Join Status: ${GREEN}Success${NC}"
        echo -e "  Login with AD users as: ${CYAN}username${NC}"
        echo -e "  Sudo enabled for group: ${PURPLE}${AD_SUDO_GROUP_RAW_NAME}${NC}"
    else
        echo -e "  Domain Join Status: ${RED}Failed or Not Performed${NC}"
    fi
    echo -e "  Log File: ${YELLOW}${LOG_FILE}${NC}"
    echo -e "${GREEN}=============================================${NC}"
}
system_updates_interactive() {
    log_step "23/X" "System Updates (Optional, Ctrl+C to skip)"
    ( # Start subshell for cancellation
        trap 'echo -e "\n${YELLOW}Operation cancelled. Skipping System Updates...${NC}"; exit 0;' INT

        read -rp "$(echo -e "${CYAN}Check for and apply all available system updates? [y/N]: ${NC}")" choice < /dev/tty
        if [[ "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" != "y" ]]; then
            echo -e "${BLUE}INFO:${NC} Skipping system updates."; exit 0;
        fi
        echo -e "${BLUE}INFO:${NC} Checking for updates..."
        $PKG_MANAGER -y update
        echo -e "${GREEN}SUCCESS:${NC} System is up-to-date."

        echo -e "${BLUE}INFO:${NC} Checking if a reboot is required..."
        if [[ "$PKG_MANAGER" == "dnf" || "$PKG_MANAGER" == "yum" ]]; then
            if needs-restarting -r &>/dev/null; then
                # Using an external file to communicate back to the main script
                echo "true" > /tmp/reboot_required.flag
            fi
        elif [[ "$PKG_MANAGER" == "apt" ]]; then
            if [ -f /var/run/reboot-required ]; then
                echo "true" > /tmp/reboot_required.flag
            fi
        fi

        if [ -f /tmp/reboot_required.flag ]; then
            echo -e "\n${YELLOW}####################################################################"
            echo -e "# WARNING: System updates have been installed that require a reboot."
            echo -e "####################################################################${NC}"
        else
            echo -e "${GREEN}INFO:${NC} No reboot is required at this time."
        fi
    )
}
cleanup() {
    unset AD_PASSWORD
    rm -f /tmp/reboot_required.flag
    echo -e "${BLUE}INFO:${NC} Sensitive variables cleared from memory."
}
usage() {
    echo -e "${CYAN}Usage: $0 [OPTION]${NC}"
    echo "  --full      Run the complete end-to-end installation and configuration."
    echo "  --dev       Install the development stack (PHP, Node, Nginx, etc.)."
    echo "  --security  Apply security hardening (Fail2ban, SSH optimization)."
    echo "  --shell     Configure user experience (Bash, Zsh, Tmux, MOTD)."
    echo "  --updates   Check for and apply system updates."
    echo "  --help      Display this help message."
    echo
    echo -e "${YELLOW}If no option is provided, the script will run the full installation interactively.${NC}"
}
#---
# MODULAR EXECUTION RUNNERS
#
run_full_install() {
    echo -e "${PURPLE}Starting Full System Setup...${NC}"
    gather_credentials
    # Core System & Network (Not cancellable - these are critical)
    change_hostname
    configure_proxy
    configure_dns_and_hosts
    check_connectivity
    install_packages
    configure_time
    # Domain & Auth (Not cancellable - these are critical)
    join_ad_domain
    configure_sssd_mkhomedir
    configure_sudoers
    # Security (Optional sections are now cancellable)
    optimize_sshd
    install_fail2ban
    #Shell & UX
    configure_nano
    configure_vim
    enhance_bash
    setup_tmux
    setup_motd
    install_zsh_omz
    # App Stacks
    install_dev_stack
    install_cockpit
    install_container_runtime
    # Utilities & Finalization
    setup_logrotate
    system_updates_interactive
    final_summary
}
run_dev_stack() {
    echo -e "${PURPLE}Starting Development Stack Setup...${NC}"
    install_packages
    install_dev_stack
    final_summary
}
run_security_hardening() {
    echo -e "${PURPLE}Starting Security Hardening...${NC}"
    install_packages
    optimize_sshd
    install_fail2ban
    final_summary
}
run_shell_ux() {
    echo -e "${PURPLE}Starting Shell & UX Setup...${NC}"
    install_packages
    configure_nano
    configure_vim
    enhance_bash
    setup_tmux
    setup_motd
    install_zsh_omz
    final_summary
}
#---
# MAIN EXECUTION FLOW
#---
main() {
    trap cleanup EXIT
    if [ $# -eq 0 ]; then
        run_full_install
    else
        case "$1" in
            --full) run_full_install ;;
            --dev) run_dev_stack ;;
            --security) run_security_hardening ;;
            --shell) run_shell_ux ;;
            --updates) system_updates_interactive ;;
            --help) usage ;;
            *)
                echo -e "${RED}Error: Invalid option '$1'${NC}" >&2
                usage
                exit 1
                ;;
        esac
    fi
    
    echo -e "\n${GREEN}========= Script finished at $(date --iso-8601=seconds) =========${NC}"
    
    if [ -f /tmp/reboot_required.flag ]; then
        REBOOT_REQUIRED_FLAG=true
    fi

    if $REBOOT_REQUIRED_FLAG; then
        read -rp "$(echo -e "${YELLOW}A reboot is required to apply updates. Reboot now? [y/N]: ${NC}")" choice < /dev/tty
        if [[ "$(echo "$choice" | tr '[:upper:]' '[:lower:]')" == "y" ]]; then
            echo -e "${RED}Rebooting now...${NC}"
            reboot
        else
            echo -e "${YELLOW}Please reboot the server manually to apply all changes.${NC}"
        fi
    else
         echo -e "${GREEN}Script complete. No reboot required.${NC}"
    fi
}
# Only run main if the script is executed directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

 

master_script.sh - v43
#!/usr/bin/env bash
#
# MASTER INFRASTRUCTURE SETUP v43
# Enhancements: Modularized Docker, Web, Database, and Terminal Tools
#
###############################################################################
# 1. CONFIGURATION
###############################################################################
DOMAIN_FQDN="m21.gov.local"
DOMAIN_ALT="m21.gov.tt"
DOMAIN_SHORT="M21"
DC_DNS_IP="172.16.21.161"
NTP_SERVER="172.16.121.9"
TARGET_TIMEZONE="America/Port_of_Spain"

# File Server Info
FILE_SERVER_IP="172.16.21.16"
FILE_SERVER_NAME="fileserver2"

# Proxy
PROXY_URL="http://172.40.4.14:8080"
NO_PROXY_LIST="127.0.0.1,localhost,localhost.localdomain,${DOMAIN_FQDN},${DOMAIN_ALT},.${DOMAIN_FQDN},.${DOMAIN_ALT},${DC_DNS_IP},172.30.0.0/20,172.26.21.0/24,10.21.0.0/21,172.16.121.0/24"

# Docker Settings
INSECURE_REGISTRIES='"172.16.121.119:5000", "docker-repo.msya.gov.tt"'

# AD Access Control
AD_SUDO_GROUP="ICT Staff SG M21"
ALLOWED_LOGIN_GROUP="ICT Staff SG M21"

# Share Credentials
SHARE_PATH="//172.16.21.16/fileserver2"
SHARE_USER="Cipher.m21"
SHARE_PASS=")\ly; 634'NJ%i+"
CERT_SOURCE_PATH="/General/IT FILES/prx/Gortt_certificate_V4.cer"
TARGET_CERT_NAME="GORTT_Root_Exp2029"

# Failsafe User
LOCAL_USER="pcsupport"
LOCAL_PASS="ProIT321*"

# LVM Settings
HOME_TARGET_SIZE="8G"

# Versions
PHP_VERSION="8.3"
JAVA_VERSION="21"
MARIADB_VERSION="10.11"

###############################################################################
# 2. HELPER FUNCTIONS
###############################################################################
set -e
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; BLUE='\033[0;34m'; NC='\033[0m'

log() { echo -e "${BLUE}[$(date +'%H:%M:%S')] [INFO]${NC} $1"; }
step() { echo -e "\n${YELLOW}[$(date +'%H:%M:%S')] >>> $1${NC}"; }
success() { echo -e "${GREEN}[$(date +'%H:%M:%S')] [OK]${NC} $1"; }
error() { echo -e "${RED}[$(date +'%H:%M:%S')] [ERROR]${NC} $1"; }

run_retry() {
    local n=1; local max=3; local delay=2
    while true; do
        "$@" && return 0
        if [[ $n -lt $max ]]; then
            ((n++)); log "Command failed. Retrying ($n/$max)..."; sleep $delay
        else
            return 1
        fi
    done
}

###############################################################################
# 3. PRE-FLIGHT CHECKS
###############################################################################
detect_and_fix_os() {
    source /etc/os-release
    OS_ID=$(echo "$ID" | tr '[:upper:]' '[:lower:]')
    VERSION_MAJOR=$(echo "$VERSION_ID" | cut -d. -f1)
    
    if timeout 10s systemctl is-active --quiet packagekit.service 2>/dev/null; then
        timeout 15s systemctl stop packagekit.service || true
    fi
    
    if [[ "$OS_ID" == "centos" && "$VERSION_MAJOR" == "7" ]]; then
        PKG="yum"
        if grep -q "linux/rhel" /etc/yum.repos.d/docker-ce.repo 2>/dev/null; then rm -f /etc/yum.repos.d/docker-ce.repo; fi
        if [ ! -f /etc/yum.repos.d/CentOS-Base.repo.backup ]; then
            cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2>/dev/null || true
            run_retry curl -o /etc/yum.repos.d/CentOS-Base.repo https://el7.repo.almalinux.org/centos/CentOS-Base.repo
        fi
    elif [[ "$OS_ID" =~ (rhel|centos|almalinux|rocky) ]]; then PKG="dnf"
    elif [[ "$OS_ID" =~ (ubuntu|debian|zorin) ]]; then PKG="apt-get"
    else error "Unsupported OS: $OS_ID"; exit 1; fi
}

###############################################################################
# 4. CORE MODULES
###############################################################################

mod_proxy() {
    step "Configuring System Proxy"
    cat > /etc/profile.d/proxy.sh <<EOF
export http_proxy="${PROXY_URL}"
export https_proxy="${PROXY_URL}"
export ftp_proxy="${PROXY_URL}"
export no_proxy="${NO_PROXY_LIST}"
export HTTP_PROXY="${PROXY_URL}"
export HTTPS_PROXY="${PROXY_URL}"
export FTP_PROXY="${PROXY_URL}"
export NO_PROXY="${NO_PROXY_LIST}"
EOF
    source /etc/profile.d/proxy.sh

    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        CONF_FILE="/etc/dnf/dnf.conf"
        [[ ! -f "$CONF_FILE" ]] && CONF_FILE="/etc/yum.conf"
        grep -q "proxy=" "$CONF_FILE" 2>/dev/null || echo "proxy=${PROXY_URL}" >> "$CONF_FILE"
        if ! grep -q "minrate" "$CONF_FILE" 2>/dev/null; then
            echo -e "timeout=60\nretries=10\nminrate=1" >> "$CONF_FILE"
        fi
    else
        echo -e "Acquire::http::Proxy \"${PROXY_URL}\";\nAcquire::https::Proxy \"${PROXY_URL}\";" > /etc/apt/apt.conf.d/80proxy
    fi
}

mod_clock_fix() {
    step "Synchronizing System Clock"
    timedatectl set-timezone "$TARGET_TIMEZONE" || true
    timedatectl set-ntp true || true
    if systemctl list-unit-files | grep -q systemd-timesyncd; then
        timeout 30s systemctl restart systemd-timesyncd || true
    fi
}

mod_certs() {
    step "Installing Certificates"
    MNT="/mnt/share_certs_tmp"
    mkdir -p "$MNT"
    
    if ! command -v mount.cifs &>/dev/null; then
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get update -qq >/dev/null 2>&1 || true; run_retry apt-get install -y cifs-utils
        else run_retry $PKG install -y cifs-utils; fi
    fi

    if mountpoint -q "$MNT"; then umount -l "$MNT"; fi
    
    if timeout 30s mount -t cifs "$SHARE_PATH" "$MNT" -o username="$SHARE_USER",password="$SHARE_PASS",vers=3.0; then
        SOURCE_FULL="$MNT$CERT_SOURCE_PATH"
        TEMP_PEM="/tmp/${TARGET_CERT_NAME}_staging.pem"
        
        if [[ -f "$SOURCE_FULL" ]]; then
            if ! openssl x509 -inform der -in "$SOURCE_FULL" -out "$TEMP_PEM" 2>/dev/null; then cp "$SOURCE_FULL" "$TEMP_PEM"; fi
            if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
                cp "$TEMP_PEM" "/etc/pki/ca-trust/source/anchors/${TARGET_CERT_NAME}.pem"
                [[ "$VERSION_MAJOR" -lt 9 ]] && update-ca-trust force-enable 2>/dev/null || true
                update-ca-trust extract
            else
                cp "$TEMP_PEM" "/usr/local/share/ca-certificates/${TARGET_CERT_NAME}.crt"
                update-ca-certificates
            fi
        fi
        timeout 15s umount "$MNT" || true
    fi
    rmdir "$MNT" 2>/dev/null || true
}

mod_base_repos() {
    step "Configuring Base OS Repositories"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if ! rpm -q epel-release >/dev/null 2>&1; then run_retry $PKG install -y epel-release; fi
        if [[ "$PKG" == "dnf" ]]; then
            if ! dnf repolist enabled 2>/dev/null | grep -E "crb|powertools" >/dev/null; then
                run_retry $PKG install -y 'dnf-command(config-manager)'
                $PKG config-manager --set-enabled crb 2>/dev/null || $PKG config-manager --set-enabled powertools 2>/dev/null || true
            fi
        fi
    else
        export DEBIAN_FRONTEND=noninteractive
        rm -f /etc/apt/sources.list.d/45drives.list
        apt-get update -qq || true
        run_retry apt-get install -y software-properties-common curl wget gnupg lsb-release
    fi
}

mod_base_tools() {
    step "Installing Base System Tools"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        PACKAGES="git curl wget nano neovim zsh util-linux-user bind-utils net-tools openssl policycoreutils-python-utils psmisc PackageKit pcp pcp-conf pcp-libs pcp-selinux"
        run_retry $PKG install -y $PACKAGES
    else
        PACKAGES="git curl wget nano neovim zsh openssl net-tools dnsutils psmisc packagekit pcp network-manager"
        run_retry apt-get install -y $PACKAGES
        timeout 30s systemctl enable --now NetworkManager || true
    fi
    systemctl unmask packagekit 2>/dev/null || true
    timeout 30s systemctl start packagekit 2>/dev/null || true
}

mod_network() {
    step "Configuring Network & DNS"
    if [[ "$PKG" == "apt-get" ]]; then
        if ls /etc/netplan/*.yaml >/dev/null 2>&1 && grep -q "addresses:" /etc/netplan/*.yaml; then
            log "Static Netplan detected. Skipping wipe to prevent lockout."
        else
            mkdir -p /etc/netplan
            cat > /etc/netplan/01-network-manager-all.yaml <<EOF
network:
  version: 2
  renderer: NetworkManager
EOF
            netplan apply || true
        fi
    fi

    sed -i "/${DOMAIN_FQDN}/d; /${DOMAIN_ALT}/d; /${DC_DNS_IP}/d; /${FILE_SERVER_NAME}/d" /etc/hosts
    cat >> /etc/hosts <<EOF
${DC_DNS_IP}    ${DOMAIN_FQDN} ${DOMAIN_ALT} ${DOMAIN_SHORT}
${FILE_SERVER_IP}    ${FILE_SERVER_NAME}.${DOMAIN_FQDN} ${FILE_SERVER_NAME}.${DOMAIN_ALT} ${FILE_SERVER_NAME}
EOF

    if [[ -L /etc/resolv.conf ]]; then rm -f /etc/resolv.conf; fi
    echo -e "search ${DOMAIN_FQDN} ${DOMAIN_ALT}\nnameserver ${DC_DNS_IP}" > /etc/resolv.conf

    if command -v nmcli &>/dev/null; then
        TARGET_IFACE=$(ip -4 -o addr show | grep "172.16." | awk '{print $2}' | head -n1)
        if [[ -n "$TARGET_IFACE" ]]; then
            CONN=$(nmcli -t -f NAME,DEVICE con show --active | grep ":${TARGET_IFACE}" | cut -d: -f1 | head -n1)
            if [[ -n "$CONN" ]]; then
                nmcli con mod "$CONN" ipv4.dns "$DC_DNS_IP" ipv4.dns-search "${DOMAIN_FQDN},${DOMAIN_ALT}" ipv4.ignore-auto-dns yes
                timeout 15s nmcli con up "$CONN" >/dev/null 2>&1
            fi
        fi
    fi

    echo -e "net.ipv6.conf.all.disable_ipv6 = 1\nnet.ipv6.conf.default.disable_ipv6 = 1" > /etc/sysctl.d/90-disable-ipv6.conf
    sysctl --system &>/dev/null || true

    if command -v systemctl &>/dev/null; then
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y chrony; CHRONY_CONF="/etc/chrony/chrony.conf"
        else run_retry $PKG install -y chrony; CHRONY_CONF="/etc/chrony.conf"; fi
        
        if [[ -f "$CHRONY_CONF" ]]; then
            sed -i '/server/d; /pool/d' "$CHRONY_CONF" 2>/dev/null || true
            echo "server ${NTP_SERVER} iburst" >> "$CHRONY_CONF"
        fi
        timeout 30s systemctl restart chronyd 2>/dev/null || timeout 30s systemctl restart chrony || true
    fi
}

mod_firewall() {
    step "Configuring Firewalld (Defense in Depth)"
    if [[ "$PKG" == "apt-get" ]]; then
        run_retry apt-get install -y firewalld
        systemctl disable ufw --now 2>/dev/null || true
    else
        run_retry $PKG install -y firewalld
    fi

    systemctl enable --now firewalld

    # Trust Docker Subnets
    firewall-cmd --permanent --zone=trusted --add-source=172.17.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=172.18.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=172.19.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=172.20.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=192.168.250.0/24

    # Web Ports
    firewall-cmd --permanent --add-service=http
    firewall-cmd --permanent --add-service=https

    # SSH Lockdown
    firewall-cmd --permanent --remove-service=ssh
    firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.21.0.0/21" service name="ssh" accept'
    firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.16.121.0/24" service name="ssh" accept'
    firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.16.21.0/24" service name="ssh" accept'

    firewall-cmd --reload
}

mod_resize_home() {
    step "LVM Home Resizer"
    if ! command -v lvs &>/dev/null; then return; fi
    if ! mountpoint -q /home; then return; fi
    HOME_DEV=$(findmnt -n -o SOURCE /home)
    if [[ "$HOME_DEV" != *"/mapper/"* ]]; then return; fi

    LV_NAME=$(lvs --noheadings -o lv_name "$HOME_DEV" | tr -d ' ')
    VG_NAME=$(lvs --noheadings -o vg_name "$HOME_DEV" | tr -d ' ')
    LV_PATH="/dev/$VG_NAME/$LV_NAME"
    ROOT_LV_PATH="/dev/$VG_NAME/root" 
    MAPPER_PATH="/dev/mapper/${VG_NAME}-${LV_NAME}"

    CURRENT_SIZE=$(lvs --noheadings -o lv_size --units g "$LV_PATH" 2>/dev/null | tr -d 'g ' || lvs --noheadings -o L_SIZE --units g "$LV_PATH" | tr -d 'g ')
    if [[ ${CURRENT_SIZE%.*} -le 9 ]]; then return; fi

    tar czf /tmp/home_backup.tar.gz -C /home .
    fuser -km /home || true
    timeout 30s umount /home || timeout 15s umount -l /home || true

    lvremove -y "$LV_PATH"
    lvcreate -L "$HOME_TARGET_SIZE" -n "$LV_NAME" "$VG_NAME" -y
    mkfs.ext4 "$LV_PATH"
    
    sed -i '/\/home/d' /etc/fstab
    echo "$MAPPER_PATH /home ext4 defaults 0 0" >> /etc/fstab
    systemctl daemon-reload || true
    
    timeout 30s mount /home || true
    tar xzf /tmp/home_backup.tar.gz -C /home
    if command -v restorecon &>/dev/null; then restorecon -R /home; fi
    
    lvextend -l +100%FREE "$ROOT_LV_PATH"
    xfs_growfs / || resize2fs "$ROOT_LV_PATH" || true
    rm -f /tmp/home_backup.tar.gz
}

mod_domain_users() {
    step "Domain Join & User Setup"
    
    if ! timeout 15s id "$LOCAL_USER" &>/dev/null; then timeout 15s useradd -m -s /bin/bash "$LOCAL_USER" || true; fi
    echo "$LOCAL_USER:$LOCAL_PASS" | chpasswd || true
    timeout 15s usermod -aG sudo "$LOCAL_USER" 2>/dev/null || timeout 15s usermod -aG wheel "$LOCAL_USER" 2>/dev/null || true

    if [[ "$PKG" == "apt-get" ]]; then 
        run_retry apt-get install -y realmd sssd sssd-tools libnss-sss libpam-sss adcli packagekit
        if ! grep -q "pam_mkhomedir.so" /etc/pam.d/common-session; then
            echo "session optional pam_mkhomedir.so skel=/etc/skel umask=077" >> /etc/pam.d/common-session
        fi
    else 
        run_retry $PKG install -y realmd sssd oddjob oddjob-mkhomedir adcli samba-common-tools
    fi

    if ! ping -c 1 -W 2 "$DOMAIN_FQDN" &>/dev/null; then error "DNS setup failed. Cannot join domain."; return; fi

    if command -v update-crypto-policies &>/dev/null; then
        update-crypto-policies --set DEFAULT:AD-SUPPORT >/dev/null 2>&1 || true
    fi

    if ! timeout 15s realm list | grep -q "$DOMAIN_FQDN"; then
        echo -e "\n${YELLOW}Enter AD Admin Username (e.g., ent_joeld):${NC}"
        read -p "User: " JOIN_USER
        realm join --verbose --user="$JOIN_USER" "$DOMAIN_FQDN"
    else
        success "Already joined. Enforcing state..."
    fi

    SSSD_CONF="/etc/sssd/sssd.conf"
    if [[ -f "$SSSD_CONF" ]]; then
        timeout 15s systemctl stop sssd || true
        
        # SSSD Bulletproofing
        if grep -q "^services" "$SSSD_CONF"; then
            sed -i 's/^services.*/services = nss, pam, ssh/' "$SSSD_CONF"
        else
            sed -i '/\[sssd\]/a services = nss, pam, ssh' "$SSSD_CONF"
        fi

        grep -q "access_provider" "$SSSD_CONF" && sed -i 's/access_provider.*/access_provider = simple/' "$SSSD_CONF" || sed -i '/\[domain/a access_provider = simple' "$SSSD_CONF"
        grep -q "simple_allow_groups" "$SSSD_CONF" && sed -i "s/simple_allow_groups.*/simple_allow_groups = ${ALLOWED_LOGIN_GROUP}/" "$SSSD_CONF" || sed -i "/access_provider = simple/a simple_allow_groups = ${ALLOWED_LOGIN_GROUP}" "$SSSD_CONF"
        
        sed -i '/ldap_user_ssh_public_key/d' "$SSSD_CONF"
        sed -i '/ldap_user_extra_attrs/d' "$SSSD_CONF"
        sed -i '/\[domain/a ldap_user_extra_attrs = info:sshPublicKey\nldap_user_ssh_public_key = info' "$SSSD_CONF"
        
        sed -i 's/use_fully_qualified_names.*/use_fully_qualified_names = False/' "$SSSD_CONF"
        sed -i 's/fallback_homedir.*/fallback_homedir = \/home\/%u/' "$SSSD_CONF"
        
        sed -i '/ignore_group_members/d' "$SSSD_CONF"
        sed -i '/subdomain_enumerate/d' "$SSSD_CONF"
        sed -i '/\[domain/a ignore_group_members = True\nsubdomain_enumerate = False' "$SSSD_CONF"

        timeout 30s systemctl start sssd || true
        
        if command -v sss_cache &>/dev/null; then sss_cache -E || true; fi
        
        log "Configuring SSH daemon for AD-based keys..."
        sed -i '/AuthorizedKeysCommand/d' /etc/ssh/sshd_config
        echo -e "\nAuthorizedKeysCommand /usr/bin/sss_ssh_authorizedkeys\nAuthorizedKeysCommandUser nobody" >> /etc/ssh/sshd_config
            
        if systemctl list-unit-files | grep -q "^ssh.service"; then systemctl restart ssh || true
        else systemctl restart sshd || true; fi
    fi
}

###############################################################################
# 5. MODULAR COMPONENTS
###############################################################################

mod_docker() {
    step "Installing & Configuring Docker"

    # 1. OS-Aware Repositories
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if [[ ! -f /etc/yum.repos.d/docker-ce.repo ]]; then
            run_retry $PKG install -y yum-utils
            run_retry yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
        fi
        $PKG remove -y podman buildah docker docker-client docker-common docker-engine >/dev/null 2>&1 || true
    else
        if [[ ! -f /etc/apt/sources.list.d/docker.list ]]; then
            install -m 0755 -d /etc/apt/keyrings
            run_retry curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
            chmod a+r /etc/apt/keyrings/docker.asc
            
            source /etc/os-release
            REPO_OS=${ID}
            [[ "$ID" == "zorin" ]] && REPO_OS="ubuntu" 
            echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${REPO_OS} ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list
            apt-get update -qq || true
        fi
    fi

    # 2. Install Engine
    if ! command -v docker &>/dev/null; then
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
        else run_retry $PKG install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin; fi
    fi

    # 3. Daemon Config
    mkdir -p /etc/docker
    cat > /etc/docker/daemon.json <<EOF
{
  "insecure-registries": [ ${INSECURE_REGISTRIES} ]
}
EOF

    # 4. Systemd Proxy
    mkdir -p /etc/systemd/system/docker.service.d
    cat > /etc/systemd/system/docker.service.d/http-proxy.conf <<EOF
[Service]
Environment="HTTP_PROXY=${PROXY_URL}"
Environment="HTTPS_PROXY=${PROXY_URL}"
Environment="NO_PROXY=${NO_PROXY_LIST}"
EOF

    systemctl daemon-reload || true
    timeout 30s systemctl enable --now docker || true
    timeout 60s systemctl restart docker || true

    # 5. Access Control
    timeout 15s usermod -aG docker root 2>/dev/null || true
    if timeout 15s id "$LOCAL_USER" &>/dev/null; then timeout 15s usermod -aG docker "$LOCAL_USER" 2>/dev/null || true; fi

    # 6. Client Proxy
    mkdir -p /root/.docker
    cat > /root/.docker/config.json <<EOF
{
  "proxies": {
    "default": {
      "httpProxy": "${PROXY_URL}",
      "httpsProxy": "${PROXY_URL}",
      "noProxy": "${NO_PROXY_LIST}"
    }
  }
}
EOF

    if timeout 15s id "$LOCAL_USER" &>/dev/null; then
        USER_HOME=$(eval echo ~$LOCAL_USER)
        mkdir -p "$USER_HOME/.docker"
        cp /root/.docker/config.json "$USER_HOME/.docker/config.json"
        chown -R "$LOCAL_USER:$LOCAL_USER" "$USER_HOME/.docker" || true
    fi
}

mod_lazydocker() {
    step "Installing LazyDocker"
    if ! command -v lazydocker &>/dev/null; then
        run_retry curl -sSL https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bash
    fi
}

mod_web_stack() {
    step "Installing Web Stack (PHP, Nginx, Node)"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if ! rpm -q remi-release >/dev/null 2>&1; then
            if [[ "$PKG" == "dnf" ]]; then run_retry $PKG install -y "https://rpms.remirepo.net/enterprise/remi-release-${VERSION_MAJOR}.rpm"
            else run_retry $PKG install -y http://rpms.remirepo.net/enterprise/remi-release-7.rpm yum-utils; fi
        fi
        $PKG clean packages >/dev/null 2>&1 || true
        
        if [[ "$PKG" == "dnf" ]]; then
            $PKG module reset php -y || true
            $PKG module install -y php:remi-${PHP_VERSION}
        else
            yum-config-manager --enable remi-php83 || true
            $PKG install -y php php-cli php-fpm php-mysqlnd php-gd
        fi
        $PKG install -y java-${JAVA_VERSION}-openjdk nginx nodejs
    else
        if ! grep -q "ondrej/php" /etc/apt/sources.list.d/* 2>/dev/null; then run_retry add-apt-repository -y ppa:ondrej/php; fi
        apt-get update -qq || true
        run_retry apt-get install -y php${PHP_VERSION} php${PHP_VERSION}-{cli,fpm,mysql,gd,mbstring,xml,curl,zip}
        run_retry apt-get install -y openjdk-${JAVA_VERSION}-jdk nginx nodejs npm
    fi

    # Inject Proxy into PHP Configs
    if command -v php &>/dev/null; then
        find /etc/php* -name "php.ini" 2>/dev/null | while read -r INI_FILE; do
            sed -i '/^http_proxy/d; /^https_proxy/d' "$INI_FILE"
            echo -e "\n; Proxy Settings\nhttp_proxy = \"${PROXY_URL}\"\nhttps_proxy = \"${PROXY_URL}\"" >> "$INI_FILE"
            if grep -q "allow_url_fopen" "$INI_FILE"; then sed -i 's/^allow_url_fopen.*/allow_url_fopen = On/' "$INI_FILE"
            else echo "allow_url_fopen = On" >> "$INI_FILE"; fi
        done
        if systemctl list-unit-files | grep -q php-fpm; then timeout 30s systemctl restart php-fpm || true; fi
        if systemctl list-unit-files | grep -q php${PHP_VERSION}-fpm; then timeout 30s systemctl restart php${PHP_VERSION}-fpm || true; fi
    fi
}

mod_db_stack() {
    step "Installing Databases"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if [[ ! -f /etc/yum.repos.d/mariadb.repo ]]; then
            cat > /etc/yum.repos.d/mariadb.repo <<EOF
[mariadb]
name = MariaDB
baseurl = https://rpm.mariadb.org/${MARIADB_VERSION}/rhel/\$releasever/\$basearch
module_hotfixes=1
gpgkey=https://rpm.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
EOF
        fi
        run_retry $PKG install -y MariaDB-server MariaDB-client postgresql-server
    else
        run_retry apt-get install -y mariadb-server postgresql
    fi
}

mod_cockpit() {
    step "Installing Cockpit"
    if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y cockpit cockpit-storaged cockpit-pcp cockpit-packagekit
    else run_retry $PKG install -y cockpit cockpit-storaged cockpit-pcp 2>/dev/null || run_retry $PKG install -y cockpit; fi
    
    mkdir -p /etc/systemd/system/cockpit.service.d
    echo -e "[Service]\nEnvironment=\"HTTP_PROXY=${PROXY_URL}\"\nEnvironment=\"HTTPS_PROXY=${PROXY_URL}\"\nEnvironment=\"NO_PROXY=${NO_PROXY_LIST}\"" > /etc/systemd/system/cockpit.service.d/proxy.conf
    systemctl daemon-reload || true
    timeout 30s systemctl enable --now cockpit.socket || true
}

mod_cleanup() {
    step "Final Cleanup & Hardening"
    if command -v tmux &>/dev/null; then $PKG remove -y tmux 2>/dev/null || true; fi
    rm -f /etc/tmux.conf
    if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y fish fail2ban; else run_retry $PKG install -y fish fail2ban; fi
    
    systemctl disable systemd-networkd-wait-online.service 2>/dev/null || true
    systemctl mask systemd-networkd-wait-online.service 2>/dev/null || true
    if [[ -f /etc/rc.d/rc.local ]]; then chmod +x /etc/rc.d/rc.local; fi
    if grep -q "172.16.21.16" /etc/fstab; then sed -i '/172.16.21.16/d' /etc/fstab; fi
    
    if systemctl is-failed sssd-nss.socket &>/dev/null; then
        systemctl reset-failed || true
        timeout 30s systemctl restart sssd || true
    fi

    ESCAPED_GROUP=$(echo "$AD_SUDO_GROUP" | sed 's/ /\\ /g')
    echo "%${ESCAPED_GROUP} ALL=(ALL) NOPASSWD: ALL" > "/etc/sudoers.d/10-ad-admins"
    chmod 440 "/etc/sudoers.d/10-ad-admins"

    cat > /etc/fail2ban/jail.local <<EOF
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3
bantime = 3600
EOF
    timeout 30s systemctl enable --now fail2ban || true
}

###############################################################################
# 6. CLI ROUTER
###############################################################################
detect_and_fix_os 

show_help() {
    echo "Usage: $0 [OPTION]"
    echo ""
    echo "Core Deployment:"
    echo "  --basics        Proxy, Certs, Repos, Network, Firewalld, AD, Cleanup."
    echo "  --full          Everything (Basics + Docker + Web/DB Stack + Cockpit)."
    echo ""
    echo "Modular Execution:"
    echo "  --docker        Install and configure Docker Engine with Proxy/Subnets."
    echo "  --ad-join       Run the SSSD and Realmd AD Join sequence."
    echo "  --certs         Mount CIFS, fetch root cert, update CA trust."
    echo "  --web-stack     Install PHP, Nginx, Node, and Java."
    echo "  --db-stack      Install MariaDB and PostgreSQL."
    echo "  --tools         Install zsh, fish, neovim, git, nano, lazydocker."
    echo "  --resize-home   Shrink LVM /home to ${HOME_TARGET_SIZE} (Backup/Restore)."
    echo ""
}

if [[ $# -eq 0 ]]; then show_help; exit 0; fi

while [[ "$#" -gt 0 ]]; do
    case $1 in
        --basics) mod_proxy; mod_clock_fix; mod_certs; mod_base_repos; mod_base_tools; mod_network; mod_firewall; mod_domain_users; mod_cleanup ;;
        --full) mod_proxy; mod_clock_fix; mod_certs; mod_base_repos; mod_base_tools; mod_network; mod_firewall; mod_domain_users; mod_docker; mod_web_stack; mod_db_stack; mod_cockpit; mod_cleanup ;;
        
        --docker) mod_proxy; mod_docker ;;
        --ad-join) mod_domain_users ;;
        --certs) mod_certs ;;
        --web-stack) mod_proxy; mod_web_stack ;;
        --db-stack) mod_proxy; mod_db_stack ;;
        --tools) mod_proxy; mod_base_tools; mod_lazydocker ;;
        --resize-home) mod_resize_home ;;
        
        *) echo "Unknown option: $1"; show_help; exit 1 ;;
    esac
    shift
done

echo -e "\n${GREEN}[$(date +'%H:%M:%S')] === Setup Complete ===${NC}"

 

master_script.sh - v49
#!/usr/bin/env bash
#
# MASTER INFRASTRUCTURE SETUP v49
# Enhancements: Firefox Enterprise Policy Proxying, Flatpak Ecosystem, DE Auto-Detect
#
###############################################################################
# 1. CONFIGURATION
###############################################################################
DOMAIN_FQDN="m21.gov.local"
DOMAIN_ALT="m21.gov.tt"
DOMAIN_SHORT="M21"
DC_DNS_IP="172.16.21.161"
NTP_SERVER="172.16.121.9"
TARGET_TIMEZONE="America/Port_of_Spain"

# File Server Info
FILE_SERVER_IP="172.16.21.16"
FILE_SERVER_NAME="fileserver2"

# Proxy
PROXY_URL="http://172.40.4.14:8080"
NO_PROXY_LIST="127.0.0.1,localhost,localhost.localdomain,${DOMAIN_FQDN},${DOMAIN_ALT},.${DOMAIN_FQDN},.${DOMAIN_ALT},${DC_DNS_IP},172.30.0.0/20,172.26.21.0/24,10.21.0.0/21,172.16.121.0/24"

# Docker Settings
INSECURE_REGISTRIES='"172.16.121.119:5000", "docker-repo.msya.gov.tt"'

# AD Access Control
AD_SUDO_GROUP="ICT Staff SG M21"
ALLOWED_LOGIN_GROUP="ICT Staff SG M21"

# Share Credentials
SHARE_PATH="//172.16.21.16/fileserver2"
SHARE_USER="Cipher.m21"
SHARE_PASS=")\ly; 634'NJ%i+"
CERT_SOURCE_PATH="/General/IT FILES/prx/Gortt_certificate_V4.cer"
TARGET_CERT_NAME="GORTT_Root_Exp2029"

# Failsafe User
LOCAL_USER="pcsupport"
LOCAL_PASS="ProIT321*"

# LVM Settings
HOME_TARGET_SIZE="8G"

# Versions
PHP_VERSION="8.3"
JAVA_VERSION="21"
MARIADB_VERSION="10.11"

###############################################################################
# 2. HELPER FUNCTIONS
###############################################################################
set -e
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; BLUE='\033[0;34m'; NC='\033[0m'

log() { echo -e "${BLUE}[$(date +'%H:%M:%S')] [INFO]${NC} $1"; }
step() { echo -e "\n${YELLOW}[$(date +'%H:%M:%S')] >>> $1${NC}"; }
success() { echo -e "${GREEN}[$(date +'%H:%M:%S')] [OK]${NC} $1"; }
error() { echo -e "${RED}[$(date +'%H:%M:%S')] [ERROR]${NC} $1"; }

run_retry() {
    local n=1; local max=3; local delay=2
    while true; do
        "$@" && return 0
        if [[ $n -lt $max ]]; then
            ((n++)); log "Command failed. Retrying ($n/$max)..."; sleep $delay
        else
            return 1
        fi
    done
}

###############################################################################
# 3. PRE-FLIGHT CHECKS
###############################################################################
detect_and_fix_os() {
    if [[ ! -f /etc/os-release ]]; then error "Cannot detect OS. /etc/os-release missing."; exit 1; fi
    source /etc/os-release
    OS_ID=$(echo "$ID" | tr '[:upper:]' '[:lower:]')
    VERSION_MAJOR=$(echo "$VERSION_ID" | cut -d. -f1)
    
    if timeout 10s systemctl is-active --quiet packagekit.service 2>/dev/null; then
        timeout 15s systemctl stop packagekit.service || true
    fi
    
    if [[ "$OS_ID" == "centos" && "$VERSION_MAJOR" == "7" ]]; then
        PKG="yum"
        if grep -q "linux/rhel" /etc/yum.repos.d/docker-ce.repo 2>/dev/null; then rm -f /etc/yum.repos.d/docker-ce.repo; fi
        if [ ! -f /etc/yum.repos.d/CentOS-Base.repo.backup ]; then
            cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2>/dev/null || true
            run_retry curl -o /etc/yum.repos.d/CentOS-Base.repo https://el7.repo.almalinux.org/centos/CentOS-Base.repo
        fi
    elif [[ "$OS_ID" =~ (rhel|centos|almalinux|rocky|fedora) ]]; then PKG="dnf"
    elif [[ "$OS_ID" =~ (ubuntu|debian|zorin) ]]; then PKG="apt-get"; export DEBIAN_FRONTEND=noninteractive
    elif [[ "$OS_ID" == "arch" || "$ID_LIKE" == *"arch"* ]]; then PKG="pacman"; run_retry pacman -Sy
    else error "Unsupported OS: $OS_ID"; exit 1; fi
}

###############################################################################
# 4. CORE MODULES
###############################################################################

mod_proxy() {
    step "Configuring System Proxy"
    
    cat > /etc/profile.d/proxy.sh <<EOF
export http_proxy="${PROXY_URL}"
export https_proxy="${PROXY_URL}"
export ftp_proxy="${PROXY_URL}"
export no_proxy="${NO_PROXY_LIST}"
export HTTP_PROXY="${PROXY_URL}"
export HTTPS_PROXY="${PROXY_URL}"
export FTP_PROXY="${PROXY_URL}"
export NO_PROXY="${NO_PROXY_LIST}"
EOF
    source /etc/profile.d/proxy.sh

    mkdir -p /etc/sudoers.d
    echo 'Defaults env_keep += "http_proxy https_proxy ftp_proxy no_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY NO_PROXY"' > /etc/sudoers.d/10-proxy-env
    chmod 440 /etc/sudoers.d/10-proxy-env

    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        CONF_FILE="/etc/dnf/dnf.conf"
        [[ ! -f "$CONF_FILE" ]] && CONF_FILE="/etc/yum.conf"
        grep -q "proxy=" "$CONF_FILE" 2>/dev/null || echo "proxy=${PROXY_URL}" >> "$CONF_FILE"
        if ! grep -q "minrate" "$CONF_FILE" 2>/dev/null; then
            echo -e "timeout=60\nretries=10\nminrate=1" >> "$CONF_FILE"
        fi
    elif [[ "$PKG" == "apt-get" ]]; then
        echo -e "Acquire::http::Proxy \"${PROXY_URL}\";\nAcquire::https::Proxy \"${PROXY_URL}\";" > /etc/apt/apt.conf.d/80proxy
    fi
}

mod_gui_proxy() {
    step "Configuring GUI Proxy Settings (System-Wide)"
    
    PROXY_HOST=$(echo "$PROXY_URL" | awk -F/ '{print $3}' | cut -d: -f1)
    PROXY_PORT=$(echo "$PROXY_URL" | awk -F: '{print $NF}')
    DCONF_NO_PROXY="['$(echo "$NO_PROXY_LIST" | sed "s/,/','/g")']"

    if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y dconf-cli
    elif [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then run_retry $PKG install -y dconf
    elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm dconf
    fi

    # 1. GNOME / Cinnamon / Mate
    mkdir -p /etc/dconf/profile
    mkdir -p /etc/dconf/db/local.d
    echo -e "user-db:user\nsystem-db:local" > /etc/dconf/profile/user

    cat > /etc/dconf/db/local.d/01-proxy <<EOF
[system/proxy]
mode='manual'
ignore-hosts=${DCONF_NO_PROXY}

[system/proxy/http]
host='${PROXY_HOST}'
port=${PROXY_PORT}

[system/proxy/https]
host='${PROXY_HOST}'
port=${PROXY_PORT}

[system/proxy/ftp]
host='${PROXY_HOST}'
port=${PROXY_PORT}
EOF
    dconf update || log "Warning: dconf update failed, GUI settings may require reboot."

    # 2. KDE Plasma
    mkdir -p /etc/xdg
    cat > /etc/xdg/kioslaverc <<EOF
[Proxy Settings]
ProxyType=1
httpProxy=${PROXY_URL}
httpsProxy=${PROXY_URL}
ftpProxy=${PROXY_URL}
NoProxyFor=${NO_PROXY_LIST}
EOF

    # 3. Firefox Enterprise Policy Setup
    mkdir -p /etc/firefox/policies
    cat > /etc/firefox/policies/policies.json <<FFEOF
{
  "policies": {
    "Proxy": {
      "Mode": "manual",
      "HTTPProxy": "${PROXY_HOST}:${PROXY_PORT}",
      "HTTPSProxy": "${PROXY_HOST}:${PROXY_PORT}",
      "FTPProxy": "${PROXY_HOST}:${PROXY_PORT}",
      "Passthrough": "${NO_PROXY_LIST}"
    }
  }
}
FFEOF
}

mod_proxy_toggle() {
    step "Installing Proxy Toggle Tool"
    
    cat > /usr/local/bin/toggle-proxy <<EOF
#!/usr/bin/env bash
# System-Wide Proxy Toggle
# Usage: sudo toggle-proxy [on|off]

if [[ "\$EUID" -ne 0 ]]; then
  echo "Please run as root (sudo toggle-proxy on|off)"
  exit 1
fi

MODE=\$1
PROXY_URL="${PROXY_URL}"
PROXY_HOST="\$(echo "\$PROXY_URL" | awk -F/ '{print \$3}' | cut -d: -f1)"
PROXY_PORT="\$(echo "\$PROXY_URL" | awk -F: '{print \$NF}')"
NO_PROXY_LIST="${NO_PROXY_LIST}"

# Scrub hardcoded package manager proxies in BOTH states
if command -v apt-get &>/dev/null; then rm -f /etc/apt/apt.conf.d/80proxy; fi
if command -v dnf &>/dev/null; then sed -i '/^proxy=/d' /etc/dnf/dnf.conf 2>/dev/null || true; fi

if [[ "\$MODE" == "on" ]]; then
    echo "Enabling System Proxy..."
    
    cat > /etc/profile.d/proxy.sh <<ENVEOF
export http_proxy="\${PROXY_URL}"
export https_proxy="\${PROXY_URL}"
export ftp_proxy="\${PROXY_URL}"
export no_proxy="\${NO_PROXY_LIST}"
export HTTP_PROXY="\${PROXY_URL}"
export HTTPS_PROXY="\${PROXY_URL}"
export FTP_PROXY="\${PROXY_URL}"
export NO_PROXY="\${NO_PROXY_LIST}"
ENVEOF

    if [[ -d /etc/systemd/system/docker.service.d ]]; then
        cat > /etc/systemd/system/docker.service.d/http-proxy.conf <<DOCKEREOF
[Service]
Environment="HTTP_PROXY=\${PROXY_URL}"
Environment="HTTPS_PROXY=\${PROXY_URL}"
Environment="NO_PROXY=\${NO_PROXY_LIST}"
DOCKEREOF
        systemctl daemon-reload && systemctl restart docker || true
    fi

    if command -v dconf &>/dev/null; then
        mkdir -p /etc/dconf/db/local.d
        sed -i "s/mode='none'/mode='manual'/" /etc/dconf/db/local.d/01-proxy 2>/dev/null || true
        dconf update
    fi
    if [[ -f /etc/xdg/kioslaverc ]]; then
        sed -i "s/ProxyType=0/ProxyType=1/" /etc/xdg/kioslaverc 2>/dev/null || true
    fi
    
    mkdir -p /etc/firefox/policies
    cat > /etc/firefox/policies/policies.json <<FFEOF
{
  "policies": {
    "Proxy": {
      "Mode": "manual",
      "HTTPProxy": "\${PROXY_HOST}:\${PROXY_PORT}",
      "HTTPSProxy": "\${PROXY_HOST}:\${PROXY_PORT}",
      "FTPProxy": "\${PROXY_HOST}:\${PROXY_PORT}",
      "Passthrough": "\${NO_PROXY_LIST}"
    }
  }
}
FFEOF

    echo "[OK] Proxy is ON. Log out and back in for all terminal sessions to update."

elif [[ "\$MODE" == "off" ]]; then
    echo "Disabling System Proxy..."
    
    > /etc/profile.d/proxy.sh

    if [[ -d /etc/systemd/system/docker.service.d ]]; then
        > /etc/systemd/system/docker.service.d/http-proxy.conf
        systemctl daemon-reload && systemctl restart docker || true
    fi

    if command -v dconf &>/dev/null; then
        mkdir -p /etc/dconf/db/local.d
        sed -i "s/mode='manual'/mode='none'/" /etc/dconf/db/local.d/01-proxy 2>/dev/null || true
        dconf update
    fi
    if [[ -f /etc/xdg/kioslaverc ]]; then
        sed -i "s/ProxyType=1/ProxyType=0/" /etc/xdg/kioslaverc 2>/dev/null || true
    fi

    mkdir -p /etc/firefox/policies
    cat > /etc/firefox/policies/policies.json <<FFEOF
{
  "policies": {
    "Proxy": {
      "Mode": "none"
    }
  }
}
FFEOF

    echo "[OK] Proxy is OFF. Log out and back in for all terminal sessions to update."
else
    echo "Usage: toggle-proxy [on|off]"
fi
EOF

    chmod +x /usr/local/bin/toggle-proxy
}

mod_flatpak() {
    step "Configuring Flatpak, Flathub & DE Integrations"
    
    # Check for DE footprint
    HAS_GNOME=false
    HAS_KDE=false
    if command -v gnome-shell &>/dev/null || dpkg -l | grep -q "gnome-shell" 2>/dev/null || rpm -q gnome-shell 2>/dev/null; then HAS_GNOME=true; fi
    if command -v plasmashell &>/dev/null || dpkg -l | grep -q "plasma-workspace" 2>/dev/null || rpm -q plasma-workspace 2>/dev/null; then HAS_KDE=true; fi

    if [[ "$PKG" == "apt-get" ]]; then
        run_retry apt-get install -y flatpak
        if [ "$HAS_GNOME" = true ]; then run_retry apt-get install -y gnome-software-plugin-flatpak; fi
        if [ "$HAS_KDE" = true ]; then run_retry apt-get install -y plasma-discover-backend-flatpak; fi
    elif [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        run_retry $PKG install -y flatpak
        if [ "$HAS_GNOME" = true ]; then run_retry $PKG install -y gnome-software; fi
        if [ "$HAS_KDE" = true ]; then run_retry $PKG install -y plasma-discover-flatpak; fi
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm flatpak
        if [ "$HAS_GNOME" = true ]; then run_retry pacman -S --noconfirm gnome-software; fi
        if [ "$HAS_KDE" = true ]; then run_retry pacman -S --noconfirm discover; fi
    fi

    # Add Flathub Repository System-Wide
    run_retry flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo

    # Install Auto-Extensions
    if [ "$HAS_GNOME" = true ]; then
        log "GNOME detected. Installing Extension Manager from Flathub..."
        run_retry flatpak install -y flathub com.mattjakeman.ExtensionManager
    fi
}

mod_clock_fix() {
    step "Synchronizing System Clock"
    timedatectl set-timezone "$TARGET_TIMEZONE" || true
    timedatectl set-ntp true || true
    if systemctl list-unit-files | grep -q systemd-timesyncd; then
        timeout 30s systemctl restart systemd-timesyncd || true
    fi
}

mod_certs() {
    step "Installing Certificates"
    MNT="/mnt/share_certs_tmp"
    mkdir -p "$MNT"
    
    if ! command -v mount.cifs &>/dev/null; then
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get update -qq >/dev/null 2>&1 || true; run_retry apt-get install -y cifs-utils
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm cifs-utils
        else run_retry $PKG install -y cifs-utils; fi
    fi

    if mountpoint -q "$MNT"; then umount -l "$MNT"; fi
    
    if timeout 30s mount -t cifs "$SHARE_PATH" "$MNT" -o username="$SHARE_USER",password="$SHARE_PASS",vers=3.0; then
        SOURCE_FULL="$MNT$CERT_SOURCE_PATH"
        TEMP_PEM="/tmp/${TARGET_CERT_NAME}_staging.pem"
        
        if [[ -f "$SOURCE_FULL" ]]; then
            if ! openssl x509 -inform der -in "$SOURCE_FULL" -out "$TEMP_PEM" 2>/dev/null; then cp "$SOURCE_FULL" "$TEMP_PEM"; fi
            
            if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
                cp "$TEMP_PEM" "/etc/pki/ca-trust/source/anchors/${TARGET_CERT_NAME}.pem"
                [[ "$VERSION_MAJOR" -lt 9 ]] && update-ca-trust force-enable 2>/dev/null || true
                update-ca-trust extract
            elif [[ "$PKG" == "pacman" ]]; then
                cp "$TEMP_PEM" "/etc/ca-certificates/trust-source/anchors/${TARGET_CERT_NAME}.crt"
                trust extract-compat
            else
                cp "$TEMP_PEM" "/usr/local/share/ca-certificates/${TARGET_CERT_NAME}.crt"
                update-ca-certificates
            fi
        fi
        timeout 15s umount "$MNT" || true
    fi
    rmdir "$MNT" 2>/dev/null || true
}

mod_base_repos() {
    step "Configuring Base OS Repositories"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if [[ "$OS_ID" == "fedora" ]]; then
            log "Setting up Fedora 3rd Party Repos (RPM Fusion & Workstation Repos)..."
            run_retry dnf install -y dnf-plugins-core fedora-workstation-repositories || true
            run_retry dnf install -y "https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-${VERSION_MAJOR}.noarch.rpm" \
                                     "https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-${VERSION_MAJOR}.noarch.rpm" || true
            dnf config-manager --set-enabled rpmfusion-free rpmfusion-nonfree || true
        else
            if ! rpm -q epel-release >/dev/null 2>&1; then run_retry $PKG install -y epel-release; fi
            if [[ "$PKG" == "dnf" ]]; then
                if ! dnf repolist enabled 2>/dev/null | grep -E "crb|powertools" >/dev/null; then
                    run_retry $PKG install -y 'dnf-command(config-manager)'
                    $PKG config-manager --set-enabled crb 2>/dev/null || $PKG config-manager --set-enabled powertools 2>/dev/null || true
                fi
            fi
        fi
    elif [[ "$PKG" == "apt-get" ]]; then
        export DEBIAN_FRONTEND=noninteractive
        rm -f /etc/apt/sources.list.d/45drives.list
        apt-get update -qq || true
        BASE_APT_PKGS="curl wget gnupg lsb-release ca-certificates"
        if [[ "$OS_ID" != "debian" ]]; then BASE_APT_PKGS="software-properties-common $BASE_APT_PKGS"; fi
        run_retry apt-get install -y $BASE_APT_PKGS
    fi
}

mod_base_tools() {
    step "Installing Base System Tools"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        PACKAGES="git curl wget nano neovim zsh util-linux-user bind-utils net-tools openssl policycoreutils-python-utils psmisc PackageKit pcp pcp-conf pcp-libs pcp-selinux"
        run_retry $PKG install -y $PACKAGES
    elif [[ "$PKG" == "pacman" ]]; then
        PACKAGES="git curl wget nano neovim zsh openssl net-tools bind psmisc networkmanager"
        run_retry pacman -S --noconfirm $PACKAGES
        timeout 30s systemctl enable --now NetworkManager || true
    else
        PACKAGES="git curl wget nano neovim zsh openssl net-tools dnsutils psmisc packagekit pcp network-manager"
        run_retry apt-get install -y $PACKAGES
        timeout 30s systemctl enable --now NetworkManager || true
    fi
    systemctl unmask packagekit 2>/dev/null || true
    timeout 30s systemctl start packagekit 2>/dev/null || true
}

mod_network() {
    step "Configuring Network & DNS"
    if [[ "$PKG" == "apt-get" ]] && command -v netplan >/dev/null 2>&1; then
        if ls /etc/netplan/*.yaml >/dev/null 2>&1 && grep -q "addresses:" /etc/netplan/*.yaml; then
            log "Static Netplan detected. Skipping wipe to prevent lockout."
        else
            mkdir -p /etc/netplan
            cat > /etc/netplan/01-network-manager-all.yaml <<EOF
network:
  version: 2
  renderer: NetworkManager
EOF
            netplan apply || true
        fi
    fi

    sed -i "/${DOMAIN_FQDN}/d; /${DOMAIN_ALT}/d; /${DC_DNS_IP}/d; /${FILE_SERVER_NAME}/d" /etc/hosts
    cat >> /etc/hosts <<EOF
${DC_DNS_IP}    ${DOMAIN_FQDN} ${DOMAIN_ALT} ${DOMAIN_SHORT}
${FILE_SERVER_IP}    ${FILE_SERVER_NAME}.${DOMAIN_FQDN} ${FILE_SERVER_NAME}.${DOMAIN_ALT} ${FILE_SERVER_NAME}
EOF

    if [[ -L /etc/resolv.conf ]]; then rm -f /etc/resolv.conf; fi
    echo -e "search ${DOMAIN_FQDN} ${DOMAIN_ALT}\nnameserver ${DC_DNS_IP}" > /etc/resolv.conf

    if command -v nmcli &>/dev/null; then
        TARGET_IFACE=$(ip -4 -o addr show | grep "172.16." | awk '{print $2}' | head -n1)
        if [[ -n "$TARGET_IFACE" ]]; then
            CONN=$(nmcli -t -f NAME,DEVICE con show --active | grep ":${TARGET_IFACE}" | cut -d: -f1 | head -n1)
            if [[ -n "$CONN" ]]; then
                nmcli con mod "$CONN" ipv4.dns "$DC_DNS_IP" ipv4.dns-search "${DOMAIN_FQDN},${DOMAIN_ALT}" ipv4.ignore-auto-dns yes
                timeout 15s nmcli con up "$CONN" >/dev/null 2>&1
            fi
        fi
    fi

    echo -e "net.ipv6.conf.all.disable_ipv6 = 1\nnet.ipv6.conf.default.disable_ipv6 = 1" > /etc/sysctl.d/90-disable-ipv6.conf
    sysctl --system &>/dev/null || true

    if command -v systemctl &>/dev/null; then
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y chrony; CHRONY_CONF="/etc/chrony/chrony.conf"
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm chrony; CHRONY_CONF="/etc/chrony.conf"
        else run_retry $PKG install -y chrony; CHRONY_CONF="/etc/chrony.conf"; fi
        
        if [[ -f "$CHRONY_CONF" ]]; then
            sed -i '/server/d; /pool/d' "$CHRONY_CONF" 2>/dev/null || true
            echo "server ${NTP_SERVER} iburst" >> "$CHRONY_CONF"
        fi
        timeout 30s systemctl restart chronyd 2>/dev/null || timeout 30s systemctl restart chrony || true
    fi
}

mod_firewall() {
    step "Configuring Firewalld (Defense in Depth)"
    if [[ "$PKG" == "apt-get" ]]; then
        run_retry apt-get install -y firewalld
        systemctl disable ufw --now 2>/dev/null || true
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm firewalld
    else
        run_retry $PKG install -y firewalld
    fi

    systemctl enable --now firewalld

    firewall-cmd --permanent --zone=trusted --add-source=172.17.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=172.18.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=172.19.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=172.20.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=192.168.250.0/24

    firewall-cmd --permanent --add-service=http
    firewall-cmd --permanent --add-service=https

    firewall-cmd --permanent --remove-service=ssh
    firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.21.0.0/21" service name="ssh" accept'
    firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.16.121.0/24" service name="ssh" accept'
    firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.16.21.0/24" service name="ssh" accept'

    firewall-cmd --reload
}

mod_resize_home() {
    step "LVM Home Resizer"
    if ! command -v lvs &>/dev/null; then return; fi
    if ! mountpoint -q /home; then return; fi
    HOME_DEV=$(findmnt -n -o SOURCE /home)
    if [[ "$HOME_DEV" != *"/mapper/"* ]]; then return; fi

    LV_NAME=$(lvs --noheadings -o lv_name "$HOME_DEV" | tr -d ' ')
    VG_NAME=$(lvs --noheadings -o vg_name "$HOME_DEV" | tr -d ' ')
    LV_PATH="/dev/$VG_NAME/$LV_NAME"
    ROOT_LV_PATH="/dev/$VG_NAME/root" 
    MAPPER_PATH="/dev/mapper/${VG_NAME}-${LV_NAME}"

    CURRENT_SIZE=$(lvs --noheadings -o lv_size --units g "$LV_PATH" 2>/dev/null | tr -d 'g ' || lvs --noheadings -o L_SIZE --units g "$LV_PATH" | tr -d 'g ')
    if [[ ${CURRENT_SIZE%.*} -le 9 ]]; then return; fi

    tar czf /tmp/home_backup.tar.gz -C /home .
    fuser -km /home || true
    timeout 30s umount /home || timeout 15s umount -l /home || true

    lvremove -y "$LV_PATH"
    lvcreate -L "$HOME_TARGET_SIZE" -n "$LV_NAME" "$VG_NAME" -y
    mkfs.ext4 "$LV_PATH"
    
    sed -i '/\/home/d' /etc/fstab
    echo "$MAPPER_PATH /home ext4 defaults 0 0" >> /etc/fstab
    systemctl daemon-reload || true
    
    timeout 30s mount /home || true
    tar xzf /tmp/home_backup.tar.gz -C /home
    if command -v restorecon &>/dev/null; then restorecon -R /home; fi
    
    lvextend -l +100%FREE "$ROOT_LV_PATH"
    xfs_growfs / || resize2fs "$ROOT_LV_PATH" || true
    rm -f /tmp/home_backup.tar.gz
}

mod_domain_users() {
    step "Domain Join & User Setup"
    
    if ! timeout 15s id "$LOCAL_USER" &>/dev/null; then timeout 15s useradd -m -s /bin/bash "$LOCAL_USER" || true; fi
    echo "$LOCAL_USER:$LOCAL_PASS" | chpasswd || true
    timeout 15s usermod -aG sudo "$LOCAL_USER" 2>/dev/null || timeout 15s usermod -aG wheel "$LOCAL_USER" 2>/dev/null || true

    if [[ "$PKG" == "apt-get" ]]; then 
        run_retry apt-get install -y realmd sssd sssd-tools libnss-sss libpam-sss adcli packagekit
        if ! grep -q "pam_mkhomedir.so" /etc/pam.d/common-session; then
            echo "session optional pam_mkhomedir.so skel=/etc/skel umask=077" >> /etc/pam.d/common-session
        fi
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm sssd adcli smbclient
        if ! command -v realm &>/dev/null; then
            log "Warning: 'realmd' is not in standard Arch repos. Please install it via AUR (e.g., yay -S realmd) to join the domain later."
        fi
    else 
        run_retry $PKG install -y realmd sssd oddjob oddjob-mkhomedir adcli samba-common-tools
    fi

    if ! ping -c 1 -W 2 "$DOMAIN_FQDN" &>/dev/null; then error "DNS setup failed. Cannot join domain."; return; fi

    if command -v update-crypto-policies &>/dev/null; then
        update-crypto-policies --set DEFAULT:AD-SUPPORT >/dev/null 2>&1 || true
    fi

    if command -v realm &>/dev/null; then
        if ! timeout 15s realm list | grep -q "$DOMAIN_FQDN"; then
            echo -e "\n${YELLOW}Enter AD Admin Username (e.g., ent_joeld):${NC}"
            read -p "User: " JOIN_USER
            realm join --verbose --user="$JOIN_USER" "$DOMAIN_FQDN"
        else
            success "Already joined. Enforcing state..."
        fi
    fi

    if ! command -v sshd &>/dev/null || [[ ! -f /etc/ssh/sshd_config ]]; then
        log "OpenSSH Server missing or unconfigured. Installing explicitly..."
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y openssh-server
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm openssh
        else run_retry $PKG install -y openssh-server; fi
        
        if systemctl list-unit-files | grep -q "^ssh.service"; then
            systemctl enable ssh --now || true
        else
            systemctl enable sshd --now || true
        fi
        sleep 2
    fi

    if [[ ! -f /etc/ssh/sshd_config ]]; then
        error "/etc/ssh/sshd_config still not found after installation attempts. SSH AD key injection bypassed."
    else
        log "Configuring SSH daemon for AD-based keys..."
        sed -i '/AuthorizedKeysCommand/d' /etc/ssh/sshd_config
        echo -e "\nAuthorizedKeysCommand /usr/bin/sss_ssh_authorizedkeys\nAuthorizedKeysCommandUser nobody" >> /etc/ssh/sshd_config
        
        if systemctl list-unit-files | grep -q "^ssh.service"; then systemctl restart ssh || true
        else systemctl restart sshd || true; fi
    fi

    SSSD_CONF="/etc/sssd/sssd.conf"
    if [[ -f "$SSSD_CONF" ]]; then
        timeout 15s systemctl stop sssd || true
        
        if grep -q "^services" "$SSSD_CONF"; then
            sed -i 's/^services.*/services = nss, pam, ssh/' "$SSSD_CONF"
        else
            sed -i '/\[sssd\]/a services = nss, pam, ssh' "$SSSD_CONF"
        fi

        grep -q "access_provider" "$SSSD_CONF" && sed -i 's/access_provider.*/access_provider = simple/' "$SSSD_CONF" || sed -i '/\[domain/a access_provider = simple' "$SSSD_CONF"
        grep -q "simple_allow_groups" "$SSSD_CONF" && sed -i "s/simple_allow_groups.*/simple_allow_groups = ${ALLOWED_LOGIN_GROUP}/" "$SSSD_CONF" || sed -i "/access_provider = simple/a simple_allow_groups = ${ALLOWED_LOGIN_GROUP}" "$SSSD_CONF"
        
        sed -i '/ldap_user_ssh_public_key/d' "$SSSD_CONF"
        sed -i '/ldap_user_extra_attrs/d' "$SSSD_CONF"
        sed -i '/\[domain/a ldap_user_extra_attrs = info:sshPublicKey\nldap_user_ssh_public_key = info' "$SSSD_CONF"
        
        sed -i 's/use_fully_qualified_names.*/use_fully_qualified_names = False/' "$SSSD_CONF"
        sed -i 's/fallback_homedir.*/fallback_homedir = \/home\/%u/' "$SSSD_CONF"
        
        sed -i '/ignore_group_members/d' "$SSSD_CONF"
        sed -i '/subdomain_enumerate/d' "$SSSD_CONF"
        sed -i '/\[domain/a ignore_group_members = True\nsubdomain_enumerate = False' "$SSSD_CONF"

        timeout 30s systemctl start sssd || true
        
        if command -v sss_cache &>/dev/null; then sss_cache -E || true; fi
    fi
}

###############################################################################
# 5. MODULAR COMPONENTS
###############################################################################

mod_docker() {
    step "Installing & Configuring Docker"

    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if [[ ! -f /etc/yum.repos.d/docker-ce.repo ]]; then
            run_retry $PKG install -y yum-utils
            if [[ "$OS_ID" == "fedora" ]]; then
                run_retry yum-config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
            else
                run_retry yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
            fi
        fi
        $PKG remove -y podman buildah docker docker-client docker-common docker-engine >/dev/null 2>&1 || true
    elif [[ "$PKG" == "apt-get" ]]; then
        if [[ ! -f /etc/apt/sources.list.d/docker.list ]]; then
            source /etc/os-release
            REPO_OS=${ID}
            case "$REPO_OS" in
                debian|ubuntu) : ;;
                *) REPO_OS="ubuntu" ;;
            esac
            REPO_CODENAME="${VERSION_CODENAME:-$(command -v lsb_release >/dev/null 2>&1 && lsb_release -cs || echo stable)}"

            install -m 0755 -d /etc/apt/keyrings
            run_retry curl -fsSL "https://download.docker.com/linux/${REPO_OS}/gpg" -o /etc/apt/keyrings/docker.asc
            chmod a+r /etc/apt/keyrings/docker.asc

            echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${REPO_OS} ${REPO_CODENAME} stable" > /etc/apt/sources.list.d/docker.list
            apt-get update -qq || true
        fi
    fi

    if ! command -v docker &>/dev/null; then
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm docker docker-compose docker-buildx
        else run_retry $PKG install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin; fi
    fi

    mkdir -p /etc/docker
    cat > /etc/docker/daemon.json <<EOF
{
  "insecure-registries": [ ${INSECURE_REGISTRIES} ]
}
EOF

    mkdir -p /etc/systemd/system/docker.service.d
    cat > /etc/systemd/system/docker.service.d/http-proxy.conf <<EOF
[Service]
Environment="HTTP_PROXY=${PROXY_URL}"
Environment="HTTPS_PROXY=${PROXY_URL}"
Environment="NO_PROXY=${NO_PROXY_LIST}"
EOF

    systemctl daemon-reload || true
    timeout 30s systemctl enable --now docker || true
    timeout 60s systemctl restart docker || true

    timeout 15s usermod -aG docker root 2>/dev/null || true
    if timeout 15s id "$LOCAL_USER" &>/dev/null; then timeout 15s usermod -aG docker "$LOCAL_USER" 2>/dev/null || true; fi

    mkdir -p /root/.docker
    cat > /root/.docker/config.json <<EOF
{
  "proxies": {
    "default": {
      "httpProxy": "${PROXY_URL}",
      "httpsProxy": "${PROXY_URL}",
      "noProxy": "${NO_PROXY_LIST}"
    }
  }
}
EOF

    if timeout 15s id "$LOCAL_USER" &>/dev/null; then
        USER_HOME=$(eval echo ~$LOCAL_USER)
        mkdir -p "$USER_HOME/.docker"
        cp /root/.docker/config.json "$USER_HOME/.docker/config.json"
        chown -R "$LOCAL_USER:$LOCAL_USER" "$USER_HOME/.docker" || true
    fi
}

mod_lazydocker() {
    step "Installing LazyDocker"
    if ! command -v lazydocker &>/dev/null; then
        run_retry curl -sSL https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bash
    fi
}

mod_web_stack() {
    step "Installing Web Stack (PHP, Nginx, Node)"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if ! rpm -q remi-release >/dev/null 2>&1; then
            if [[ "$OS_ID" == "fedora" ]]; then
                run_retry dnf install -y "https://rpms.remirepo.net/fedora/remi-release-${VERSION_MAJOR}.rpm"
            elif [[ "$PKG" == "dnf" ]]; then
                run_retry $PKG install -y "https://rpms.remirepo.net/enterprise/remi-release-${VERSION_MAJOR}.rpm"
            else
                run_retry $PKG install -y http://rpms.remirepo.net/enterprise/remi-release-7.rpm yum-utils
            fi
        fi
        $PKG clean packages >/dev/null 2>&1 || true
        
        if [[ "$PKG" == "dnf" ]]; then
            $PKG module reset php -y || true
            $PKG module install -y php:remi-${PHP_VERSION}
        else
            yum-config-manager --enable remi-php83 || true
            $PKG install -y php php-cli php-fpm php-mysqlnd php-gd
        fi
        $PKG install -y java-${JAVA_VERSION}-openjdk nginx nodejs
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm php php-fpm php-gd php-pgsql nginx nodejs npm jre-openjdk
    else
        source /etc/os-release
        if [[ "$ID" == "debian" ]]; then
            if [[ ! -f /etc/apt/sources.list.d/sury-php.list ]]; then
                install -m 0755 -d /etc/apt/keyrings
                run_retry curl -fsSL https://packages.sury.org/php/apt.gpg -o /etc/apt/keyrings/sury-php.gpg
                chmod a+r /etc/apt/keyrings/sury-php.gpg
                PHP_CODENAME="${VERSION_CODENAME:-$(command -v lsb_release >/dev/null 2>&1 && lsb_release -cs || echo bookworm)}"
                echo "deb [signed-by=/etc/apt/keyrings/sury-php.gpg] https://packages.sury.org/php/ ${PHP_CODENAME} main" > /etc/apt/sources.list.d/sury-php.list
                apt-get update -qq || true
            fi
        else
            if ! grep -q "ondrej/php" /etc/apt/sources.list.d/* 2>/dev/null; then run_retry add-apt-repository -y ppa:ondrej/php; fi
            apt-get update -qq || true
        fi
        run_retry apt-get install -y php${PHP_VERSION} php${PHP_VERSION}-{cli,fpm,mysql,gd,mbstring,xml,curl,zip}
        run_retry apt-get install -y "openjdk-${JAVA_VERSION}-jdk" || { log "openjdk-${JAVA_VERSION} unavailable; installing default-jdk"; run_retry apt-get install -y default-jdk; }
        run_retry apt-get install -y nginx nodejs npm
    fi

    if command -v php &>/dev/null; then
        find /etc/php* -name "php.ini" 2>/dev/null | while read -r INI_FILE; do
            sed -i '/^http_proxy/d; /^https_proxy/d' "$INI_FILE"
            echo -e "\n; Proxy Settings\nhttp_proxy = \"${PROXY_URL}\"\nhttps_proxy = \"${PROXY_URL}\"" >> "$INI_FILE"
            if grep -q "allow_url_fopen" "$INI_FILE"; then sed -i 's/^allow_url_fopen.*/allow_url_fopen = On/' "$INI_FILE"
            else echo "allow_url_fopen = On" >> "$INI_FILE"; fi
        done
        if systemctl list-unit-files | grep -q php-fpm; then timeout 30s systemctl restart php-fpm || true; fi
        if systemctl list-unit-files | grep -q php${PHP_VERSION}-fpm; then timeout 30s systemctl restart php${PHP_VERSION}-fpm || true; fi
    fi
}

mod_db_stack() {
    step "Installing Databases"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if [[ ! -f /etc/yum.repos.d/mariadb.repo ]]; then
            if [[ "$OS_ID" == "fedora" ]]; then DB_OS="fedora"; else DB_OS="rhel"; fi
            cat > /etc/yum.repos.d/mariadb.repo <<EOF
[mariadb]
name = MariaDB
baseurl = https://rpm.mariadb.org/${MARIADB_VERSION}/${DB_OS}/\$releasever/\$basearch
module_hotfixes=1
gpgkey=https://rpm.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
EOF
        fi
        run_retry $PKG install -y MariaDB-server MariaDB-client postgresql-server
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm mariadb postgresql
    else
        run_retry apt-get install -y mariadb-server postgresql
    fi
}

mod_cockpit() {
    step "Installing Cockpit"
    if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y cockpit cockpit-storaged cockpit-pcp cockpit-packagekit
    elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm cockpit
    else run_retry $PKG install -y cockpit cockpit-storaged cockpit-pcp 2>/dev/null || run_retry $PKG install -y cockpit; fi
    
    mkdir -p /etc/systemd/system/cockpit.service.d
    echo -e "[Service]\nEnvironment=\"HTTP_PROXY=${PROXY_URL}\"\nEnvironment=\"HTTPS_PROXY=${PROXY_URL}\"\nEnvironment=\"NO_PROXY=${NO_PROXY_LIST}\"" > /etc/systemd/system/cockpit.service.d/proxy.conf
    systemctl daemon-reload || true
    timeout 30s systemctl enable --now cockpit.socket || true
}

mod_cleanup() {
    step "Final Cleanup & Hardening"
    
    if command -v apt-get &>/dev/null; then rm -f /etc/apt/apt.conf.d/80proxy; fi
    if command -v dnf &>/dev/null; then sed -i '/^proxy=/d' /etc/dnf/dnf.conf 2>/dev/null || true; fi

    if command -v tmux &>/dev/null; then $PKG remove -y tmux 2>/dev/null || true; fi
    rm -f /etc/tmux.conf
    
    if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y fish fail2ban; 
    elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm fish fail2ban;
    else run_retry $PKG install -y fish fail2ban; fi
    
    systemctl disable systemd-networkd-wait-online.service 2>/dev/null || true
    systemctl mask systemd-networkd-wait-online.service 2>/dev/null || true
    if [[ -f /etc/rc.d/rc.local ]]; then chmod +x /etc/rc.d/rc.local; fi
    if grep -q "172.16.21.16" /etc/fstab; then sed -i '/172.16.21.16/d' /etc/fstab; fi
    
    if systemctl is-failed sssd-nss.socket &>/dev/null; then
        systemctl reset-failed || true
        timeout 30s systemctl restart sssd || true
    fi

    ESCAPED_GROUP=$(echo "$AD_SUDO_GROUP" | sed 's/ /\\ /g')
    mkdir -p /etc/sudoers.d
    echo "%${ESCAPED_GROUP} ALL=(ALL) NOPASSWD: ALL" > "/etc/sudoers.d/10-ad-admins"
    chmod 440 "/etc/sudoers.d/10-ad-admins"

    cat > /etc/fail2ban/jail.local <<EOF
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3
bantime = 3600
EOF
    timeout 30s systemctl enable --now fail2ban || true
}

###############################################################################
# 6. CLI ROUTER
###############################################################################
detect_and_fix_os 

show_help() {
    echo "Usage: $0 [OPTION]"
    echo "Supported: RHEL/CentOS/Alma/Rocky/Fedora (dnf/yum), Ubuntu/Debian/Zorin (apt), Arch (pacman)."
    echo ""
    echo "Core Deployment:"
    echo "  --basics        Proxy, Certs, Repos, Network, Firewalld, AD, Cleanup."
    echo "  --full          Everything (Basics + GUI + Docker + Web/DB + Flatpak + Cockpit)."
    echo ""
    echo "Modular Execution:"
    echo "  --docker        Install and configure Docker Engine with Proxy/Subnets."
    echo "  --flatpak       Configure Flatpak, Flathub, and GUI App Centers (GNOME/KDE)."
    echo "  --ad-join       Run the SSSD and Realmd AD Join sequence."
    echo "  --certs         Mount CIFS, fetch root cert, update CA trust."
    echo "  --gui-proxy     Configure dconf (GNOME/Cinnamon), KDE, and Firefox Proxies."
    echo "  --proxy-tool    Install the 'toggle-proxy' dynamic CLI tool."
    echo "  --web-stack     Install PHP, Nginx, Node, and Java."
    echo "  --db-stack      Install MariaDB and PostgreSQL."
    echo "  --tools         Install zsh, fish, neovim, git, nano, lazydocker."
    echo "  --resize-home   Shrink LVM /home to ${HOME_TARGET_SIZE} (Backup/Restore)."
    echo ""
}

if [[ $# -eq 0 ]]; then show_help; exit 0; fi

while [[ "$#" -gt 0 ]]; do
    case $1 in
        --basics) mod_proxy; mod_clock_fix; mod_certs; mod_base_repos; mod_base_tools; mod_network; mod_firewall; mod_domain_users; mod_cleanup ;;
        --full) mod_proxy; mod_gui_proxy; mod_proxy_toggle; mod_clock_fix; mod_certs; mod_base_repos; mod_base_tools; mod_flatpak; mod_network; mod_firewall; mod_domain_users; mod_docker; mod_web_stack; mod_db_stack; mod_cockpit; mod_cleanup ;;
        
        --docker) mod_proxy; mod_docker ;;
        --flatpak) mod_proxy; mod_flatpak ;;
        --ad-join) mod_domain_users ;;
        --certs) mod_certs ;;
        --gui-proxy) mod_gui_proxy ;;
        --proxy-tool) mod_proxy_toggle ;;
        --web-stack) mod_proxy; mod_web_stack ;;
        --db-stack) mod_proxy; mod_db_stack ;;
        --tools) mod_proxy; mod_base_tools; mod_lazydocker ;;
        --resize-home) mod_resize_home ;;
        
        *) echo "Unknown option: $1"; show_help; exit 1 ;;
    esac
    shift
done

echo -e "\n${GREEN}[$(date +'%H:%M:%S')] === Setup Complete ===${NC}"

 

master_script.sh - v52
#!/usr/bin/env bash
#
# MASTER INFRASTRUCTURE SETUP
# Version: v52
# Enhancements: Bash Function Variable Scoping, Initialization Headers, Strict Idempotence
#
###############################################################################
# 1. CONFIGURATION
###############################################################################
SCRIPT_VERSION="v52"

DOMAIN_FQDN="m21.gov.local"
DOMAIN_ALT="m21.gov.tt"
DOMAIN_SHORT="M21"
DC_DNS_IP="172.16.21.161"
NTP_SERVER="172.16.121.9"
TARGET_TIMEZONE="America/Port_of_Spain"

# File Server Info
FILE_SERVER_IP="172.16.21.16"
FILE_SERVER_NAME="fileserver2"

# Proxy
PROXY_URL="http://172.40.4.14:8080"

# Docker Subnets & Internal Container Hostnames 
# (Bypasses proxy loopback for generic stacks and specific FOSS microservices)
DOCKER_NO_PROXY="172.17.0.0/16,172.18.0.0/16,172.19.0.0/16,172.20.0.0/16,172.21.0.0/16,web,api,app,db,database,redis,postgres,mysql,minio,mq,cache,admin,live,proxy,edrive,nextcloud,huly,cockroach,zammad,glpi,authentik,peertube,npm,zoraxy"

NO_PROXY_LIST="127.0.0.1,localhost,localhost.localdomain,${DOMAIN_FQDN},${DOMAIN_ALT},.${DOMAIN_FQDN},.${DOMAIN_ALT},${DC_DNS_IP},172.30.0.0/20,172.26.21.0/24,10.21.0.0/21,172.16.121.0/24,${DOCKER_NO_PROXY}"

# Docker Settings
INSECURE_REGISTRIES='"172.16.121.119:5000", "docker-repo.msya.gov.tt"'

# AD Access Control
AD_SUDO_GROUP="ICT Staff SG M21"
ALLOWED_LOGIN_GROUP="ICT Staff SG M21"

# Share Credentials
SHARE_PATH="//172.16.21.16/fileserver2"
SHARE_USER="Cipher.m21"
SHARE_PASS=")\ly; 634'NJ%i+"
CERT_SOURCE_PATH="/General/IT FILES/prx/Gortt_certificate_V4.cer"
TARGET_CERT_NAME="GORTT_Root_Exp2029"

# Failsafe User
LOCAL_USER="pcsupport"
LOCAL_PASS="ProIT321*"

# LVM Settings
HOME_TARGET_SIZE="8G"

# Versions
PHP_VERSION="8.3"
JAVA_VERSION="21"
MARIADB_VERSION="10.11"

###############################################################################
# 2. HELPER FUNCTIONS
###############################################################################
set -e
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; BLUE='\033[0;34m'; NC='\033[0m'

log() { echo -e "${BLUE}[$(date +'%H:%M:%S')] [INFO]${NC} $1"; }
step() { echo -e "\n${YELLOW}[$(date +'%H:%M:%S')] >>> $1${NC}"; }
success() { echo -e "${GREEN}[$(date +'%H:%M:%S')] [OK]${NC} $1"; }
error() { echo -e "${RED}[$(date +'%H:%M:%S')] [ERROR]${NC} $1"; }
init_header() { echo -e "\n${BLUE}====================================================${NC}\n${GREEN} Starting Master Infrastructure Setup ${SCRIPT_VERSION} ${NC}\n${BLUE}====================================================${NC}\n"; }

run_retry() {
    local n=1; local max=3; local delay=2
    while true; do
        "$@" && return 0
        if [[ $n -lt $max ]]; then
            ((n++)); log "Command failed. Retrying ($n/$max)..."; sleep $delay
        else
            return 1
        fi
    done
}

###############################################################################
# 3. PRE-FLIGHT CHECKS
###############################################################################
detect_and_fix_os() {
    if [[ ! -f /etc/os-release ]]; then error "Cannot detect OS. /etc/os-release missing."; exit 1; fi
    source /etc/os-release
    OS_ID=$(echo "$ID" | tr '[:upper:]' '[:lower:]')
    VERSION_MAJOR=$(echo "$VERSION_ID" | cut -d. -f1)
    
    if timeout 10s systemctl is-active --quiet packagekit.service 2>/dev/null; then
        timeout 15s systemctl stop packagekit.service || true
    fi
    
    if [[ "$OS_ID" == "centos" && "$VERSION_MAJOR" == "7" ]]; then
        PKG="yum"
        if grep -q "linux/rhel" /etc/yum.repos.d/docker-ce.repo 2>/dev/null; then rm -f /etc/yum.repos.d/docker-ce.repo; fi
        if [ ! -f /etc/yum.repos.d/CentOS-Base.repo.backup ]; then
            cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2>/dev/null || true
            run_retry curl -o /etc/yum.repos.d/CentOS-Base.repo https://el7.repo.almalinux.org/centos/CentOS-Base.repo
        fi
    elif [[ "$OS_ID" =~ (rhel|centos|almalinux|rocky|fedora) ]]; then PKG="dnf"
    elif [[ "$OS_ID" =~ (ubuntu|debian|zorin) ]]; then PKG="apt-get"; export DEBIAN_FRONTEND=noninteractive
    elif [[ "$OS_ID" == "arch" || "$ID_LIKE" == *"arch"* ]]; then PKG="pacman"; run_retry pacman -Sy
    else error "Unsupported OS: $OS_ID"; exit 1; fi
}

###############################################################################
# 4. CORE MODULES
###############################################################################

mod_proxy() {
    step "Configuring System Proxy"
    
    # 1. Base Environment Variables
    cat > /etc/profile.d/proxy.sh <<EOF
export http_proxy="${PROXY_URL}"
export https_proxy="${PROXY_URL}"
export ftp_proxy="${PROXY_URL}"
export no_proxy="${NO_PROXY_LIST}"
export HTTP_PROXY="${PROXY_URL}"
export HTTPS_PROXY="${PROXY_URL}"
export FTP_PROXY="${PROXY_URL}"
export NO_PROXY="${NO_PROXY_LIST}"
EOF
    source /etc/profile.d/proxy.sh

    # 2. Sudo Variable Passthrough
    mkdir -p /etc/sudoers.d
    echo 'Defaults env_keep += "http_proxy https_proxy ftp_proxy no_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY NO_PROXY"' > /etc/sudoers.d/10-proxy-env
    chmod 440 /etc/sudoers.d/10-proxy-env

    # 3. Package Manager Initial Forced Proxy
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        CONF_FILE="/etc/dnf/dnf.conf"
        [[ ! -f "$CONF_FILE" ]] && CONF_FILE="/etc/yum.conf"
        grep -q "proxy=" "$CONF_FILE" 2>/dev/null || echo "proxy=${PROXY_URL}" >> "$CONF_FILE"
        if ! grep -q "minrate" "$CONF_FILE" 2>/dev/null; then
            echo -e "timeout=60\nretries=10\nminrate=1" >> "$CONF_FILE"
        fi
    elif [[ "$PKG" == "apt-get" ]]; then
        echo -e "Acquire::http::Proxy \"${PROXY_URL}\";\nAcquire::https::Proxy \"${PROXY_URL}\";" > /etc/apt/apt.conf.d/80proxy
    fi

    # 4. Inject Proxy into Systemd DBus services
    for SVC in packagekit flatpak-system-helper; do
        mkdir -p /etc/systemd/system/${SVC}.service.d
        cat > /etc/systemd/system/${SVC}.service.d/http-proxy.conf <<EOF
[Service]
Environment="HTTP_PROXY=${PROXY_URL}"
Environment="HTTPS_PROXY=${PROXY_URL}"
Environment="NO_PROXY=${NO_PROXY_LIST}"
EOF
    done
    systemctl daemon-reload
    
    # Force hard-kill so they respawn instantly with new proxy settings
    killall packagekitd 2>/dev/null || true
    killall flatpak-system-helper 2>/dev/null || true
    systemctl restart packagekit flatpak-system-helper 2>/dev/null || true
}

mod_gui_proxy() {
    step "Configuring GUI Proxy Settings (System-Wide)"
    
    PROXY_HOST=$(echo "$PROXY_URL" | awk -F/ '{print $3}' | cut -d: -f1)
    PROXY_PORT=$(echo "$PROXY_URL" | awk -F: '{print $NF}')
    DCONF_NO_PROXY="['$(echo "$NO_PROXY_LIST" | sed "s/,/','/g")']"

    if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y dconf-cli
    elif [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then run_retry $PKG install -y dconf
    elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm dconf
    fi

    # 1. GNOME / Cinnamon / Mate
    mkdir -p /etc/dconf/profile
    mkdir -p /etc/dconf/db/local.d
    echo -e "user-db:user\nsystem-db:local" > /etc/dconf/profile/user

    cat > /etc/dconf/db/local.d/01-proxy <<EOF
[system/proxy]
mode='manual'
ignore-hosts=${DCONF_NO_PROXY}

[system/proxy/http]
host='${PROXY_HOST}'
port=${PROXY_PORT}

[system/proxy/https]
host='${PROXY_HOST}'
port=${PROXY_PORT}

[system/proxy/ftp]
host='${PROXY_HOST}'
port=${PROXY_PORT}
EOF
    dconf update || log "Warning: dconf update failed, GUI settings may require reboot."

    # 2. KDE Plasma
    mkdir -p /etc/xdg
    cat > /etc/xdg/kioslaverc <<EOF
[Proxy Settings]
ProxyType=1
httpProxy=${PROXY_URL}
httpsProxy=${PROXY_URL}
ftpProxy=${PROXY_URL}
NoProxyFor=${NO_PROXY_LIST}
EOF

    # 3. Firefox Enterprise Policy Setup
    mkdir -p /etc/firefox/policies
    cat > /etc/firefox/policies/policies.json <<FFEOF
{
  "policies": {
    "Proxy": {
      "Mode": "manual",
      "HTTPProxy": "${PROXY_HOST}:${PROXY_PORT}",
      "HTTPSProxy": "${PROXY_HOST}:${PROXY_PORT}",
      "FTPProxy": "${PROXY_HOST}:${PROXY_PORT}",
      "Passthrough": "${NO_PROXY_LIST}"
    }
  }
}
FFEOF
}

mod_proxy_toggle() {
    step "Installing Proxy Toggle Tool"
    
    cat > /usr/local/bin/toggle-proxy <<EOF
#!/usr/bin/env bash
# System-Wide Proxy Toggle
# Usage: sudo toggle-proxy [on|off]

if [[ "\$EUID" -ne 0 ]]; then
  echo "Please run as root (sudo toggle-proxy on|off)"
  exit 1
fi

MODE=\$1
PROXY_URL="${PROXY_URL}"
PROXY_HOST="\$(echo "\$PROXY_URL" | awk -F/ '{print \$3}' | cut -d: -f1)"
PROXY_PORT="\$(echo "\$PROXY_URL" | awk -F: '{print \$NF}')"
NO_PROXY_LIST="${NO_PROXY_LIST}"

# Scrub hardcoded package manager proxies in BOTH states
if command -v apt-get &>/dev/null; then rm -f /etc/apt/apt.conf.d/80proxy; fi
if command -v dnf &>/dev/null; then sed -i '/^proxy=/d' /etc/dnf/dnf.conf 2>/dev/null || true; fi

if [[ "\$MODE" == "on" ]]; then
    echo "Enabling System Proxy..."
    
    # Environment Variables
    cat > /etc/profile.d/proxy.sh <<ENVEOF
export http_proxy="\${PROXY_URL}"
export https_proxy="\${PROXY_URL}"
export ftp_proxy="\${PROXY_URL}"
export no_proxy="\${NO_PROXY_LIST}"
export HTTP_PROXY="\${PROXY_URL}"
export HTTPS_PROXY="\${PROXY_URL}"
export FTP_PROXY="\${PROXY_URL}"
export NO_PROXY="\${NO_PROXY_LIST}"
ENVEOF

    # Systemd DBus & Daemon Proxies
    for SVC in docker packagekit flatpak-system-helper; do
        mkdir -p /etc/systemd/system/\${SVC}.service.d
        cat > /etc/systemd/system/\${SVC}.service.d/http-proxy.conf <<DOCKEREOF
[Service]
Environment="HTTP_PROXY=\${PROXY_URL}"
Environment="HTTPS_PROXY=\${PROXY_URL}"
Environment="NO_PROXY=\${NO_PROXY_LIST}"
DOCKEREOF
    done
    systemctl daemon-reload
    killall packagekitd 2>/dev/null || true
    killall flatpak-system-helper 2>/dev/null || true
    systemctl restart docker packagekit flatpak-system-helper 2>/dev/null || true

    # GUI Configuration
    if command -v dconf &>/dev/null; then
        mkdir -p /etc/dconf/db/local.d
        sed -i "s/mode='none'/mode='manual'/" /etc/dconf/db/local.d/01-proxy 2>/dev/null || true
        dconf update
    fi
    if [[ -f /etc/xdg/kioslaverc ]]; then
        sed -i "s/ProxyType=0/ProxyType=1/" /etc/xdg/kioslaverc 2>/dev/null || true
    fi
    
    mkdir -p /etc/firefox/policies
    cat > /etc/firefox/policies/policies.json <<FFEOF
{
  "policies": {
    "Proxy": {
      "Mode": "manual",
      "HTTPProxy": "\${PROXY_HOST}:\${PROXY_PORT}",
      "HTTPSProxy": "\${PROXY_HOST}:\${PROXY_PORT}",
      "FTPProxy": "\${PROXY_HOST}:\${PROXY_PORT}",
      "Passthrough": "\${NO_PROXY_LIST}"
    }
  }
}
FFEOF

    echo "[OK] Proxy is ON. Log out and back in for all terminal sessions to update."

elif [[ "\$MODE" == "off" ]]; then
    echo "Disabling System Proxy..."
    
    > /etc/profile.d/proxy.sh

    # Remove Systemd Proxy Overrides
    rm -f /etc/systemd/system/docker.service.d/http-proxy.conf
    rm -f /etc/systemd/system/packagekit.service.d/http-proxy.conf
    rm -f /etc/systemd/system/flatpak-system-helper.service.d/http-proxy.conf
    systemctl daemon-reload
    
    # Hard kill daemons to clear memory
    killall packagekitd 2>/dev/null || true
    killall flatpak-system-helper 2>/dev/null || true
    systemctl restart docker flatpak-system-helper 2>/dev/null || true

    # PackageKit SQLite Trap Fix
    if command -v sqlite3 &>/dev/null && [ -f /var/lib/PackageKit/transactions.db ]; then
        sqlite3 /var/lib/PackageKit/transactions.db "DELETE FROM proxy;" || true
    else
        rm -f /var/lib/PackageKit/transactions.db || true
    fi
    systemctl restart packagekit 2>/dev/null || true

    # GUI Configuration Scrub
    if command -v dconf &>/dev/null; then
        mkdir -p /etc/dconf/db/local.d
        sed -i "s/mode='manual'/mode='none'/" /etc/dconf/db/local.d/01-proxy 2>/dev/null || true
        dconf update
    fi
    if [[ -f /etc/xdg/kioslaverc ]]; then
        sed -i "s/ProxyType=1/ProxyType=0/" /etc/xdg/kioslaverc 2>/dev/null || true
    fi

    mkdir -p /etc/firefox/policies
    cat > /etc/firefox/policies/policies.json <<FFEOF
{
  "policies": {
    "Proxy": {
      "Mode": "none"
    }
  }
}
FFEOF

    echo "[OK] Proxy is OFF. Log out and back in for all terminal sessions to update."
else
    echo "Usage: toggle-proxy [on|off]"
fi
EOF

    chmod +x /usr/local/bin/toggle-proxy
}

mod_flatpak() {
    step "Configuring Flatpak & Flathub"
    
    # Foolproof DE Detection (Idempotent)
    HAS_GNOME=false
    HAS_KDE=false
    if command -v gnome-shell &>/dev/null || (command -v dpkg &>/dev/null && dpkg -l | grep -q "gnome-shell") || (command -v rpm &>/dev/null && rpm -q gnome-shell &>/dev/null); then HAS_GNOME=true; fi
    if command -v plasmashell &>/dev/null || (command -v dpkg &>/dev/null && dpkg -l | grep -q "plasma-workspace") || (command -v rpm &>/dev/null && rpm -q plasma-workspace &>/dev/null); then HAS_KDE=true; fi

    if [[ "$PKG" == "apt-get" ]]; then
        run_retry apt-get install -y flatpak
        if [ "$HAS_GNOME" = true ]; then run_retry apt-get install -y gnome-software-plugin-flatpak; fi
        if [ "$HAS_KDE" = true ]; then run_retry apt-get install -y plasma-discover-backend-flatpak; fi
    elif [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        run_retry $PKG install -y flatpak
        if [ "$HAS_GNOME" = true ]; then run_retry $PKG install -y gnome-software; fi
        if [ "$HAS_KDE" = true ]; then run_retry $PKG install -y plasma-discover-flatpak; fi
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm flatpak
        if [ "$HAS_GNOME" = true ]; then run_retry pacman -S --noconfirm gnome-software; fi
        if [ "$HAS_KDE" = true ]; then run_retry pacman -S --noconfirm discover; fi
    fi

    # Explicitly enforce Proxy variables for Flatpak DBus operations safely (Idempotent)
    HTTP_PROXY="${PROXY_URL}" HTTPS_PROXY="${PROXY_URL}" run_retry flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
}

mod_desktop_tools() {
    step "Installing Desktop Utilities & GUI Tools"
    
    # 1. Fastfetch (Universal, Idempotent)
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        run_retry $PKG install -y fastfetch || run_retry $PKG install -y neofetch || true
    elif [[ "$PKG" == "apt-get" ]]; then
        run_retry apt-get install -y fastfetch || run_retry apt-get install -y neofetch || true
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm fastfetch || true
    fi
    
    # 2. GNOME Deep Integration
    if command -v gnome-shell &>/dev/null || (command -v dpkg &>/dev/null && dpkg -l | grep -q "gnome-shell") || (command -v rpm &>/dev/null && rpm -q gnome-shell &>/dev/null); then
        log "GNOME DE detected. Deploying Tweaks, Flatseal, and ExtensionManager..."
        
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y gnome-tweaks sqlite3
        elif [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then run_retry $PKG install -y gnome-tweaks sqlite
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm gnome-tweaks sqlite
        fi
        
        if command -v flatpak &>/dev/null; then
            # Bash scoping ensures run_retry function inherits these values natively for execution
            HTTP_PROXY="${PROXY_URL}" HTTPS_PROXY="${PROXY_URL}" run_retry flatpak install -y flathub com.mattjakeman.ExtensionManager
            HTTP_PROXY="${PROXY_URL}" HTTPS_PROXY="${PROXY_URL}" run_retry flatpak install -y flathub com.github.tchx84.Flatseal
        fi
    fi
}

mod_clock_fix() {
    step "Synchronizing System Clock"
    timedatectl set-timezone "$TARGET_TIMEZONE" || true
    timedatectl set-ntp true || true
    if systemctl list-unit-files | grep -q systemd-timesyncd; then
        timeout 30s systemctl restart systemd-timesyncd || true
    fi
}

mod_certs() {
    step "Installing Certificates"
    MNT="/mnt/share_certs_tmp"
    mkdir -p "$MNT"
    
    if ! command -v mount.cifs &>/dev/null; then
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get update -qq >/dev/null 2>&1 || true; run_retry apt-get install -y cifs-utils
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm cifs-utils
        else run_retry $PKG install -y cifs-utils; fi
    fi

    if mountpoint -q "$MNT"; then umount -l "$MNT"; fi
    
    if timeout 30s mount -t cifs "$SHARE_PATH" "$MNT" -o username="$SHARE_USER",password="$SHARE_PASS",vers=3.0; then
        SOURCE_FULL="$MNT$CERT_SOURCE_PATH"
        TEMP_PEM="/tmp/${TARGET_CERT_NAME}_staging.pem"
        
        if [[ -f "$SOURCE_FULL" ]]; then
            if ! openssl x509 -inform der -in "$SOURCE_FULL" -out "$TEMP_PEM" 2>/dev/null; then cp "$SOURCE_FULL" "$TEMP_PEM"; fi
            
            if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
                cp "$TEMP_PEM" "/etc/pki/ca-trust/source/anchors/${TARGET_CERT_NAME}.pem"
                [[ "$VERSION_MAJOR" -lt 9 ]] && update-ca-trust force-enable 2>/dev/null || true
                update-ca-trust extract
            elif [[ "$PKG" == "pacman" ]]; then
                cp "$TEMP_PEM" "/etc/ca-certificates/trust-source/anchors/${TARGET_CERT_NAME}.crt"
                trust extract-compat
            else
                cp "$TEMP_PEM" "/usr/local/share/ca-certificates/${TARGET_CERT_NAME}.crt"
                update-ca-certificates
            fi
        fi
        timeout 15s umount "$MNT" || true
    fi
    rmdir "$MNT" 2>/dev/null || true
}

mod_base_repos() {
    step "Configuring Base OS Repositories"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if [[ "$OS_ID" == "fedora" ]]; then
            log "Setting up Fedora 3rd Party Repos (RPM Fusion & Workstation Repos)..."
            run_retry dnf install -y dnf-plugins-core fedora-workstation-repositories || true
            run_retry dnf install -y "https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-${VERSION_MAJOR}.noarch.rpm" \
                                     "https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-${VERSION_MAJOR}.noarch.rpm" || true
            dnf config-manager --set-enabled rpmfusion-free rpmfusion-nonfree || true
        else
            if ! rpm -q epel-release >/dev/null 2>&1; then run_retry $PKG install -y epel-release; fi
            if [[ "$PKG" == "dnf" ]]; then
                if ! dnf repolist enabled 2>/dev/null | grep -E "crb|powertools" >/dev/null; then
                    run_retry $PKG install -y 'dnf-command(config-manager)'
                    $PKG config-manager --set-enabled crb 2>/dev/null || $PKG config-manager --set-enabled powertools 2>/dev/null || true
                fi
            fi
        fi
    elif [[ "$PKG" == "apt-get" ]]; then
        export DEBIAN_FRONTEND=noninteractive
        rm -f /etc/apt/sources.list.d/45drives.list
        apt-get update -qq || true
        BASE_APT_PKGS="curl wget gnupg lsb-release ca-certificates"
        if [[ "$OS_ID" != "debian" ]]; then BASE_APT_PKGS="software-properties-common $BASE_APT_PKGS"; fi
        run_retry apt-get install -y $BASE_APT_PKGS
    fi
}

mod_base_tools() {
    step "Installing Base System Tools"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        PACKAGES="git curl wget nano neovim zsh util-linux-user bind-utils net-tools openssl policycoreutils-python-utils psmisc PackageKit pcp pcp-conf pcp-libs pcp-selinux"
        run_retry $PKG install -y $PACKAGES
    elif [[ "$PKG" == "pacman" ]]; then
        PACKAGES="git curl wget nano neovim zsh openssl net-tools bind psmisc networkmanager"
        run_retry pacman -S --noconfirm $PACKAGES
        timeout 30s systemctl enable --now NetworkManager || true
    else
        PACKAGES="git curl wget nano neovim zsh openssl net-tools dnsutils psmisc packagekit pcp network-manager"
        run_retry apt-get install -y $PACKAGES
        timeout 30s systemctl enable --now NetworkManager || true
    fi
    systemctl unmask packagekit 2>/dev/null || true
    timeout 30s systemctl start packagekit 2>/dev/null || true
}

mod_network() {
    step "Configuring Network & DNS"
    if [[ "$PKG" == "apt-get" ]] && command -v netplan >/dev/null 2>&1; then
        if ls /etc/netplan/*.yaml >/dev/null 2>&1 && grep -q "addresses:" /etc/netplan/*.yaml; then
            log "Static Netplan detected. Skipping wipe to prevent lockout."
        else
            mkdir -p /etc/netplan
            cat > /etc/netplan/01-network-manager-all.yaml <<EOF
network:
  version: 2
  renderer: NetworkManager
EOF
            netplan apply || true
        fi
    fi

    sed -i "/${DOMAIN_FQDN}/d; /${DOMAIN_ALT}/d; /${DC_DNS_IP}/d; /${FILE_SERVER_NAME}/d" /etc/hosts
    cat >> /etc/hosts <<EOF
${DC_DNS_IP}    ${DOMAIN_FQDN} ${DOMAIN_ALT} ${DOMAIN_SHORT}
${FILE_SERVER_IP}    ${FILE_SERVER_NAME}.${DOMAIN_FQDN} ${FILE_SERVER_NAME}.${DOMAIN_ALT} ${FILE_SERVER_NAME}
EOF

    if [[ -L /etc/resolv.conf ]]; then rm -f /etc/resolv.conf; fi
    echo -e "search ${DOMAIN_FQDN} ${DOMAIN_ALT}\nnameserver ${DC_DNS_IP}" > /etc/resolv.conf

    if command -v nmcli &>/dev/null; then
        TARGET_IFACE=$(ip -4 -o addr show | grep "172.16." | awk '{print $2}' | head -n1)
        if [[ -n "$TARGET_IFACE" ]]; then
            CONN=$(nmcli -t -f NAME,DEVICE con show --active | grep ":${TARGET_IFACE}" | cut -d: -f1 | head -n1)
            if [[ -n "$CONN" ]]; then
                nmcli con mod "$CONN" ipv4.dns "$DC_DNS_IP" ipv4.dns-search "${DOMAIN_FQDN},${DOMAIN_ALT}" ipv4.ignore-auto-dns yes
                timeout 15s nmcli con up "$CONN" >/dev/null 2>&1
            fi
        fi
    fi

    echo -e "net.ipv6.conf.all.disable_ipv6 = 1\nnet.ipv6.conf.default.disable_ipv6 = 1" > /etc/sysctl.d/90-disable-ipv6.conf
    sysctl --system &>/dev/null || true

    if command -v systemctl &>/dev/null; then
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y chrony; CHRONY_CONF="/etc/chrony/chrony.conf"
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm chrony; CHRONY_CONF="/etc/chrony.conf"
        else run_retry $PKG install -y chrony; CHRONY_CONF="/etc/chrony.conf"; fi
        
        if [[ -f "$CHRONY_CONF" ]]; then
            sed -i '/server/d; /pool/d' "$CHRONY_CONF" 2>/dev/null || true
            echo "server ${NTP_SERVER} iburst" >> "$CHRONY_CONF"
        fi
        timeout 30s systemctl restart chronyd 2>/dev/null || timeout 30s systemctl restart chrony || true
    fi
}

mod_firewall() {
    step "Configuring Firewalld (Defense in Depth)"
    if [[ "$PKG" == "apt-get" ]]; then
        run_retry apt-get install -y firewalld
        systemctl disable ufw --now 2>/dev/null || true
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm firewalld
    else
        run_retry $PKG install -y firewalld
    fi

    systemctl enable --now firewalld

    firewall-cmd --permanent --zone=trusted --add-source=172.17.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=172.18.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=172.19.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=172.20.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=192.168.250.0/24

    firewall-cmd --permanent --add-service=http
    firewall-cmd --permanent --add-service=https

    firewall-cmd --permanent --remove-service=ssh
    firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.21.0.0/21" service name="ssh" accept'
    firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.16.121.0/24" service name="ssh" accept'
    firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.16.21.0/24" service name="ssh" accept'

    firewall-cmd --reload
}

mod_resize_home() {
    step "LVM Home Resizer"
    if ! command -v lvs &>/dev/null; then return; fi
    if ! mountpoint -q /home; then return; fi
    HOME_DEV=$(findmnt -n -o SOURCE /home)
    if [[ "$HOME_DEV" != *"/mapper/"* ]]; then return; fi

    LV_NAME=$(lvs --noheadings -o lv_name "$HOME_DEV" | tr -d ' ')
    VG_NAME=$(lvs --noheadings -o vg_name "$HOME_DEV" | tr -d ' ')
    LV_PATH="/dev/$VG_NAME/$LV_NAME"
    ROOT_LV_PATH="/dev/$VG_NAME/root" 
    MAPPER_PATH="/dev/mapper/${VG_NAME}-${LV_NAME}"

    CURRENT_SIZE=$(lvs --noheadings -o lv_size --units g "$LV_PATH" 2>/dev/null | tr -d 'g ' || lvs --noheadings -o L_SIZE --units g "$LV_PATH" | tr -d 'g ')
    if [[ ${CURRENT_SIZE%.*} -le 9 ]]; then return; fi

    tar czf /tmp/home_backup.tar.gz -C /home .
    fuser -km /home || true
    timeout 30s umount /home || timeout 15s umount -l /home || true

    lvremove -y "$LV_PATH"
    lvcreate -L "$HOME_TARGET_SIZE" -n "$LV_NAME" "$VG_NAME" -y
    mkfs.ext4 "$LV_PATH"
    
    sed -i '/\/home/d' /etc/fstab
    echo "$MAPPER_PATH /home ext4 defaults 0 0" >> /etc/fstab
    systemctl daemon-reload || true
    
    timeout 30s mount /home || true
    tar xzf /tmp/home_backup.tar.gz -C /home
    if command -v restorecon &>/dev/null; then restorecon -R /home; fi
    
    lvextend -l +100%FREE "$ROOT_LV_PATH"
    xfs_growfs / || resize2fs "$ROOT_LV_PATH" || true
    rm -f /tmp/home_backup.tar.gz
}

mod_domain_users() {
    step "Domain Join & User Setup"
    
    if ! timeout 15s id "$LOCAL_USER" &>/dev/null; then timeout 15s useradd -m -s /bin/bash "$LOCAL_USER" || true; fi
    echo "$LOCAL_USER:$LOCAL_PASS" | chpasswd || true
    timeout 15s usermod -aG sudo "$LOCAL_USER" 2>/dev/null || timeout 15s usermod -aG wheel "$LOCAL_USER" 2>/dev/null || true

    if [[ "$PKG" == "apt-get" ]]; then 
        run_retry apt-get install -y realmd sssd sssd-tools libnss-sss libpam-sss adcli packagekit
        if ! grep -q "pam_mkhomedir.so" /etc/pam.d/common-session; then
            echo "session optional pam_mkhomedir.so skel=/etc/skel umask=077" >> /etc/pam.d/common-session
        fi
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm sssd adcli smbclient
        if ! command -v realm &>/dev/null; then
            log "Warning: 'realmd' is not in standard Arch repos. Please install it via AUR (e.g., yay -S realmd) to join the domain later."
        fi
    else 
        run_retry $PKG install -y realmd sssd oddjob oddjob-mkhomedir adcli samba-common-tools
    fi

    if ! ping -c 1 -W 2 "$DOMAIN_FQDN" &>/dev/null; then error "DNS setup failed. Cannot join domain."; return; fi

    if command -v update-crypto-policies &>/dev/null; then
        update-crypto-policies --set DEFAULT:AD-SUPPORT >/dev/null 2>&1 || true
    fi

    if command -v realm &>/dev/null; then
        if ! timeout 15s realm list | grep -q "$DOMAIN_FQDN"; then
            echo -e "\n${YELLOW}Enter AD Admin Username (e.g., ent_joeld):${NC}"
            read -p "User: " JOIN_USER
            realm join --verbose --user="$JOIN_USER" "$DOMAIN_FQDN"
        else
            success "Already joined. Enforcing state..."
        fi
    fi

    if ! command -v sshd &>/dev/null || [[ ! -f /etc/ssh/sshd_config ]]; then
        log "OpenSSH Server missing or unconfigured. Installing explicitly..."
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y openssh-server
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm openssh
        else run_retry $PKG install -y openssh-server; fi
        
        if systemctl list-unit-files | grep -q "^ssh.service"; then
            systemctl enable ssh --now || true
        else
            systemctl enable sshd --now || true
        fi
        sleep 2
    fi

    if [[ ! -f /etc/ssh/sshd_config ]]; then
        error "/etc/ssh/sshd_config still not found after installation attempts. SSH AD key injection bypassed."
    else
        log "Configuring SSH daemon for AD-based keys..."
        sed -i '/AuthorizedKeysCommand/d' /etc/ssh/sshd_config
        echo -e "\nAuthorizedKeysCommand /usr/bin/sss_ssh_authorizedkeys\nAuthorizedKeysCommandUser nobody" >> /etc/ssh/sshd_config
        
        if systemctl list-unit-files | grep -q "^ssh.service"; then systemctl restart ssh || true
        else systemctl restart sshd || true; fi
    fi

    SSSD_CONF="/etc/sssd/sssd.conf"
    if [[ -f "$SSSD_CONF" ]]; then
        timeout 15s systemctl stop sssd || true
        
        if grep -q "^services" "$SSSD_CONF"; then
            sed -i 's/^services.*/services = nss, pam, ssh/' "$SSSD_CONF"
        else
            sed -i '/\[sssd\]/a services = nss, pam, ssh' "$SSSD_CONF"
        fi

        grep -q "access_provider" "$SSSD_CONF" && sed -i 's/access_provider.*/access_provider = simple/' "$SSSD_CONF" || sed -i '/\[domain/a access_provider = simple' "$SSSD_CONF"
        grep -q "simple_allow_groups" "$SSSD_CONF" && sed -i "s/simple_allow_groups.*/simple_allow_groups = ${ALLOWED_LOGIN_GROUP}/" "$SSSD_CONF" || sed -i "/access_provider = simple/a simple_allow_groups = ${ALLOWED_LOGIN_GROUP}" "$SSSD_CONF"
        
        sed -i '/ldap_user_ssh_public_key/d' "$SSSD_CONF"
        sed -i '/ldap_user_extra_attrs/d' "$SSSD_CONF"
        sed -i '/\[domain/a ldap_user_extra_attrs = info:sshPublicKey\nldap_user_ssh_public_key = info' "$SSSD_CONF"
        
        sed -i 's/use_fully_qualified_names.*/use_fully_qualified_names = False/' "$SSSD_CONF"
        sed -i 's/fallback_homedir.*/fallback_homedir = \/home\/%u/' "$SSSD_CONF"
        
        sed -i '/ignore_group_members/d' "$SSSD_CONF"
        sed -i '/subdomain_enumerate/d' "$SSSD_CONF"
        sed -i '/\[domain/a ignore_group_members = True\nsubdomain_enumerate = False' "$SSSD_CONF"

        if ! grep -q "offline_credentials_expiration" "$SSSD_CONF"; then
            sed -i '/\[domain/a cache_credentials = True\noffline_credentials_expiration = 0\naccount_cache_expiration = 2' "$SSSD_CONF"
        fi

        timeout 30s systemctl start sssd || true
        
        if command -v sss_cache &>/dev/null; then sss_cache -E || true; fi
    fi
}

###############################################################################
# 5. MODULAR COMPONENTS
###############################################################################

mod_docker() {
    step "Installing & Configuring Docker"

    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if [[ ! -f /etc/yum.repos.d/docker-ce.repo ]]; then
            run_retry $PKG install -y yum-utils
            if [[ "$OS_ID" == "fedora" ]]; then
                run_retry yum-config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
            else
                run_retry yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
            fi
        fi
        $PKG remove -y podman buildah docker docker-client docker-common docker-engine >/dev/null 2>&1 || true
    elif [[ "$PKG" == "apt-get" ]]; then
        if [[ ! -f /etc/apt/sources.list.d/docker.list ]]; then
            source /etc/os-release
            REPO_OS=${ID}
            case "$REPO_OS" in
                debian|ubuntu) : ;;
                *) REPO_OS="ubuntu" ;;
            esac
            REPO_CODENAME="${VERSION_CODENAME:-$(command -v lsb_release >/dev/null 2>&1 && lsb_release -cs || echo stable)}"

            install -m 0755 -d /etc/apt/keyrings
            run_retry curl -fsSL "https://download.docker.com/linux/${REPO_OS}/gpg" -o /etc/apt/keyrings/docker.asc
            chmod a+r /etc/apt/keyrings/docker.asc

            echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${REPO_OS} ${REPO_CODENAME} stable" > /etc/apt/sources.list.d/docker.list
            apt-get update -qq || true
        fi
    fi

    if ! command -v docker &>/dev/null; then
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm docker docker-compose docker-buildx
        else run_retry $PKG install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin; fi
    fi

    mkdir -p /etc/docker
    cat > /etc/docker/daemon.json <<EOF
{
  "insecure-registries": [ ${INSECURE_REGISTRIES} ]
}
EOF

    mkdir -p /etc/systemd/system/docker.service.d
    cat > /etc/systemd/system/docker.service.d/http-proxy.conf <<EOF
[Service]
Environment="HTTP_PROXY=${PROXY_URL}"
Environment="HTTPS_PROXY=${PROXY_URL}"
Environment="NO_PROXY=${NO_PROXY_LIST}"
EOF

    systemctl daemon-reload || true
    timeout 30s systemctl enable --now docker || true
    timeout 60s systemctl restart docker || true

    timeout 15s usermod -aG docker root 2>/dev/null || true
    if timeout 15s id "$LOCAL_USER" &>/dev/null; then timeout 15s usermod -aG docker "$LOCAL_USER" 2>/dev/null || true; fi

    mkdir -p /root/.docker
    cat > /root/.docker/config.json <<EOF
{
  "proxies": {
    "default": {
      "httpProxy": "${PROXY_URL}",
      "httpsProxy": "${PROXY_URL}",
      "noProxy": "${NO_PROXY_LIST}"
    }
  }
}
EOF

    if timeout 15s id "$LOCAL_USER" &>/dev/null; then
        USER_HOME=$(eval echo ~$LOCAL_USER)
        mkdir -p "$USER_HOME/.docker"
        cp /root/.docker/config.json "$USER_HOME/.docker/config.json"
        chown -R "$LOCAL_USER:$LOCAL_USER" "$USER_HOME/.docker" || true
    fi
}

mod_lazydocker() {
    step "Installing LazyDocker"
    if ! command -v lazydocker &>/dev/null; then
        run_retry curl -sSL https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bash
    fi
}

mod_web_stack() {
    step "Installing Web Stack (PHP, Nginx, Node)"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if ! rpm -q remi-release >/dev/null 2>&1; then
            if [[ "$OS_ID" == "fedora" ]]; then
                run_retry dnf install -y "https://rpms.remirepo.net/fedora/remi-release-${VERSION_MAJOR}.rpm"
            elif [[ "$PKG" == "dnf" ]]; then
                run_retry $PKG install -y "https://rpms.remirepo.net/enterprise/remi-release-${VERSION_MAJOR}.rpm"
            else
                run_retry $PKG install -y http://rpms.remirepo.net/enterprise/remi-release-7.rpm yum-utils
            fi
        fi
        $PKG clean packages >/dev/null 2>&1 || true
        
        if [[ "$PKG" == "dnf" ]]; then
            $PKG module reset php -y || true
            $PKG module install -y php:remi-${PHP_VERSION}
        else
            yum-config-manager --enable remi-php83 || true
            $PKG install -y php php-cli php-fpm php-mysqlnd php-gd
        fi
        $PKG install -y java-${JAVA_VERSION}-openjdk nginx nodejs
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm php php-fpm php-gd php-pgsql nginx nodejs npm jre-openjdk
    else
        source /etc/os-release
        if [[ "$ID" == "debian" ]]; then
            if [[ ! -f /etc/apt/sources.list.d/sury-php.list ]]; then
                install -m 0755 -d /etc/apt/keyrings
                run_retry curl -fsSL https://packages.sury.org/php/apt.gpg -o /etc/apt/keyrings/sury-php.gpg
                chmod a+r /etc/apt/keyrings/sury-php.gpg
                PHP_CODENAME="${VERSION_CODENAME:-$(command -v lsb_release >/dev/null 2>&1 && lsb_release -cs || echo bookworm)}"
                echo "deb [signed-by=/etc/apt/keyrings/sury-php.gpg] https://packages.sury.org/php/ ${PHP_CODENAME} main" > /etc/apt/sources.list.d/sury-php.list
                apt-get update -qq || true
            fi
        else
            if ! grep -q "ondrej/php" /etc/apt/sources.list.d/* 2>/dev/null; then run_retry add-apt-repository -y ppa:ondrej/php; fi
            apt-get update -qq || true
        fi
        run_retry apt-get install -y php${PHP_VERSION} php${PHP_VERSION}-{cli,fpm,mysql,gd,mbstring,xml,curl,zip}
        run_retry apt-get install -y "openjdk-${JAVA_VERSION}-jdk" || { log "openjdk-${JAVA_VERSION} unavailable; installing default-jdk"; run_retry apt-get install -y default-jdk; }
        run_retry apt-get install -y nginx nodejs npm
    fi

    if command -v php &>/dev/null; then
        find /etc/php* -name "php.ini" 2>/dev/null | while read -r INI_FILE; do
            sed -i '/^http_proxy/d; /^https_proxy/d' "$INI_FILE"
            echo -e "\n; Proxy Settings\nhttp_proxy = \"${PROXY_URL}\"\nhttps_proxy = \"${PROXY_URL}\"" >> "$INI_FILE"
            if grep -q "allow_url_fopen" "$INI_FILE"; then sed -i 's/^allow_url_fopen.*/allow_url_fopen = On/' "$INI_FILE"
            else echo "allow_url_fopen = On" >> "$INI_FILE"; fi
        done
        if systemctl list-unit-files | grep -q php-fpm; then timeout 30s systemctl restart php-fpm || true; fi
        if systemctl list-unit-files | grep -q php${PHP_VERSION}-fpm; then timeout 30s systemctl restart php${PHP_VERSION}-fpm || true; fi
    fi
}

mod_db_stack() {
    step "Installing Databases"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if [[ ! -f /etc/yum.repos.d/mariadb.repo ]]; then
            if [[ "$OS_ID" == "fedora" ]]; then DB_OS="fedora"; else DB_OS="rhel"; fi
            cat > /etc/yum.repos.d/mariadb.repo <<EOF
[mariadb]
name = MariaDB
baseurl = https://rpm.mariadb.org/${MARIADB_VERSION}/${DB_OS}/\$releasever/\$basearch
module_hotfixes=1
gpgkey=https://rpm.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
EOF
        fi
        run_retry $PKG install -y MariaDB-server MariaDB-client postgresql-server
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm mariadb postgresql
    else
        run_retry apt-get install -y mariadb-server postgresql
    fi
}

mod_cockpit() {
    step "Installing Cockpit"
    if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y cockpit cockpit-storaged cockpit-pcp cockpit-packagekit
    elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm cockpit
    else run_retry $PKG install -y cockpit cockpit-storaged cockpit-pcp 2>/dev/null || run_retry $PKG install -y cockpit; fi
    
    mkdir -p /etc/systemd/system/cockpit.service.d
    echo -e "[Service]\nEnvironment=\"HTTP_PROXY=${PROXY_URL}\"\nEnvironment=\"HTTPS_PROXY=${PROXY_URL}\"\nEnvironment=\"NO_PROXY=${NO_PROXY_LIST}\"" > /etc/systemd/system/cockpit.service.d/proxy.conf
    systemctl daemon-reload || true
    timeout 30s systemctl enable --now cockpit.socket || true
}

mod_cleanup() {
    step "Final Cleanup & Hardening"
    
    if command -v apt-get &>/dev/null; then rm -f /etc/apt/apt.conf.d/80proxy; fi
    if command -v dnf &>/dev/null; then sed -i '/^proxy=/d' /etc/dnf/dnf.conf 2>/dev/null || true; fi

    if command -v tmux &>/dev/null; then $PKG remove -y tmux 2>/dev/null || true; fi
    rm -f /etc/tmux.conf
    
    if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y fish fail2ban; 
    elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm fish fail2ban;
    else run_retry $PKG install -y fish fail2ban; fi
    
    systemctl disable systemd-networkd-wait-online.service 2>/dev/null || true
    systemctl mask systemd-networkd-wait-online.service 2>/dev/null || true
    if [[ -f /etc/rc.d/rc.local ]]; then chmod +x /etc/rc.d/rc.local; fi
    if grep -q "172.16.21.16" /etc/fstab; then sed -i '/172.16.21.16/d' /etc/fstab; fi
    
    if systemctl is-failed sssd-nss.socket &>/dev/null; then
        systemctl reset-failed || true
        timeout 30s systemctl restart sssd || true
    fi

    ESCAPED_GROUP=$(echo "$AD_SUDO_GROUP" | sed 's/ /\\ /g')
    mkdir -p /etc/sudoers.d
    echo "%${ESCAPED_GROUP} ALL=(ALL) NOPASSWD: ALL" > "/etc/sudoers.d/10-ad-admins"
    chmod 440 "/etc/sudoers.d/10-ad-admins"

    cat > /etc/fail2ban/jail.local <<EOF
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3
bantime = 3600
EOF
    timeout 30s systemctl enable --now fail2ban || true
}

###############################################################################
# 6. CLI ROUTER
###############################################################################
detect_and_fix_os 

show_help() {
    echo "Usage: $0 [OPTION]"
    echo "Supported: RHEL/CentOS/Alma/Rocky/Fedora (dnf/yum), Ubuntu/Debian/Zorin (apt), Arch (pacman)."
    echo ""
    echo "Core Deployment:"
    echo "  --basics        Proxy, Certs, Repos, Network, Firewalld, AD, Cleanup."
    echo "  --full          Everything (Basics + GUI + Docker + Web/DB + Flatpak/Tools + Cockpit)."
    echo ""
    echo "Modular Execution:"
    echo "  --docker        Install and configure Docker Engine with Proxy/Subnets."
    echo "  --flatpak       Configure Flatpak, Flathub, and GUI App Centers (GNOME/KDE)."
    echo "  --ad-join       Run the SSSD and Realmd AD Join sequence."
    echo "  --certs         Mount CIFS, fetch root cert, update CA trust."
    echo "  --gui-proxy     Configure dconf (GNOME/Cinnamon), KDE, and Firefox Proxies."
    echo "  --proxy-tool    Install the 'toggle-proxy' dynamic CLI tool."
    echo "  --desktop-tools Install Fastfetch, GNOME Tweaks, Flatseal, ExtensionManager."
    echo "  --web-stack     Install PHP, Nginx, Node, and Java."
    echo "  --db-stack      Install MariaDB and PostgreSQL."
    echo "  --tools         Install zsh, fish, neovim, git, nano, lazydocker."
    echo "  --resize-home   Shrink LVM /home to ${HOME_TARGET_SIZE} (Backup/Restore)."
    echo ""
}

if [[ $# -eq 0 ]]; then show_help; exit 0; fi

init_header

while [[ "$#" -gt 0 ]]; do
    case $1 in
        --basics) mod_proxy; mod_clock_fix; mod_certs; mod_base_repos; mod_base_tools; mod_network; mod_firewall; mod_domain_users; mod_cleanup ;;
        --full) mod_proxy; mod_gui_proxy; mod_proxy_toggle; mod_clock_fix; mod_certs; mod_base_repos; mod_base_tools; mod_flatpak; mod_desktop_tools; mod_network; mod_firewall; mod_domain_users; mod_docker; mod_web_stack; mod_db_stack; mod_cockpit; mod_cleanup ;;
        
        --docker) mod_proxy; mod_docker ;;
        --flatpak) mod_proxy; mod_flatpak ;;
        --desktop-tools) mod_proxy; mod_desktop_tools ;;
        --ad-join) mod_domain_users ;;
        --certs) mod_certs ;;
        --gui-proxy) mod_gui_proxy ;;
        --proxy-tool) mod_proxy_toggle ;;
        --web-stack) mod_proxy; mod_web_stack ;;
        --db-stack) mod_proxy; mod_db_stack ;;
        --tools) mod_proxy; mod_base_tools; mod_lazydocker ;;
        --resize-home) mod_resize_home ;;
        
        *) echo "Unknown option: $1"; show_help; exit 1 ;;
    esac
    shift
done

echo -e "\n${GREEN}[$(date +'%H:%M:%S')] === Setup Complete ===${NC}"

 

master_script.sh - v61c
#!/usr/bin/env bash
# =============================================================================
# domainjoin.sh — v61 PATCH SET
# =============================================================================
# NOT a runnable script. These are drop-in replacements for specific functions
# in v60. Merge them one at a time and bump SCRIPT_VERSION to v61 when done.
#
# Merging by hand (rather than regenerating the whole script) is deliberate:
# v59 lost mod_domain_users in a full-file merge, which is why v60's integrity
# preflight exists. Don't repeat it.
#
# Each patch is independent. Order below is by severity.
# =============================================================================


###############################################################################
# PATCH 0 — CONFIG BLOCK CHANGES
###############################################################################
# Replace the single DC_DNS_IP with a health-probed list. Keep DC_DNS_IP as a
# derived value so nothing downstream breaks.

# --- REMOVE ---
#   DC_DNS_IP="172.16.21.161"
# --- ADD ---
DC_LIST="172.40.132.67 172.40.132.66 172.42.132.66 172.16.21.161"
DC_DNS_IP="${DC_DNS_IP:-${DC_LIST%% *}}"   # first entry; re-set by mod_network

# Credentials no longer live in this script. Create /etc/m21-setup.creds
# (chmod 600, root:root, NOT in Forgejo) containing:
#
#   SHARE_USER="Cipher.m21"
#   SHARE_PASS="..."
#   LOCAL_PASS="..."
#
CREDS_FILE="/etc/m21-setup.creds"
if [[ -f "$CREDS_FILE" ]]; then
    # shellcheck disable=SC1090
    source "$CREDS_FILE"
fi

# Distro-agnostic CA bundle path, resolved once.
SYSTEM_CA_BUNDLE=""
for _B in /etc/pki/tls/certs/ca-bundle.crt /etc/ssl/certs/ca-certificates.crt; do
    [[ -f "$_B" ]] && SYSTEM_CA_BUNDLE="$_B" && break
done
unset _B

# Production guard override, set by the router.
FORCE_PROD=false

# NOTE: NO_PROXY_LIST still contains 172.30.0.0/20 (the retired sport network).
# Harmless, but dead weight — remove it when convenient.


###############################################################################
# PATCH 1 — PRODUCTION GUARD  (new function)
###############################################################################
# Root cause of 2026-07-28: --basics was run three times against a live
# ministry file server during business hours. It reconfigures proxy, DNS,
# resolv.conf, firewalld and repos. Nothing in v60 stopped that.

guard_production() {
    if [[ "$FORCE_PROD" == true ]]; then
        warn "Production guard OVERRIDDEN by --i-know-this-is-production."
        warn "This host will have its proxy, DNS, firewalld and repos rewritten."
        sleep 3
        return 0
    fi

    local REASONS=() LISTEN P
    LISTEN=$(ss -lnt 2>/dev/null | awk 'NR>1 {print $4}' | sed 's/.*://' | sort -un)

    for P in 80 443 3306 5432; do
        if grep -qx "$P" <<< "$LISTEN"; then
            REASONS+=("something is serving on TCP/${P}")
        fi
    done
    [[ -d /var/www/html/nextcloud ]] && REASONS+=("Nextcloud webroot present")
    [[ -d /var/lib/mysql/nextcloud ]] && REASONS+=("Nextcloud database present")

    [[ ${#REASONS[@]} -eq 0 ]] && return 0

    error "PRODUCTION HOST DETECTED — refusing to run."
    local R
    for R in "${REASONS[@]}"; do echo "           - ${R}"; done
    error "This script rewrites proxy, DNS, /etc/resolv.conf, firewalld and repos."
    error "On a live host that is an outage, not a deployment."
    error ""
    error "If you are certain, re-run with:  $0 --i-know-this-is-production $*"
    exit 1
}


###############################################################################
# PATCH 2 — DC HEALTH PROBE  (new function)
###############################################################################
# 2026-07-28 lesson: 172.16.21.161 accepted TCP/389 all day and never answered
# a single LDAP query. `ping` and a bare TCP connect both said "healthy".
# Only an actual RootDSE query tells the truth.

probe_dc() {
    local IP="$1"

    if command -v ldapsearch &>/dev/null; then
        timeout 8s ldapsearch -x -LLL -H "ldap://${IP}" \
            -s base -b "" defaultNamingContext &>/dev/null && return 0
        return 1
    fi

    # Fallback only. This is the check that fooled us — it cannot detect a DC
    # that completes the TCP handshake and then goes silent. Install
    # openldap-clients (see PATCH 7) so this path is never taken.
    warn "ldapsearch unavailable — falling back to TCP probe (unreliable)."
    timeout 3s bash -c "exec 3<>/dev/tcp/${IP}/389" 2>/dev/null || return 1
    timeout 3s bash -c "exec 3<>/dev/tcp/${IP}/53"  2>/dev/null || return 1
    return 0
}


###############################################################################
# PATCH 3 — mod_network  (full replacement)
###############################################################################
# v60 problems this fixes:
#   1. Hardcoded a single DC into /etc/resolv.conf with ipv4.ignore-auto-dns.
#      When that DC died, every LDAP lookup on the box died with it.
#   2. `sed -i "/${DOMAIN_FQDN}/d"` — unescaped dots match any character, and
#      /${DC_DNS_IP}/d deleted ANY /etc/hosts line containing that string.
#   3. `nmcli con up "$CONN"` bounces the interface — drops your SSH session
#      and can silently reassign the firewalld zone.
#   4. TARGET_IFACE picked by grepping "172.16." — wrong interface on any
#      dual-homed host.
#   5. No backup of resolv.conf or hosts before rewriting.

mod_network() {
    step "Configuring Network & DNS"

    if [[ "$PKG" == "apt-get" ]] && command -v netplan >/dev/null 2>&1; then
        if ls /etc/netplan/*.yaml >/dev/null 2>&1 && grep -q "addresses:" /etc/netplan/*.yaml; then
            log "Static Netplan detected. Skipping wipe to prevent lockout."
        else
            mkdir -p /etc/netplan
            cat > /etc/netplan/01-network-manager-all.yaml <<EOF
network:
  version: 2
  renderer: NetworkManager
EOF
            netplan apply || true
        fi
    fi

    # --- Health-probe every DC before trusting any of them -------------------
    local HEALTHY_DCS=() IP
    for IP in $DC_LIST; do
        if probe_dc "$IP"; then
            HEALTHY_DCS+=("$IP")
            success "DC ${IP} answers LDAP"
        else
            warn "DC ${IP} did NOT answer LDAP — excluded from DNS/hosts"
        fi
    done

    if [[ ${#HEALTHY_DCS[@]} -eq 0 ]]; then
        error "No domain controller answered an LDAP query."
        error "Leaving /etc/resolv.conf and /etc/hosts UNTOUCHED."
        error "Escalate to the network team before re-running."
        return 1
    fi
    DC_DNS_IP="${HEALTHY_DCS[0]}"

    # --- /etc/hosts: managed block, not blind sed deletes --------------------
    cp -a /etc/hosts "/etc/hosts.m21.bak-$(date +%s)" 2>/dev/null || true
    sed -i '/# >>> m21-hosts >>>/,/# <<< m21-hosts <<</d' /etc/hosts
    {
        echo "# >>> m21-hosts >>>   managed by m21 setup — edits here are overwritten"
        echo "${DC_DNS_IP}    ${DOMAIN_FQDN} ${DOMAIN_ALT} ${DOMAIN_SHORT}"
        echo "${FILE_SERVER_IP}    ${FILE_SERVER_NAME}.${DOMAIN_FQDN} ${FILE_SERVER_NAME}.${DOMAIN_ALT} ${FILE_SERVER_NAME}"
        echo "# <<< m21-hosts <<<"
    } >> /etc/hosts

    # --- resolv.conf: every healthy DC, best first ---------------------------
    cp -a /etc/resolv.conf "/etc/resolv.conf.m21.bak-$(date +%s)" 2>/dev/null || true
    if [[ -L /etc/resolv.conf ]]; then rm -f /etc/resolv.conf; fi
    {
        echo "search ${DOMAIN_FQDN} ${DOMAIN_ALT}"
        for IP in "${HEALTHY_DCS[@]:0:3}"; do echo "nameserver ${IP}"; done
    } > /etc/resolv.conf

    # --- Persist on the NM profile WITHOUT bouncing the interface ------------
    # `nmcli con mod` writes ifcfg and takes effect at next activation. We
    # already wrote resolv.conf directly for immediate effect, so there is no
    # reason to drop the link (and the admin's SSH session) here.
    if command -v nmcli &>/dev/null; then
        local DEFAULT_IFACE CONN DNS_CSV
        DEFAULT_IFACE=$(ip -4 route show default | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1); exit}')
        if [[ -n "$DEFAULT_IFACE" ]]; then
            CONN=$(nmcli -t -f NAME,DEVICE con show --active | grep ":${DEFAULT_IFACE}$" | cut -d: -f1 | head -n1)
            if [[ -n "$CONN" ]]; then
                DNS_CSV=$(IFS=,; echo "${HEALTHY_DCS[*]:0:3}")
                nmcli con mod "$CONN" \
                    ipv4.dns "$DNS_CSV" \
                    ipv4.dns-search "${DOMAIN_FQDN},${DOMAIN_ALT}" \
                    ipv4.ignore-auto-dns yes || true
                log "DNS persisted on connection '${CONN}' (applies at next activation/reboot)."
                log "Interface NOT bounced — run 'nmcli con up ${CONN}' yourself if needed."
            fi
        fi
    fi

    echo -e "net.ipv6.conf.all.disable_ipv6 = 1\nnet.ipv6.conf.default.disable_ipv6 = 1" > /etc/sysctl.d/90-disable-ipv6.conf
    sysctl --system &>/dev/null || true

    if command -v systemctl &>/dev/null; then
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y chrony; CHRONY_CONF="/etc/chrony/chrony.conf"
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm chrony; CHRONY_CONF="/etc/chrony.conf"
        else run_retry $PKG install -y chrony; CHRONY_CONF="/etc/chrony.conf"; fi

        if [[ -f "$CHRONY_CONF" ]]; then
            cp -a "$CHRONY_CONF" "${CHRONY_CONF}.m21.bak-$(date +%s)" 2>/dev/null || true
            sed -i '/^server /d; /^pool /d' "$CHRONY_CONF" 2>/dev/null || true
            grep -q "^server ${NTP_SERVER} iburst" "$CHRONY_CONF" || echo "server ${NTP_SERVER} iburst" >> "$CHRONY_CONF"
        fi
        timeout 30s systemctl restart chronyd 2>/dev/null || timeout 30s systemctl restart chrony || true
    fi
}


###############################################################################
# PATCH 4 — CERTIFICATE HANDLING  (new helper + mod_certs replacement)
###############################################################################
# v60 installed /General/IT FILES/prx/Gortt_certificate_V4.cer into the trust
# anchors as "GORTT_Root_Exp2029.pem". That file is NOT a root:
#
#   subject= /O=GORTT/CN=dc01intfw.gov.local
#   issuer=  /DC=local/DC=gov/CN=GORTT Enterprise Root CA v2.0
#
# It is the Check Point inspection appliance's LEAF cert. Installing a leaf as
# a trust anchor does nothing, and the misleading filename cost real debugging
# time. The actual root was already present as firewall-proxy.crt / GORTT.pem.

install_ca_anchor() {
    local SRC="$1" NAME="$2"
    local TMP="/tmp/${NAME}.$$.pem" DEST=""

    [[ -f "$SRC" ]] || { warn "Cert source not found: ${SRC}"; return 1; }

    # DER -> PEM, or pass through if already PEM
    if ! openssl x509 -inform der -in "$SRC" -out "$TMP" 2>/dev/null; then
        cp "$SRC" "$TMP"
    fi
    if ! openssl x509 -in "$TMP" -noout -subject &>/dev/null; then
        warn "${NAME}: not a parseable X.509 certificate. Skipping."
        rm -f "$TMP"; return 1
    fi

    # If the file holds a chain, note it — openssl x509 only reads the first.
    local COUNT
    COUNT=$(grep -c 'BEGIN CERTIFICATE' "$TMP" 2>/dev/null || echo 1)
    if [[ "$COUNT" -gt 1 ]]; then
        log "${NAME}: file contains ${COUNT} certificates; only the first is inspected."
        log "  Split with: openssl crl2pkcs7 -nocrl -certfile ${SRC} | openssl pkcs7 -print_certs"
    fi

    local SUBJ ISS
    SUBJ=$(openssl x509 -in "$TMP" -noout -subject 2>/dev/null); SUBJ="${SUBJ#subject=}"
    ISS=$(openssl  x509 -in "$TMP" -noout -issuer  2>/dev/null); ISS="${ISS#issuer=}"

    # Refuse leaves and intermediates.
    if [[ "$SUBJ" != "$ISS" ]]; then
        warn "${NAME}: NOT self-signed — this is a leaf or intermediate, not a root CA."
        warn "  subject:${SUBJ}"
        warn "  issuer: ${ISS}"
        warn "  Installing it as a trust anchor has no effect. SKIPPING."
        warn "  Point CERT_SOURCE_PATH at the actual root CA on the share."
        rm -f "$TMP"; return 1
    fi

    # Refuse expired.
    if ! openssl x509 -in "$TMP" -noout -checkend 0 &>/dev/null; then
        warn "${NAME}: certificate is EXPIRED. SKIPPING."
        rm -f "$TMP"; return 1
    fi

    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        DEST="/etc/pki/ca-trust/source/anchors/${NAME}.pem"
    elif [[ "$PKG" == "pacman" ]]; then
        DEST="/etc/ca-certificates/trust-source/anchors/${NAME}.crt"
    else
        DEST="/usr/local/share/ca-certificates/${NAME}.crt"
    fi

    # Idempotent: byte-identical means nothing to do.
    if [[ -f "$DEST" ]] && cmp -s "$TMP" "$DEST"; then
        log "${NAME}: already installed and unchanged."
        rm -f "$TMP"
    else
        cp "$TMP" "$DEST"; rm -f "$TMP"
        if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
            [[ "$VERSION_MAJOR" -lt 9 ]] && update-ca-trust force-enable 2>/dev/null || true
            update-ca-trust extract
        elif [[ "$PKG" == "pacman" ]]; then
            trust extract-compat
        else
            update-ca-certificates
        fi
        success "${NAME}: root CA installed into system trust store."
    fi

    # ---- Application trust stores do NOT use the system bundle --------------
    # Nextcloud ships its own resources/config/ca-bundle.crt. Adding the CA to
    # /etc/pki does nothing for it — this is why every outbound HTTPS call from
    # Nextcloud failed with cURL 60 behind the inspecting proxy.
    local NCROOT
    for NCROOT in /var/www/html/nextcloud /var/www/nextcloud /usr/share/nextcloud; do
        [[ -f "${NCROOT}/occ" ]] || continue
        local WEBUSER="apache"
        id www-data &>/dev/null && WEBUSER="www-data"
        if sudo -u "$WEBUSER" php "${NCROOT}/occ" security:certificates:import "$DEST" &>/dev/null; then
            success "${NAME}: imported into Nextcloud trust store (${NCROOT})."
        else
            warn "${NAME}: Nextcloud import failed — run manually:"
            warn "  sudo -u ${WEBUSER} php ${NCROOT}/occ security:certificates:import ${DEST}"
        fi
        break
    done

    return 0
}

mod_certs() {
    step "Installing Certificates"
    local MNT="/mnt/share_certs_tmp"
    local CREDS="/root/.m21-share-creds.$$"
    mkdir -p "$MNT"

    if ! command -v mount.cifs &>/dev/null; then
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get update -qq >/dev/null 2>&1 || true; run_retry apt-get install -y cifs-utils
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm cifs-utils
        else run_retry $PKG install -y cifs-utils; fi
    fi

    if mountpoint -q "$MNT"; then umount -l "$MNT"; fi

    # Credentials via file, not argv — `mount -o password=...` is visible to
    # every user on the box via ps(1) for the duration of the mount.
    ( umask 077; printf 'username=%s\npassword=%s\n' "$SHARE_USER" "$SHARE_PASS" > "$CREDS" )
    trap 'rm -f "'"$CREDS"'"' RETURN

    if timeout 30s mount -t cifs "$SHARE_PATH" "$MNT" -o credentials="$CREDS",vers=3.0; then
        install_ca_anchor "${MNT}${CERT_SOURCE_PATH}" "$TARGET_CERT_NAME" || true
        timeout 15s umount "$MNT" || true
    else
        warn "Could not mount ${SHARE_PATH} — skipping certificate install."
    fi

    rm -f "$CREDS"
    rmdir "$MNT" 2>/dev/null || true
}


###############################################################################
# PATCH 5 — mod_web_stack: PHP CONFIG BLOCK  (replace the trailing if-block)
###############################################################################
# v60 appended this to every php.ini on every run:
#
#     ; Proxy Settings
#     http_proxy = "http://172.40.4.14:8080"
#     https_proxy = "http://172.40.4.14:8080"
#
# Two problems. It has no idempotency marker, so edrive's php.ini accumulated
# three copies. And http_proxy is not a PHP ini directive — PHP cannot set
# environment variables from php.ini, so those lines never did anything. PHP's
# proxying comes from the application (Nextcloud's config.php 'proxy' key).
#
# Worse: everything lived in the RPM-owned /etc/php.ini, so `yum remove php*`
# on 2026-07-28 renamed it to php.ini.rpmsave and the reinstall dropped a stock
# file. memory_limit fell to 128M and uploads to 2M on a live file server.
#
# --- REPLACE the `if command -v php &>/dev/null; then ... fi` block with: ---

    if command -v php &>/dev/null; then
        local PHP_D INI

        # Config in a drop-in that no RPM owns — survives `yum remove php*`.
        for PHP_D in /etc/php.d \
                     "/etc/php/${PHP_VERSION}/fpm/conf.d" \
                     "/etc/php/${PHP_VERSION}/cli/conf.d" \
                     "/etc/php/${PHP_VERSION}/apache2/conf.d"; do
            [[ -d "$PHP_D" ]] || continue
            cat > "${PHP_D}/99-m21.ini" <<EOF
; Managed by m21 setup ${SCRIPT_VERSION}. Not owned by any package.
; Deliberately NOT in php.ini — an RPM removal takes php.ini with it.
allow_url_fopen = On
curl.cainfo = ${SYSTEM_CA_BUNDLE}
openssl.cafile = ${SYSTEM_CA_BUNDLE}
EOF
        done

        # Remove the inert proxy cruft appended by v60 and earlier.
        while IFS= read -r INI; do
            [[ -f "$INI" ]] || continue
            if grep -q '^; Proxy Settings' "$INI"; then
                cp -a "$INI" "${INI}.m21.bak-$(date +%s)"
                sed -i '/^; Proxy Settings injected by Setup Script$/d
                        /^; Proxy Settings$/d
                        /^http_proxy = /d
                        /^https_proxy = /d' "$INI"
                log "Removed legacy proxy cruft from ${INI}"
            fi
        done < <(find /etc/php* -maxdepth 3 -name 'php.ini' 2>/dev/null)

        if systemctl list-unit-files | grep -q php-fpm; then timeout 30s systemctl restart php-fpm || true; fi
        if systemctl list-unit-files | grep -q "php${PHP_VERSION}-fpm"; then timeout 30s systemctl restart "php${PHP_VERSION}-fpm" || true; fi
    fi

# WARNING, unrelated to the above: mod_web_stack installs nginx. On a host
# already running Apache (edrive) that is a port-80 collision. Consider
# splitting nginx into its own --nginx flag, or guarding on `httpd -v`.


###############################################################################
# PATCH 6 — mod_domain_users: JOIN SEQUENCE  (replace two blocks)
###############################################################################

# --- REPLACE ---
#   if ! ping -c 1 -W 2 "$DOMAIN_FQDN" &>/dev/null; then error "DNS setup failed..."; return; fi
# --- WITH ---
    if ! getent hosts "$DOMAIN_FQDN" &>/dev/null; then
        error "DNS cannot resolve ${DOMAIN_FQDN}. Run --basics network module first."
        return 1
    fi
    if ! probe_dc "$(getent hosts "$DOMAIN_FQDN" | awk '{print $1}' | head -n1)"; then
        error "${DOMAIN_FQDN} resolves, but that DC does not answer LDAP."
        error "ICMP/TCP reachability is NOT sufficient — do not proceed."
        return 1
    fi

# --- REPLACE the realm join call ---
#   realm join --verbose --user="$JOIN_USER" "$DOMAIN_FQDN"
# --- WITH ---
    # --membership-software=adcli stops realmd from shelling out to
    # `net ads join`, which prompts for the password a SECOND time and echoes
    # it in cleartext because it does not inherit the no-echo terminal state.
    # (Observed 2026-07-28 — the credential ended up in a scrollback buffer.)
    realm join --verbose --membership-software=adcli --user="$JOIN_USER" "$DOMAIN_FQDN"

# If realmd discovery still times out, pin the DC and bypass realmd entirely:
#   kinit ent_joeld@M21.GOV.LOCAL
#   adcli join -D m21.gov.local -S <healthy-dc-ip> -U ent_joeld --verbose
# then let this module write sssd.conf on the next run.


###############################################################################
# PATCH 7 — mod_base_tools: ADD REQUIRED PACKAGES
###############################################################################
# probe_dc needs ldapsearch. Manual join recovery needs kinit. Neither was
# installed by v60, and both were needed under pressure on 2026-07-28.

# yum/dnf PACKAGES line — append:
#     openldap-clients krb5-workstation
# apt PACKAGES line — append:
#     ldap-utils krb5-user
# pacman PACKAGES line — append:
#     openldap krb5


###############################################################################
# PATCH 8 — mod_firewall: SSH LOCKOUT GUARD
###############################################################################
# v60 does `--permanent --remove-service=ssh` then adds three source-scoped
# rich rules. If your current SSH source is outside all three, the --reload at
# the end locks you out of a remote host with no console.

_ip2int() { local a b c d; IFS=. read -r a b c d <<< "$1"; echo $(( (a<<24)+(b<<16)+(c<<8)+d )); }

ip_in_cidr() {
    local IP="$1" CIDR="$2"
    local NET="${CIDR%/*}" BITS="${CIDR#*/}" MASK IPN NETN
    [[ "$IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 1
    MASK=$(( (0xFFFFFFFF << (32 - BITS)) & 0xFFFFFFFF ))
    IPN=$(_ip2int "$IP"); NETN=$(_ip2int "$NET")
    [[ $(( IPN & MASK )) -eq $(( NETN & MASK )) ]]
}

# --- INSERT into mod_firewall, immediately before --remove-service=ssh ---
    local SSH_ALLOWED_CIDRS="10.21.0.0/21 172.16.121.0/24 172.16.21.0/24"
    local MY_SRC="${SSH_CLIENT%% *}"
    if [[ -n "$MY_SRC" ]]; then
        local COVERED=false C
        for C in $SSH_ALLOWED_CIDRS; do
            if ip_in_cidr "$MY_SRC" "$C"; then COVERED=true; break; fi
        done
        if [[ "$COVERED" != true ]]; then
            error "Your SSH source ${MY_SRC} is not in any allowed range:"
            error "  ${SSH_ALLOWED_CIDRS}"
            error "Removing the ssh service would lock you out at --reload."
            error "Add your range to SSH_ALLOWED_CIDRS or run from console."
            return 1
        fi
        log "SSH source ${MY_SRC} is covered by the rich rules — safe to proceed."
    fi


###############################################################################
# PATCH 9 — mod_cleanup: STOP DESTROYING THINGS
###############################################################################

# --- REMOVE entirely (uninstalling a tool an admin may be mid-session in,
#     and deleting their config, is not "cleanup") ---
#   if command -v tmux &>/dev/null; then $PKG remove -y tmux 2>/dev/null || true; fi
#   rm -f /etc/tmux.conf

# --- REPLACE (blind fstab line deletion by IP substring) ---
#   if grep -q "172.16.21.16" /etc/fstab; then sed -i '/172.16.21.16/d' /etc/fstab; fi
# --- WITH ---
    if grep -q "${FILE_SERVER_IP}" /etc/fstab; then
        warn "/etc/fstab references ${FILE_SERVER_IP}:"
        grep -n "${FILE_SERVER_IP}" /etc/fstab | sed 's/^/           /'
        warn "NOT removing automatically — review and edit by hand if unwanted."
    fi

# --- REPLACE (jail.local is the admin's file; clobbering it discards any
#     custom jails) ---
#   cat > /etc/fail2ban/jail.local <<EOF ... EOF
# --- WITH ---
    mkdir -p /etc/fail2ban/jail.d
    cat > /etc/fail2ban/jail.d/10-m21-sshd.conf <<EOF
# Managed by m21 setup ${SCRIPT_VERSION}
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3
bantime = 3600
EOF


###############################################################################
# PATCH 10 — verify_modules: COVER THE NEW FUNCTIONS
###############################################################################
# Add to the FN list in verify_modules:
#
#     guard_production probe_dc install_ca_anchor ip_in_cidr _ip2int
#
# Also worth adding the helpers v60 omitted:
#
#     run_retry detect_de detect_and_fix_os init_header _chk


###############################################################################
# PATCH 11 — ROUTER: WIRE IN THE GUARD
###############################################################################

# --- INSERT before `detect_and_fix_os` in the router ---
for _ARG in "$@"; do
    if [[ "$_ARG" == "--i-know-this-is-production" ]]; then FORCE_PROD=true; fi
done
set -- "${@/--i-know-this-is-production/}"
unset _ARG

# --- ADD guard_production to every system-mutating route.
#     --doctor is read-only and must stay unguarded. ---
#
#   --basics)  guard_production "$@"; mod_proxy; mod_clock_fix; mod_certs; ...
#   --full)    guard_production "$@"; mod_proxy; mod_gui_proxy; ...
#   --certs)   guard_production "$@"; mod_certs ;;
#   --network) guard_production "$@"; mod_network ;;
#   ... etc for every route except --doctor
#
# --- ADD to show_help ---
#   echo "  --i-know-this-is-production   Bypass the production-host guard."


###############################################################################
# PATCH 12 — detect_and_fix_os: EXPLICIT PROXY ON THE REPO FETCH
###############################################################################
# detect_and_fix_os runs BEFORE mod_proxy, so /etc/profile.d/proxy.sh may not
# be sourced in a non-login shell and this curl can hang or fail.
#
# --- REPLACE ---
#   run_retry curl -o /etc/yum.repos.d/CentOS-Base.repo https://el7.repo.almalinux.org/centos/CentOS-Base.repo
# --- WITH ---
    run_retry curl -fsSL -x "${PROXY_URL}" \
        -o /etc/yum.repos.d/CentOS-Base.repo \
        https://el7.repo.almalinux.org/centos/CentOS-Base.repo


###############################################################################
# PATCH 13 — mod_doctor: ADDITIONAL CHECKS
###############################################################################
# Insert into the "Trust & Identity" section:

    echo -e "\n${YELLOW}-- Certificate Sanity --${NC}"
    local ANCHOR_DIR="/etc/pki/ca-trust/source/anchors"
    [[ -d /usr/local/share/ca-certificates ]] && ANCHOR_DIR="/usr/local/share/ca-certificates"
    local F S I
    for F in "${ANCHOR_DIR}"/*; do
        [[ -f "$F" ]] || continue
        S=$(openssl x509 -in "$F" -noout -subject 2>/dev/null); S="${S#subject=}"
        I=$(openssl x509 -in "$F" -noout -issuer  2>/dev/null); I="${I#issuer=}"
        [[ -z "$S" ]] && continue
        if ! openssl x509 -in "$F" -noout -checkend 0 &>/dev/null; then
            echo -e "  ${RED}[FAIL]${NC} $(basename "$F") is EXPIRED — remove it"
        elif [[ "$S" != "$I" ]]; then
            echo -e "  ${RED}[FAIL]${NC} $(basename "$F") is a LEAF, not a root CA — useless as an anchor"
        else
            echo -e "  ${GREEN}[PASS]${NC} $(basename "$F") — valid self-signed root"
        fi
    done

# Insert into the "Network & DNS" section:

    echo -e "\n${YELLOW}-- Domain Controllers --${NC}"
    for IP in $DC_LIST; do
        _chk "DC ${IP} answers LDAP RootDSE" probe_dc "$IP"
    done

# Insert a new section (Nextcloud hosts only):

    if [[ -f /var/www/html/nextcloud/occ ]]; then
        echo -e "\n${YELLOW}-- Nextcloud --${NC}"
        _chk "opcache loaded" bash -c "php -m 2>/dev/null | grep -qi 'Zend OPcache'"
        _chk "memory_limit >= 512M" bash -c "php -r 'exit(ini_get(\"memory_limit\")===\"-1\"||(int)ini_get(\"memory_limit\")>=512?0:1);'"
        _chk "php-smbclient present (external storage)" bash -c "php -m 2>/dev/null | grep -qi smbclient"
        _chk "m21 php drop-in present" test -f /etc/php.d/99-m21.ini
        _chk "CA present in Nextcloud trust store" bash -c "sudo -u apache php /var/www/html/nextcloud/occ security:certificates 2>/dev/null | grep -q 'Trinidad'"
    fi


# =============================================================================
# NOT PATCHED — worth a decision, not a code change
# =============================================================================
#
# mod_resize_home
#   `tar czf /tmp/home_backup.tar.gz` then `lvremove -y` with no free-space
#   check on /tmp. If /home exceeds free space on root, the backup truncates
#   and the lvremove still runs. Add a df guard and an interactive
#   confirmation, or drop the module — it is a one-time provisioning action
#   that does not belong in a script run repeatedly against live hosts.
#
# LOCAL_USER / LOCAL_PASS
#   The failsafe account is created with a fixed password on every host the
#   script touches. One leak is a credential for the whole estate. At minimum
#   move it to the creds file; better, install an SSH key and set the account
#   to no-password.
#
# NO_PROXY_LIST / DOCKER_NO_PROXY
#   Includes bare container hostnames (web, api, db, redis...). Harmless on the
#   host but noise. 172.30.0.0/20 is the retired sport network.
#
# Log file
#   /var/log/m21-setup.log is chmod 600, which is right. Keep it that way — the
#   ERR trap prints ${BASH_COMMAND} and would capture SHARE_PASS if a
#   credential-bearing command ever escaped its if/|| true guard.

 

master_script.sh - v59g
#!/usr/bin/env bash
#
# MASTER INFRASTRUCTURE SETUP
# Version: v59
#
# v59 Changelog:
#   - FIX: Restored all core server modules (Docker, Web, DB, Cockpit, Cleanup, Shell) 
#     that were accidentally truncated in previous revisions.
#   - ENHANCEMENT: Bulletproofed `mod_lazydocker` proxy routing. The install script
#     is now downloaded locally, executed with explicit proxy environment variables.
#   - FIX: 172.16.21.0/24 (Prism Central) added to NO_PROXY_LIST.
#
###############################################################################
# 1. CONFIGURATION
###############################################################################
SCRIPT_VERSION="v59"
LOG_FILE="/var/log/m21-setup.log"

DOMAIN_FQDN="m21.gov.local"
DOMAIN_ALT="m21.gov.tt"
DOMAIN_SHORT="M21"
DC_DNS_IP="172.16.21.161"
NTP_SERVER="172.16.121.9"
TARGET_TIMEZONE="America/Port_of_Spain"

# File Server Info
FILE_SERVER_IP="172.16.21.16"
FILE_SERVER_NAME="fileserver2"

# Proxy
PROXY_URL="http://172.40.4.14:8080"

# Docker Subnets & Internal Container Hostnames
DOCKER_NO_PROXY="172.17.0.0/16,172.18.0.0/16,172.19.0.0/16,172.20.0.0/16,172.21.0.0/16,web,api,app,db,database,redis,postgres,mysql,minio,mq,cache,admin,live,proxy,edrive,nextcloud,huly,cockroach,zammad,glpi,authentik,peertube,npm,zoraxy"

NO_PROXY_LIST="127.0.0.1,localhost,localhost.localdomain,${DOMAIN_FQDN},${DOMAIN_ALT},.${DOMAIN_FQDN},.${DOMAIN_ALT},${DC_DNS_IP},172.30.0.0/20,172.26.21.0/24,10.21.0.0/21,172.16.121.0/24,172.16.21.0/24,${DOCKER_NO_PROXY}"

# Docker Settings
INSECURE_REGISTRIES='"172.16.121.119:5000", "docker-repo.msya.gov.tt"'

# AD Access Control
AD_SUDO_GROUP="ICT Staff SG M21"
ALLOWED_LOGIN_GROUP="ICT Staff SG M21"

# Share Credentials
# WARNING: Never wrap commands containing these credentials in run_retry, and
# keep them inside if/|| true guards so the ERR trap doesn't log them to /var/log!
SHARE_PATH="//172.16.21.16/fileserver2"
SHARE_USER="Cipher.m21"
SHARE_PASS=")\ly; 634'NJ%i+"
CERT_SOURCE_PATH="/General/IT FILES/prx/Gortt_certificate_V4.cer"
TARGET_CERT_NAME="GORTT_Root_Exp2029"

# Failsafe User
LOCAL_USER="pcsupport"
LOCAL_PASS="ProIT321*"

# LVM Settings
HOME_TARGET_SIZE="8G"

# Versions
PHP_VERSION="8.3"
JAVA_VERSION="21"
MARIADB_VERSION="10.11"

# Systemd services that must inherit the proxy
PROXY_SERVICES="packagekit flatpak-system-helper fwupd"

###############################################################################
# 2. HELPER FUNCTIONS
###############################################################################
set -e
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; BLUE='\033[0;34m'; NC='\033[0m'

log() { echo -e "${BLUE}[$(date +'%H:%M:%S')] [INFO]${NC} $1"; }
step() { echo -e "\n${YELLOW}[$(date +'%H:%M:%S')] >>> $1${NC}"; }
success() { echo -e "${GREEN}[$(date +'%H:%M:%S')] [OK]${NC} $1"; }
error() { echo -e "${RED}[$(date +'%H:%M:%S')] [ERROR]${NC} $1"; }
warn() { echo -e "${YELLOW}[$(date +'%H:%M:%S')] [WARN]${NC} $1"; }

trap 'error "Script aborted at line ${LINENO} (last command: ${BASH_COMMAND})"' ERR
trap 'sleep 0.2' EXIT

run_retry() {
    local n=1; local max=3; local delay=2
    while true; do
        "$@" && return 0
        if [[ $n -lt $max ]]; then
            ((n++)); log "Command failed: [$*]. Retrying ($n/$max)..."; sleep $delay
        else
            error "Command failed permanently after ${max} attempts: [$*]"
            return 1
        fi
    done
}

pin_flatpak_proxy() {
    local REPO="/var/lib/flatpak/repo"
    if [[ ! -f "$REPO/config" ]]; then
        warn "Flatpak repo not initialized yet (${REPO}/config missing) — skipping proxy pin."
        return 0
    fi

    if ! command -v ostree &>/dev/null; then
        log "ostree CLI not installed (only ostree-libs) — installing for repo proxy pin..."
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y ostree || true
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm ostree || true
        else run_retry $PKG install -y ostree || true; fi
    fi

    if command -v ostree &>/dev/null; then
        ostree --repo="$REPO" config set 'remote "flathub".proxy' "${PROXY_URL}" || true
    else
        sed -i '/^\[remote "flathub"\]/,/^\[/{ /^proxy=/d }' "$REPO/config" 2>/dev/null || true
        sed -i "/^\[remote \"flathub\"\]/a proxy=${PROXY_URL}" "$REPO/config" 2>/dev/null || true
    fi

    if awk -v want="proxy=${PROXY_URL}" '
            /^\[remote "flathub"\]/ {f=1; next}
            /^\[/ {f=0}
            f && $0 == want {found=1}
            END {exit !found}
        ' "$REPO/config"; then
        success "flathub ostree proxy pinned: ${PROXY_URL}"
    else
        warn "FAILED to pin flathub proxy in ${REPO}/config — flatpak fetches will bypass the proxy and hang/fail."
    fi
}

###############################################################################
# 3. PRE-FLIGHT CHECKS
###############################################################################
detect_and_fix_os() {
    if [[ ! -f /etc/os-release ]]; then error "Cannot detect OS. /etc/os-release missing."; exit 1; fi
    source /etc/os-release
    OS_ID=$(echo "$ID" | tr '[:upper:]' '[:lower:]')
    OS_PRETTY="${PRETTY_NAME:-$ID $VERSION_ID}"
    VERSION_MAJOR=$(echo "$VERSION_ID" | cut -d. -f1)

    if timeout 10s systemctl is-active --quiet packagekit.service 2>/dev/null; then
        timeout 15s systemctl stop packagekit.service || true
    fi

    if [[ "$OS_ID" == "centos" && "$VERSION_MAJOR" == "7" ]]; then
        PKG="yum"
        if grep -q "linux/rhel" /etc/yum.repos.d/docker-ce.repo 2>/dev/null; then rm -f /etc/yum.repos.d/docker-ce.repo; fi
        if [ ! -f /etc/yum.repos.d/CentOS-Base.repo.backup ]; then
            cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2>/dev/null || true
            run_retry curl -o /etc/yum.repos.d/CentOS-Base.repo https://el7.repo.almalinux.org/centos/CentOS-Base.repo
        fi
    elif [[ "$OS_ID" =~ (rhel|centos|almalinux|rocky|fedora) ]]; then PKG="dnf"
    elif [[ "$OS_ID" =~ (ubuntu|debian|zorin) ]]; then PKG="apt-get"; export DEBIAN_FRONTEND=noninteractive
    elif [[ "$OS_ID" == "arch" || "$ID_LIKE" == *"arch"* ]]; then PKG="pacman"; run_retry pacman -Sy
    else error "Unsupported OS: $OS_ID"; exit 1; fi

    detect_de
}

detect_de() {
    HAS_GNOME=false
    HAS_KDE=false
    DETECTED_DE="Headless/Server"
    if command -v gnome-shell &>/dev/null \
        || (command -v dpkg &>/dev/null && dpkg -l 2>/dev/null | grep -q "gnome-shell") \
        || (command -v rpm &>/dev/null && rpm -q gnome-shell &>/dev/null); then
        HAS_GNOME=true; DETECTED_DE="GNOME"
    fi
    if command -v plasmashell &>/dev/null \
        || (command -v dpkg &>/dev/null && dpkg -l 2>/dev/null | grep -q "plasma-workspace") \
        || (command -v rpm &>/dev/null && rpm -q plasma-workspace &>/dev/null); then
        HAS_KDE=true
        if [ "$HAS_GNOME" = true ]; then DETECTED_DE="GNOME + KDE"; else DETECTED_DE="KDE Plasma"; fi
    fi
    if command -v cinnamon &>/dev/null; then DETECTED_DE="${DETECTED_DE/Headless\/Server/Cinnamon}"; fi
}

init_header() {
    local KERNEL ARCH HOST PRIMARY_IP PROXY_STATE JOINED UPT
    KERNEL=$(uname -r)
    ARCH=$(uname -m)
    HOST=$(hostname)
    PRIMARY_IP=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1); exit}')
    [[ -z "$PRIMARY_IP" ]] && PRIMARY_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
    UPT=$(uptime -p 2>/dev/null | sed 's/^up //')
    if [[ -s /etc/profile.d/proxy.sh ]]; then PROXY_STATE="ON (${PROXY_URL})"; else PROXY_STATE="OFF"; fi
    if command -v realm &>/dev/null && timeout 10s realm list 2>/dev/null | grep -q "$DOMAIN_FQDN"; then
        JOINED="Joined (${DOMAIN_FQDN})"
    else
        JOINED="Not joined"
    fi

    echo -e "\n${BLUE}=====================================================================${NC}"
    echo -e "${GREEN}  Master Infrastructure Setup ${SCRIPT_VERSION}${NC}"
    echo -e "${BLUE}=====================================================================${NC}"
    printf "  %-14s %s\n" "OS:"        "${OS_PRETTY} (${ARCH})"
    printf "  %-14s %s\n" "Kernel:"    "${KERNEL}"
    printf "  %-14s %s\n" "Hostname:" "${HOST}"
    printf "  %-14s %s\n" "IP:"        "${PRIMARY_IP:-unknown}"
    printf "  %-14s %s\n" "Desktop:"  "${DETECTED_DE}"
    printf "  %-14s %s\n" "Pkg Mgr:"  "${PKG}"
    printf "  %-14s %s\n" "Proxy:"    "${PROXY_STATE}"
    printf "  %-14s %s\n" "Domain:"   "${JOINED}"
    printf "  %-14s %s\n" "Uptime:"   "${UPT:-unknown}"
    printf "  %-14s %s\n" "Run at:"   "$(date '+%Y-%m-%d %H:%M:%S %Z')"
    echo -e "${BLUE}=====================================================================${NC}\n"
}

###############################################################################
# 4. CORE MODULES
###############################################################################

mod_proxy() {
    step "Configuring System Proxy"

    cat > /etc/profile.d/proxy.sh <<EOF
export http_proxy="${PROXY_URL}"
export https_proxy="${PROXY_URL}"
export ftp_proxy="${PROXY_URL}"
export no_proxy="${NO_PROXY_LIST}"
export HTTP_PROXY="${PROXY_URL}"
export HTTPS_PROXY="${PROXY_URL}"
export FTP_PROXY="${PROXY_URL}"
export NO_PROXY="${NO_PROXY_LIST}"
EOF
    source /etc/profile.d/proxy.sh

    sed -i -E '/^(http_proxy|https_proxy|ftp_proxy|no_proxy|HTTP_PROXY|HTTPS_PROXY|FTP_PROXY|NO_PROXY)=/d' /etc/environment 2>/dev/null || true
    cat >> /etc/environment <<EOF
http_proxy="${PROXY_URL}"
https_proxy="${PROXY_URL}"
ftp_proxy="${PROXY_URL}"
no_proxy="${NO_PROXY_LIST}"
HTTP_PROXY="${PROXY_URL}"
HTTPS_PROXY="${PROXY_URL}"
FTP_PROXY="${PROXY_URL}"
NO_PROXY="${NO_PROXY_LIST}"
EOF

    mkdir -p /etc/sudoers.d
    echo 'Defaults env_keep += "http_proxy https_proxy ftp_proxy no_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY NO_PROXY"' > /etc/sudoers.d/10-proxy-env
    chmod 440 /etc/sudoers.d/10-proxy-env

    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        CONF_FILE="/etc/dnf/dnf.conf"
        [[ ! -f "$CONF_FILE" ]] && CONF_FILE="/etc/yum.conf"
        grep -q "proxy=" "$CONF_FILE" 2>/dev/null || echo "proxy=${PROXY_URL}" >> "$CONF_FILE"
        if ! grep -q "minrate" "$CONF_FILE" 2>/dev/null; then
            echo -e "timeout=60\nretries=10\nminrate=1" >> "$CONF_FILE"
        fi
    elif [[ "$PKG" == "apt-get" ]]; then
        echo -e "Acquire::http::Proxy \"${PROXY_URL}\";\nAcquire::https::Proxy \"${PROXY_URL}\";" > /etc/apt/apt.conf.d/80proxy
    fi

    for SVC in ${PROXY_SERVICES}; do
        mkdir -p /etc/systemd/system/${SVC}.service.d
        cat > /etc/systemd/system/${SVC}.service.d/http-proxy.conf <<EOF
[Service]
Environment="HTTP_PROXY=${PROXY_URL}"
Environment="HTTPS_PROXY=${PROXY_URL}"
Environment="http_proxy=${PROXY_URL}"
Environment="https_proxy=${PROXY_URL}"
Environment="NO_PROXY=${NO_PROXY_LIST}"
Environment="no_proxy=${NO_PROXY_LIST}"
EOF
    done
    systemctl daemon-reload

    killall packagekitd 2>/dev/null || true
    killall flatpak-system-helper 2>/dev/null || true
    systemctl try-restart packagekit flatpak-system-helper fwupd 2>/dev/null || true
}

mod_gui_proxy() {
    step "Configuring GUI Proxy Settings (System-Wide)"

    PROXY_HOST=$(echo "$PROXY_URL" | awk -F/ '{print $3}' | cut -d: -f1)
    PROXY_PORT=$(echo "$PROXY_URL" | awk -F: '{print $NF}')
    DCONF_NO_PROXY="['$(echo "$NO_PROXY_LIST" | sed "s/,/','/g")']"

    if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y dconf-cli
    elif [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then run_retry $PKG install -y dconf
    elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm dconf
    fi

    mkdir -p /etc/dconf/profile
    mkdir -p /etc/dconf/db/local.d
    echo -e "user-db:user\nsystem-db:local" > /etc/dconf/profile/user

    cat > /etc/dconf/db/local.d/01-proxy <<EOF
[system/proxy]
mode='manual'
ignore-hosts=${DCONF_NO_PROXY}

[system/proxy/http]
host='${PROXY_HOST}'
port=${PROXY_PORT}

[system/proxy/https]
host='${PROXY_HOST}'
port=${PROXY_PORT}

[system/proxy/ftp]
host='${PROXY_HOST}'
port=${PROXY_PORT}
EOF
    dconf update || log "Warning: dconf update failed, GUI settings may require reboot."

    mkdir -p /etc/xdg
    cat > /etc/xdg/kioslaverc <<EOF
[Proxy Settings]
ProxyType=1
httpProxy=${PROXY_URL}
httpsProxy=${PROXY_URL}
ftpProxy=${PROXY_URL}
NoProxyFor=${NO_PROXY_LIST}
EOF

    mkdir -p /etc/firefox/policies
    cat > /etc/firefox/policies/policies.json <<FFEOF
{
  "policies": {
    "Proxy": {
      "Mode": "manual",
      "HTTPProxy": "${PROXY_HOST}:${PROXY_PORT}",
      "HTTPSProxy": "${PROXY_HOST}:${PROXY_PORT}",
      "FTPProxy": "${PROXY_HOST}:${PROXY_PORT}",
      "Passthrough": "${NO_PROXY_LIST}"
    }
  }
}
FFEOF
}

mod_proxy_toggle() {
    step "Installing Proxy Toggle Tool"

    cat > /usr/local/bin/toggle-proxy <<EOF
#!/usr/bin/env bash
# System-Wide Proxy Toggle (${SCRIPT_VERSION})
# Usage: sudo toggle-proxy [on|off]

if [[ "\$EUID" -ne 0 ]]; then
  echo "Please run as root (sudo toggle-proxy on|off)"
  exit 1
fi

MODE=\$1
PROXY_URL="${PROXY_URL}"
PROXY_HOST="\$(echo "\$PROXY_URL" | awk -F/ '{print \$3}' | cut -d: -f1)"
PROXY_PORT="\$(echo "\$PROXY_URL" | awk -F: '{print \$NF}')"
NO_PROXY_LIST="${NO_PROXY_LIST}"
PROXY_SERVICES="docker packagekit flatpak-system-helper fwupd"

if command -v apt-get &>/dev/null; then rm -f /etc/apt/apt.conf.d/80proxy; fi
if command -v dnf &>/dev/null; then sed -i '/^proxy=/d' /etc/dnf/dnf.conf 2>/dev/null || true; fi

if [[ "\$MODE" == "on" ]]; then
    echo "Enabling System Proxy..."

    cat > /etc/profile.d/proxy.sh <<ENVEOF
export http_proxy="\${PROXY_URL}"
export https_proxy="\${PROXY_URL}"
export ftp_proxy="\${PROXY_URL}"
export no_proxy="\${NO_PROXY_LIST}"
export HTTP_PROXY="\${PROXY_URL}"
export HTTPS_PROXY="\${PROXY_URL}"
export FTP_PROXY="\${PROXY_URL}"
export NO_PROXY="\${NO_PROXY_LIST}"
ENVEOF

    sed -i -E '/^(http_proxy|https_proxy|ftp_proxy|no_proxy|HTTP_PROXY|HTTPS_PROXY|FTP_PROXY|NO_PROXY)=/d' /etc/environment 2>/dev/null || true
    cat >> /etc/environment <<ENVEOF2
http_proxy="\${PROXY_URL}"
https_proxy="\${PROXY_URL}"
ftp_proxy="\${PROXY_URL}"
no_proxy="\${NO_PROXY_LIST}"
HTTP_PROXY="\${PROXY_URL}"
HTTPS_PROXY="\${PROXY_URL}"
FTP_PROXY="\${PROXY_URL}"
NO_PROXY="\${NO_PROXY_LIST}"
ENVEOF2

    mkdir -p /etc/fish/conf.d
    cat > /etc/fish/conf.d/proxy.fish <<FISHEOF
set -gx http_proxy "\${PROXY_URL}"
set -gx https_proxy "\${PROXY_URL}"
set -gx ftp_proxy "\${PROXY_URL}"
set -gx no_proxy "\${NO_PROXY_LIST}"
set -gx HTTP_PROXY "\${PROXY_URL}"
set -gx HTTPS_PROXY "\${PROXY_URL}"
set -gx FTP_PROXY "\${PROXY_URL}"
set -gx NO_PROXY "\${NO_PROXY_LIST}"
FISHEOF

    for SVC in \${PROXY_SERVICES}; do
        mkdir -p /etc/systemd/system/\${SVC}.service.d
        cat > /etc/systemd/system/\${SVC}.service.d/http-proxy.conf <<DOCKEREOF
[Service]
Environment="HTTP_PROXY=\${PROXY_URL}"
Environment="HTTPS_PROXY=\${PROXY_URL}"
Environment="http_proxy=\${PROXY_URL}"
Environment="https_proxy=\${PROXY_URL}"
Environment="NO_PROXY=\${NO_PROXY_LIST}"
Environment="no_proxy=\${NO_PROXY_LIST}"
DOCKEREOF
    done
    systemctl daemon-reload
    killall packagekitd 2>/dev/null || true
    killall flatpak-system-helper 2>/dev/null || true
    systemctl try-restart docker packagekit flatpak-system-helper fwupd 2>/dev/null || true

    FPREPO="/var/lib/flatpak/repo"
    if [[ -f "\$FPREPO/config" ]]; then
        if command -v ostree &>/dev/null; then
            ostree --repo="\$FPREPO" config set 'remote "flathub".proxy' "\${PROXY_URL}" 2>/dev/null || true
        else
            sed -i '/^\[remote "flathub"\]/,/^\[/{ /^proxy=/d }' "\$FPREPO/config" 2>/dev/null || true
            sed -i "/^\[remote \"flathub\"\]/a proxy=\${PROXY_URL}" "\$FPREPO/config" 2>/dev/null || true
        fi
    fi

    if command -v dconf &>/dev/null; then
        mkdir -p /etc/dconf/db/local.d
        sed -i "s/mode='none'/mode='manual'/" /etc/dconf/db/local.d/01-proxy 2>/dev/null || true
        dconf update
    fi
    if [[ -f /etc/xdg/kioslaverc ]]; then
        sed -i "s/ProxyType=0/ProxyType=1/" /etc/xdg/kioslaverc 2>/dev/null || true
    fi

    mkdir -p /etc/firefox/policies
    cat > /etc/firefox/policies/policies.json <<FFEOF
{
  "policies": {
    "Proxy": {
      "Mode": "manual",
      "HTTPProxy": "\${PROXY_HOST}:\${PROXY_PORT}",
      "HTTPSProxy": "\${PROXY_HOST}:\${PROXY_PORT}",
      "FTPProxy": "\${PROXY_HOST}:\${PROXY_PORT}",
      "Passthrough": "\${NO_PROXY_LIST}"
    }
  }
}
FFEOF

    echo "[OK] Proxy is ON. Log out and back in (or reboot) for GUI sessions to update."

elif [[ "\$MODE" == "off" ]]; then
    echo "Disabling System Proxy..."

    > /etc/profile.d/proxy.sh
    rm -f /etc/fish/conf.d/proxy.fish
    sed -i -E '/^(http_proxy|https_proxy|ftp_proxy|no_proxy|HTTP_PROXY|HTTPS_PROXY|FTP_PROXY|NO_PROXY)=/d' /etc/environment 2>/dev/null || true

    for SVC in \${PROXY_SERVICES}; do
        rm -f /etc/systemd/system/\${SVC}.service.d/http-proxy.conf
    done
    systemctl daemon-reload

    killall packagekitd 2>/dev/null || true
    killall flatpak-system-helper 2>/dev/null || true
    systemctl try-restart docker flatpak-system-helper fwupd 2>/dev/null || true

    if command -v sqlite3 &>/dev/null && [ -f /var/lib/PackageKit/transactions.db ]; then
        sqlite3 /var/lib/PackageKit/transactions.db "DELETE FROM proxy;" || true
    else
        rm -f /var/lib/PackageKit/transactions.db || true
    fi
    systemctl try-restart packagekit 2>/dev/null || true

    FPREPO="/var/lib/flatpak/repo"
    if [[ -f "\$FPREPO/config" ]]; then
        if command -v ostree &>/dev/null; then
            ostree --repo="\$FPREPO" config unset 'remote "flathub".proxy' 2>/dev/null || true
        else
            sed -i '/^\[remote "flathub"\]/,/^\[/{ /^proxy=/d }' "\$FPREPO/config" 2>/dev/null || true
        fi
    fi

    if command -v dconf &>/dev/null; then
        mkdir -p /etc/dconf/db/local.d
        sed -i "s/mode='manual'/mode='none'/" /etc/dconf/db/local.d/01-proxy 2>/dev/null || true
        dconf update
    fi
    if [[ -f /etc/xdg/kioslaverc ]]; then
        sed -i "s/ProxyType=1/ProxyType=0/" /etc/xdg/kioslaverc 2>/dev/null || true
    fi

    mkdir -p /etc/firefox/policies
    cat > /etc/firefox/policies/policies.json <<FFEOF
{
  "policies": {
    "Proxy": {
      "Mode": "none"
    }
  }
}
FFEOF

    echo "[OK] Proxy is OFF. Log out and back in (or reboot) for GUI sessions to update."
else
    echo "Usage: toggle-proxy [on|off]"
fi
EOF

    chmod +x /usr/local/bin/toggle-proxy
}

mod_flatpak() {
    step "Configuring Flatpak & Flathub"

    if [[ "$PKG" == "apt-get" ]]; then
        run_retry apt-get install -y flatpak
        if [ "$HAS_GNOME" = true ]; then run_retry apt-get install -y gnome-software-plugin-flatpak; fi
        if [ "$HAS_KDE" = true ]; then run_retry apt-get install -y plasma-discover-backend-flatpak; fi
    elif [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        run_retry $PKG install -y flatpak
        if [ "$HAS_GNOME" = true ]; then run_retry $PKG install -y gnome-software; fi
        if [ "$HAS_KDE" = true ]; then run_retry $PKG install -y plasma-discover-flatpak; fi
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm flatpak
        if [ "$HAS_GNOME" = true ]; then run_retry pacman -S --noconfirm gnome-software; fi
        if [ "$HAS_KDE" = true ]; then run_retry pacman -S --noconfirm discover; fi
    fi

    FLATHUB_URL="https://dl.flathub.org/repo/"
    FP_CONFIG="/var/lib/flatpak/repo/config"
    NEED_READD=false
    if flatpak remotes 2>/dev/null | grep -q '^flathub'; then
        CUR_URL=$(awk '
            /^\[remote "flathub"\]/ {f=1; next}
            /^\[/ {f=0}
            f && /^url=/ {sub(/^url=/,""); print; exit}
        ' "$FP_CONFIG" 2>/dev/null)
        if [[ "$CUR_URL" != "$FLATHUB_URL" ]]; then
            warn "flathub remote has WRONG url ('${CUR_URL}') — deleting and re-adding correctly."
            flatpak remote-delete --force flathub || true
            NEED_READD=true
        fi
    else
        NEED_READD=true
    fi
    if [ "$NEED_READD" = true ]; then
        run_retry curl -sf -x "${PROXY_URL}" -o /tmp/flathub.flatpakrepo "${FLATHUB_URL}flathub.flatpakrepo"
        run_retry flatpak remote-add --if-not-exists flathub /tmp/flathub.flatpakrepo
        rm -f /tmp/flathub.flatpakrepo
    fi

    pin_flatpak_proxy
}

mod_desktop_tools() {
    step "Installing Desktop Utilities & GUI Tools"

    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        run_retry $PKG install -y fastfetch || run_retry $PKG install -y neofetch || true
    elif [[ "$PKG" == "apt-get" ]]; then
        run_retry apt-get install -y fastfetch || run_retry apt-get install -y neofetch || true
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm fastfetch || true
    fi

    if [ "$HAS_GNOME" = true ]; then
        log "GNOME DE detected. Deploying Tweaks, Flatseal, and ExtensionManager..."

        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y gnome-tweaks sqlite3
        elif [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then run_retry $PKG install -y gnome-tweaks sqlite
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm gnome-tweaks sqlite
        fi

        if command -v flatpak &>/dev/null; then
            pin_flatpak_proxy
            http_proxy="${PROXY_URL}" https_proxy="${PROXY_URL}" HTTP_PROXY="${PROXY_URL}" HTTPS_PROXY="${PROXY_URL}" \
                run_retry flatpak install -y flathub com.mattjakeman.ExtensionManager
            http_proxy="${PROXY_URL}" https_proxy="${PROXY_URL}" HTTP_PROXY="${PROXY_URL}" HTTPS_PROXY="${PROXY_URL}" \
                run_retry flatpak install -y flathub com.github.tchx84.Flatseal
        fi
    fi
}

mod_gs_fix() {
    step "GNOME Software / App Center Performance Fixes"
    pin_flatpak_proxy

    mkdir -p /etc/systemd/system/fwupd.service.d
    cat > /etc/systemd/system/fwupd.service.d/http-proxy.conf <<EOF
[Service]
Environment="HTTP_PROXY=${PROXY_URL}"
Environment="HTTPS_PROXY=${PROXY_URL}"
Environment="http_proxy=${PROXY_URL}"
Environment="https_proxy=${PROXY_URL}"
Environment="NO_PROXY=${NO_PROXY_LIST}"
Environment="no_proxy=${NO_PROXY_LIST}"
EOF
    systemctl daemon-reload
    systemctl try-restart fwupd 2>/dev/null || true

    systemctl stop packagekit 2>/dev/null || true
    if command -v sqlite3 &>/dev/null && [ -f /var/lib/PackageKit/transactions.db ]; then
        sqlite3 /var/lib/PackageKit/transactions.db "DELETE FROM proxy;" 2>/dev/null || true
    fi
    systemctl start packagekit 2>/dev/null || true

    if command -v appstreamcli &>/dev/null; then
        http_proxy="${PROXY_URL}" https_proxy="${PROXY_URL}" appstreamcli refresh --force 2>/dev/null || true
    fi

    pkill -f "gnome-software" 2>/dev/null || true
    rm -rf /var/cache/gnome-software 2>/dev/null || true
    for USERDIR in /home/*; do
        [ -d "$USERDIR/.cache/gnome-software" ] && rm -rf "$USERDIR/.cache/gnome-software" || true
    done

    success "GNOME Software backends re-pointed at proxy. First relaunch may still take ~30s to rebuild caches; subsequent launches should be fast."
}

###############################################################################
# CORE SERVER MODULES (Restored in v59)
###############################################################################

mod_docker() {
    step "Installing & Configuring Docker"

    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if [[ ! -f /etc/yum.repos.d/docker-ce.repo ]]; then
            run_retry $PKG install -y yum-utils
            if [[ "$OS_ID" == "fedora" ]]; then
                run_retry yum-config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
            else
                run_retry yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
            fi
        fi
        $PKG remove -y podman buildah docker docker-client docker-common docker-engine >/dev/null 2>&1 || true
    elif [[ "$PKG" == "apt-get" ]]; then
        if [[ ! -f /etc/apt/sources.list.d/docker.list ]]; then
            source /etc/os-release
            REPO_OS=${ID}
            case "$REPO_OS" in
                debian|ubuntu) : ;;
                *) REPO_OS="ubuntu" ;;
            esac
            REPO_CODENAME="${VERSION_CODENAME:-$(command -v lsb_release >/dev/null 2>&1 && lsb_release -cs || echo stable)}"

            install -m 0755 -d /etc/apt/keyrings
            run_retry curl -fsSL "https://download.docker.com/linux/${REPO_OS}/gpg" -o /etc/apt/keyrings/docker.asc
            chmod a+r /etc/apt/keyrings/docker.asc

            echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${REPO_OS} ${REPO_CODENAME} stable" > /etc/apt/sources.list.d/docker.list
            apt-get update -qq || true
        fi
    fi

    if ! command -v docker &>/dev/null; then
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm docker docker-compose docker-buildx
        else run_retry $PKG install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin; fi
    fi

    mkdir -p /etc/docker
    cat > /etc/docker/daemon.json <<EOF
{
  "insecure-registries": [ ${INSECURE_REGISTRIES} ]
}
EOF

    mkdir -p /etc/systemd/system/docker.service.d
    cat > /etc/systemd/system/docker.service.d/http-proxy.conf <<EOF
[Service]
Environment="HTTP_PROXY=${PROXY_URL}"
Environment="HTTPS_PROXY=${PROXY_URL}"
Environment="NO_PROXY=${NO_PROXY_LIST}"
EOF

    systemctl daemon-reload || true
    timeout 30s systemctl enable --now docker || true
    timeout 60s systemctl restart docker || true

    timeout 15s usermod -aG docker root 2>/dev/null || true
    if timeout 15s id "$LOCAL_USER" &>/dev/null; then timeout 15s usermod -aG docker "$LOCAL_USER" 2>/dev/null || true; fi

    mkdir -p /root/.docker
    cat > /root/.docker/config.json <<EOF
{
  "proxies": {
    "default": {
      "httpProxy": "${PROXY_URL}",
      "httpsProxy": "${PROXY_URL}",
      "noProxy": "${NO_PROXY_LIST}"
    }
  }
}
EOF

    if timeout 15s id "$LOCAL_USER" &>/dev/null; then
        USER_HOME=$(eval echo ~$LOCAL_USER)
        mkdir -p "$USER_HOME/.docker"
        cp /root/.docker/config.json "$USER_HOME/.docker/config.json"
        chown -R "$LOCAL_USER:$LOCAL_USER" "$USER_HOME/.docker" || true
    fi
}

mod_lazydocker() {
    step "Installing LazyDocker"
    if ! command -v lazydocker &>/dev/null; then
        run_retry curl -sSL -x "${PROXY_URL}" -o /tmp/install_lazydocker.sh https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh
        chmod +x /tmp/install_lazydocker.sh
        HTTP_PROXY="${PROXY_URL}" HTTPS_PROXY="${PROXY_URL}" DIR=/usr/local/bin run_retry /tmp/install_lazydocker.sh
        rm -f /tmp/install_lazydocker.sh
    fi
}

mod_web_stack() {
    step "Installing Web Stack (PHP, Nginx, Node)"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if ! rpm -q remi-release >/dev/null 2>&1; then
            if [[ "$OS_ID" == "fedora" ]]; then
                run_retry dnf install -y "https://rpms.remirepo.net/fedora/remi-release-${VERSION_MAJOR}.rpm"
            elif [[ "$PKG" == "dnf" ]]; then
                run_retry $PKG install -y "https://rpms.remirepo.net/enterprise/remi-release-${VERSION_MAJOR}.rpm"
            else
                run_retry $PKG install -y http://rpms.remirepo.net/enterprise/remi-release-7.rpm yum-utils
            fi
        fi
        $PKG clean packages >/dev/null 2>&1 || true

        if [[ "$PKG" == "dnf" ]]; then
            $PKG module reset php -y || true
            $PKG module install -y php:remi-${PHP_VERSION}
        else
            yum-config-manager --enable remi-php83 || true
            $PKG install -y php php-cli php-fpm php-mysqlnd php-gd
        fi
        $PKG install -y java-${JAVA_VERSION}-openjdk nginx nodejs
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm php php-fpm php-gd php-pgsql nginx nodejs npm jre-openjdk
    else
        source /etc/os-release
        if [[ "$ID" == "debian" ]]; then
            if [[ ! -f /etc/apt/sources.list.d/sury-php.list ]]; then
                install -m 0755 -d /etc/apt/keyrings
                run_retry curl -fsSL https://packages.sury.org/php/apt.gpg -o /etc/apt/keyrings/sury-php.gpg
                chmod a+r /etc/apt/keyrings/sury-php.gpg
                PHP_CODENAME="${VERSION_CODENAME:-$(command -v lsb_release >/dev/null 2>&1 && lsb_release -cs || echo bookworm)}"
                echo "deb [signed-by=/etc/apt/keyrings/sury-php.gpg] https://packages.sury.org/php/ ${PHP_CODENAME} main" > /etc/apt/sources.list.d/sury-php.list
                apt-get update -qq || true
            fi
        else
            if ! grep -q "ondrej/php" /etc/apt/sources.list.d/* 2>/dev/null; then run_retry add-apt-repository -y ppa:ondrej/php; fi
            apt-get update -qq || true
        fi
        run_retry apt-get install -y php${PHP_VERSION} php${PHP_VERSION}-{cli,fpm,mysql,gd,mbstring,xml,curl,zip}
        run_retry apt-get install -y "openjdk-${JAVA_VERSION}-jdk" || { log "openjdk-${JAVA_VERSION} unavailable; installing default-jdk"; run_retry apt-get install -y default-jdk; }
        run_retry apt-get install -y nginx nodejs npm
    fi

    if command -v php &>/dev/null; then
        find /etc/php* -name "php.ini" 2>/dev/null | while read -r INI_FILE; do
            sed -i '/^http_proxy/d; /^https_proxy/d' "$INI_FILE"
            echo -e "\n; Proxy Settings\nhttp_proxy = \"${PROXY_URL}\"\nhttps_proxy = \"${PROXY_URL}\"" >> "$INI_FILE"
            if grep -q "allow_url_fopen" "$INI_FILE"; then sed -i 's/^allow_url_fopen.*/allow_url_fopen = On/' "$INI_FILE"
            else echo "allow_url_fopen = On" >> "$INI_FILE"; fi
        done
        if systemctl list-unit-files | grep -q php-fpm; then timeout 30s systemctl restart php-fpm || true; fi
        if systemctl list-unit-files | grep -q php${PHP_VERSION}-fpm; then timeout 30s systemctl restart php${PHP_VERSION}-fpm || true; fi
    fi
}

mod_db_stack() {
    step "Installing Databases"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if [[ ! -f /etc/yum.repos.d/mariadb.repo ]]; then
            if [[ "$OS_ID" == "fedora" ]]; then DB_OS="fedora"; else DB_OS="rhel"; fi
            cat > /etc/yum.repos.d/mariadb.repo <<EOF
[mariadb]
name = MariaDB
baseurl = https://rpm.mariadb.org/${MARIADB_VERSION}/${DB_OS}/\$releasever/\$basearch
module_hotfixes=1
gpgkey=https://rpm.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
EOF
        fi
        run_retry $PKG install -y MariaDB-server MariaDB-client postgresql-server
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm mariadb postgresql
    else
        run_retry apt-get install -y mariadb-server postgresql
    fi
}

mod_cockpit() {
    step "Installing Cockpit"
    if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y cockpit cockpit-storaged cockpit-pcp cockpit-packagekit
    elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm cockpit
    else run_retry $PKG install -y cockpit cockpit-storaged cockpit-pcp 2>/dev/null || run_retry $PKG install -y cockpit; fi

    mkdir -p /etc/systemd/system/cockpit.service.d
    echo -e "[Service]\nEnvironment=\"HTTP_PROXY=${PROXY_URL}\"\nEnvironment=\"HTTPS_PROXY=${PROXY_URL}\"\nEnvironment=\"NO_PROXY=${NO_PROXY_LIST}\"" > /etc/systemd/system/cockpit.service.d/proxy.conf
    systemctl daemon-reload || true
    timeout 30s systemctl enable --now cockpit.socket || true
}

mod_cleanup() {
    step "Final Cleanup & Hardening"

    if command -v apt-get &>/dev/null; then rm -f /etc/apt/apt.conf.d/80proxy; fi
    if command -v dnf &>/dev/null; then sed -i '/^proxy=/d' /etc/dnf/dnf.conf 2>/dev/null || true; fi

    if command -v tmux &>/dev/null; then $PKG remove -y tmux 2>/dev/null || true; fi
    rm -f /etc/tmux.conf

    if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y fish fail2ban;
    elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm fish fail2ban;
    else run_retry $PKG install -y fish fail2ban; fi

    systemctl disable systemd-networkd-wait-online.service 2>/dev/null || true
    systemctl mask systemd-networkd-wait-online.service 2>/dev/null || true
    systemctl disable NetworkManager-wait-online.service 2>/dev/null || true
    systemctl mask NetworkManager-wait-online.service 2>/dev/null || true

    if [[ -f /etc/rc.d/rc.local ]]; then chmod +x /etc/rc.d/rc.local; fi
    if grep -q "172.16.21.16" /etc/fstab; then sed -i '/172.16.21.16/d' /etc/fstab; fi

    if systemctl is-failed sssd-nss.socket &>/dev/null; then
        systemctl reset-failed || true
        timeout 30s systemctl restart sssd || true
    fi

    ESCAPED_GROUP=$(echo "$AD_SUDO_GROUP" | sed 's/ /\\ /g')
    mkdir -p /etc/sudoers.d
    echo "%${ESCAPED_GROUP} ALL=(ALL) NOPASSWD: ALL" > "/etc/sudoers.d/10-ad-admins"
    chmod 440 "/etc/sudoers.d/10-ad-admins"

    cat > /etc/fail2ban/jail.local <<EOF
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3
bantime = 3600
EOF
    timeout 30s systemctl enable --now fail2ban || true
}

mod_shell() {
    step "Deploying Universal Shell Environment (Starship + zsh/fish/bash)"

    if [[ "$PKG" == "apt-get" ]]; then
        run_retry apt-get install -y zsh fish git fontconfig || true
        run_retry apt-get install -y fzf || true
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm zsh fish git fzf fontconfig || true
    else
        run_retry $PKG install -y zsh fish git fontconfig || true
        run_retry $PKG install -y fzf || true
    fi

    if ! command -v starship &>/dev/null; then
        log "Installing Starship prompt to /usr/local/bin..."
        if ! run_retry sh -c "curl -sS -x '${PROXY_URL}' https://starship.rs/install.sh | sh -s -- -y -b /usr/local/bin"; then
            ARCH_S=$(uname -m)
            run_retry curl -sfL -x "${PROXY_URL}" -o /tmp/starship.tar.gz \
                "https://github.com/starship/starship/releases/latest/download/starship-${ARCH_S}-unknown-linux-musl.tar.gz" \
                && tar xzf /tmp/starship.tar.gz -C /usr/local/bin starship \
                && chmod 755 /usr/local/bin/starship
            rm -f /tmp/starship.tar.gz
        fi
    fi
    command -v starship &>/dev/null || { error "Starship install failed — aborting shell module."; return 1; }

    PLUG_DIR="/usr/local/share/zsh-plugins"
    mkdir -p "$PLUG_DIR"
    export GIT_HTTP_PROXY_AUTH=""
    for REPO in zsh-users/zsh-autosuggestions zsh-users/zsh-syntax-highlighting; do
        NAME="${REPO##*/}"
        if [[ ! -d "$PLUG_DIR/$NAME" ]]; then
            run_retry git -c http.proxy="${PROXY_URL}" clone --depth 1 "https://github.com/${REPO}.git" "$PLUG_DIR/$NAME" || true
        else
            git -C "$PLUG_DIR/$NAME" -c http.proxy="${PROXY_URL}" pull --ff-only 2>/dev/null || true
        fi
    done

    mkdir -p /etc/starship
    cat > /etc/starship/starship.toml <<'STARSHIP_EOF'
add_newline = true
format = """
$username$hostname$directory$git_branch$git_status$cmd_duration$fill$time
$character"""

[fill]
symbol = " "

[username]
style_user = "bold yellow"
style_root = "bold red"
format = "[$user]($style)"
show_always = true

[hostname]
ssh_only = false
format = "[@$hostname](bold green) "

[directory]
truncation_length = 4
truncate_to_repo = true
style = "bold cyan"
format = "[$path]($style)[$read_only](red) "

[git_branch]
symbol = " "
style = "bold purple"
format = "[$symbol$branch]($style) "

[git_status]
style = "bold red"
format = "([$all_status$ahead_behind]($style) )"

[cmd_duration]
min_time = 2000
style = "yellow"
format = "[took $duration]($style) "

[time]
disabled = false
time_format = "%T"
style = "dimmed white"
format = "[$time]($style)"

[character]
success_symbol = "[❯](bold green)"
error_symbol = "[❯](bold red)"
STARSHIP_EOF

    cat > /etc/profile.d/zz-m21-shell.sh <<'BASHRC_EOF'
case $- in *i*) ;; *) return ;; esac
export STARSHIP_CONFIG=/etc/starship/starship.toml
export HISTTIMEFORMAT='%F %T  '
export HISTSIZE=50000
export HISTFILESIZE=100000
export HISTCONTROL=ignoredups:erasedups
shopt -s histappend 2>/dev/null
PROMPT_COMMAND="history -a; ${PROMPT_COMMAND:-:}"
command -v starship >/dev/null 2>&1 && eval "$(starship init bash)"
BASHRC_EOF

    ZSHRC="/etc/zshrc"; [[ -d /etc/zsh ]] && ZSHRC="/etc/zsh/zshrc"
    touch "$ZSHRC"
    sed -i '/# >>> m21-shell >>>/,/# <<< m21-shell <<</d' "$ZSHRC"
    cat >> "$ZSHRC" <<'ZSHRC_EOF'
# >>> m21-shell >>>
export STARSHIP_CONFIG=/etc/starship/starship.toml
HISTFILE=~/.zsh_history
HISTSIZE=50000
SAVEHIST=100000
setopt EXTENDED_HISTORY SHARE_HISTORY HIST_IGNORE_DUPS HIST_REDUCE_BLANKS
autoload -Uz compinit && compinit -u
zstyle ':completion:*' menu select
[[ -r /usr/local/share/zsh-plugins/zsh-autosuggestions/zsh-autosuggestions.zsh ]] && \
    source /usr/local/share/zsh-plugins/zsh-autosuggestions/zsh-autosuggestions.zsh
[[ -r /usr/local/share/zsh-plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]] && \
    source /usr/local/share/zsh-plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
command -v starship >/dev/null 2>&1 && eval "$(starship init zsh)"
# <<< m21-shell <<<
ZSHRC_EOF

    mkdir -p /etc/fish/conf.d
    cat > /etc/fish/conf.d/m21-shell.fish <<'FISH_EOF'
set -gx STARSHIP_CONFIG /etc/starship/starship.toml
if type -q starship
    starship init fish | source
end
FISH_EOF

    cat > /etc/fish/conf.d/proxy.fish <<FISHPROXY_EOF
set -gx http_proxy "${PROXY_URL}"
set -gx https_proxy "${PROXY_URL}"
set -gx ftp_proxy "${PROXY_URL}"
set -gx no_proxy "${NO_PROXY_LIST}"
set -gx HTTP_PROXY "${PROXY_URL}"
set -gx HTTPS_PROXY "${PROXY_URL}"
set -gx FTP_PROXY "${PROXY_URL}"
set -gx NO_PROXY "${NO_PROXY_LIST}"
FISHPROXY_EOF

    if [ "$HAS_GNOME" = true ] || [ "$HAS_KDE" = true ]; then
        FONT_DIR="/usr/local/share/fonts/MesloNF"
        if [[ ! -d "$FONT_DIR" ]]; then
            log "Installing Meslo Nerd Font (desktop glyph support)..."
            mkdir -p "$FONT_DIR"
            if run_retry curl -sfL -x "${PROXY_URL}" -o /tmp/meslo.tar.xz \
                "https://github.com/ryanoasis/nerd-fonts/releases/latest/download/Meslo.tar.xz"; then
                tar xf /tmp/meslo.tar.xz -C "$FONT_DIR" && fc-cache -f "$FONT_DIR" || true
            fi
            rm -f /tmp/meslo.tar.xz
        fi
        log "Set your terminal font to 'MesloLGS Nerd Font'."
    fi

    success "Shell environment deployed. Default login shell remains bash."
}

mod_clock_fix() {
    step "Synchronizing System Clock"
    timedatectl set-timezone "$TARGET_TIMEZONE" || true
    timedatectl set-ntp true || true
    if systemctl list-unit-files | grep -q systemd-timesyncd; then
        timeout 30s systemctl restart systemd-timesyncd || true
    fi
}

mod_certs() {
    step "Installing Certificates"
    MNT="/mnt/share_certs_tmp"
    mkdir -p "$MNT"

    if ! command -v mount.cifs &>/dev/null; then
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get update -qq >/dev/null 2>&1 || true; run_retry apt-get install -y cifs-utils
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm cifs-utils
        else run_retry $PKG install -y cifs-utils; fi
    fi

    if mountpoint -q "$MNT"; then umount -l "$MNT"; fi

    if timeout 30s mount -t cifs "$SHARE_PATH" "$MNT" -o username="$SHARE_USER",password="$SHARE_PASS",vers=3.0; then
        SOURCE_FULL="$MNT$CERT_SOURCE_PATH"
        TEMP_PEM="/tmp/${TARGET_CERT_NAME}_staging.pem"

        if [[ -f "$SOURCE_FULL" ]]; then
            if ! openssl x509 -inform der -in "$SOURCE_FULL" -out "$TEMP_PEM" 2>/dev/null; then cp "$SOURCE_FULL" "$TEMP_PEM"; fi

            if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
                cp "$TEMP_PEM" "/etc/pki/ca-trust/source/anchors/${TARGET_CERT_NAME}.pem"
                [[ "$VERSION_MAJOR" -lt 9 ]] && update-ca-trust force-enable 2>/dev/null || true
                update-ca-trust extract
            elif [[ "$PKG" == "pacman" ]]; then
                cp "$TEMP_PEM" "/etc/ca-certificates/trust-source/anchors/${TARGET_CERT_NAME}.crt"
                trust extract-compat
            else
                cp "$TEMP_PEM" "/usr/local/share/ca-certificates/${TARGET_CERT_NAME}.crt"
                update-ca-certificates
            fi
        fi
        timeout 15s umount "$MNT" || true
    fi
    rmdir "$MNT" 2>/dev/null || true
}

mod_base_repos() {
    step "Configuring Base OS Repositories"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        if [[ "$OS_ID" == "fedora" ]]; then
            log "Setting up Fedora 3rd Party Repos (RPM Fusion & Workstation Repos)..."
            run_retry dnf install -y dnf-plugins-core fedora-workstation-repositories || true
            run_retry dnf install -y "https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-${VERSION_MAJOR}.noarch.rpm" \
                                     "https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-${VERSION_MAJOR}.noarch.rpm" || true
            dnf config-manager --set-enabled rpmfusion-free rpmfusion-nonfree || true
        else
            if ! rpm -q epel-release >/dev/null 2>&1; then run_retry $PKG install -y epel-release; fi
            if [[ "$PKG" == "dnf" ]]; then
                if ! dnf repolist enabled 2>/dev/null | grep -E "crb|powertools" >/dev/null; then
                    run_retry $PKG install -y 'dnf-command(config-manager)'
                    $PKG config-manager --set-enabled crb 2>/dev/null || $PKG config-manager --set-enabled powertools 2>/dev/null || true
                fi
            fi
        fi
    elif [[ "$PKG" == "apt-get" ]]; then
        export DEBIAN_FRONTEND=noninteractive
        rm -f /etc/apt/sources.list.d/45drives.list
        apt-get update -qq || true
        BASE_APT_PKGS="curl wget gnupg lsb-release ca-certificates"
        if [[ "$OS_ID" != "debian" ]]; then BASE_APT_PKGS="software-properties-common $BASE_APT_PKGS"; fi
        run_retry apt-get install -y $BASE_APT_PKGS
    fi
}

mod_base_tools() {
    step "Installing Base System Tools"
    if [[ "$PKG" == "dnf" || "$PKG" == "yum" ]]; then
        PACKAGES="git curl wget nano neovim zsh util-linux-user bind-utils net-tools openssl policycoreutils-python-utils psmisc PackageKit pcp pcp-conf pcp-libs pcp-selinux"
        run_retry $PKG install -y $PACKAGES
    elif [[ "$PKG" == "pacman" ]]; then
        PACKAGES="git curl wget nano neovim zsh openssl net-tools bind psmisc networkmanager"
        run_retry pacman -S --noconfirm $PACKAGES
        timeout 30s systemctl enable --now NetworkManager || true
    else
        PACKAGES="git curl wget nano neovim zsh openssl net-tools dnsutils psmisc packagekit pcp network-manager"
        run_retry apt-get install -y $PACKAGES
        timeout 30s systemctl enable --now NetworkManager || true
    fi
    systemctl unmask packagekit 2>/dev/null || true
    timeout 30s systemctl start packagekit 2>/dev/null || true
}

mod_network() {
    step "Configuring Network & DNS"
    if [[ "$PKG" == "apt-get" ]] && command -v netplan >/dev/null 2>&1; then
        if ls /etc/netplan/*.yaml >/dev/null 2>&1 && grep -q "addresses:" /etc/netplan/*.yaml; then
            log "Static Netplan detected. Skipping wipe to prevent lockout."
        else
            mkdir -p /etc/netplan
            cat > /etc/netplan/01-network-manager-all.yaml <<EOF
network:
  version: 2
  renderer: NetworkManager
EOF
            netplan apply || true
        fi
    fi

    sed -i "/${DOMAIN_FQDN}/d; /${DOMAIN_ALT}/d; /${DC_DNS_IP}/d; /${FILE_SERVER_NAME}/d" /etc/hosts
    cat >> /etc/hosts <<EOF
${DC_DNS_IP}    ${DOMAIN_FQDN} ${DOMAIN_ALT} ${DOMAIN_SHORT}
${FILE_SERVER_IP}    ${FILE_SERVER_NAME}.${DOMAIN_FQDN} ${FILE_SERVER_NAME}.${DOMAIN_ALT} ${FILE_SERVER_NAME}
EOF

    if [[ -L /etc/resolv.conf ]]; then rm -f /etc/resolv.conf; fi
    echo -e "search ${DOMAIN_FQDN} ${DOMAIN_ALT}\nnameserver ${DC_DNS_IP}" > /etc/resolv.conf

    if command -v nmcli &>/dev/null; then
        TARGET_IFACE=$(ip -4 -o addr show | grep "172.16." | awk '{print $2}' | head -n1)
        if [[ -n "$TARGET_IFACE" ]]; then
            CONN=$(nmcli -t -f NAME,DEVICE con show --active | grep ":${TARGET_IFACE}" | cut -d: -f1 | head -n1)
            if [[ -n "$CONN" ]]; then
                nmcli con mod "$CONN" ipv4.dns "$DC_DNS_IP" ipv4.dns-search "${DOMAIN_FQDN},${DOMAIN_ALT}" ipv4.ignore-auto-dns yes
                timeout 15s nmcli con up "$CONN" >/dev/null 2>&1
            fi
        fi
    fi

    echo -e "net.ipv6.conf.all.disable_ipv6 = 1\nnet.ipv6.conf.default.disable_ipv6 = 1" > /etc/sysctl.d/90-disable-ipv6.conf
    sysctl --system &>/dev/null || true

    if command -v systemctl &>/dev/null; then
        if [[ "$PKG" == "apt-get" ]]; then run_retry apt-get install -y chrony; CHRONY_CONF="/etc/chrony/chrony.conf"
        elif [[ "$PKG" == "pacman" ]]; then run_retry pacman -S --noconfirm chrony; CHRONY_CONF="/etc/chrony.conf"
        else run_retry $PKG install -y chrony; CHRONY_CONF="/etc/chrony.conf"; fi

        if [[ -f "$CHRONY_CONF" ]]; then
            sed -i '/server/d; /pool/d' "$CHRONY_CONF" 2>/dev/null || true
            echo "server ${NTP_SERVER} iburst" >> "$CHRONY_CONF"
        fi
        timeout 30s systemctl restart chronyd 2>/dev/null || timeout 30s systemctl restart chrony || true
    fi
}

mod_firewall() {
    step "Configuring Firewalld (Defense in Depth)"
    if [[ "$PKG" == "apt-get" ]]; then
        run_retry apt-get install -y firewalld
        systemctl disable ufw --now 2>/dev/null || true
    elif [[ "$PKG" == "pacman" ]]; then
        run_retry pacman -S --noconfirm firewalld
    else
        run_retry $PKG install -y firewalld
    fi

    systemctl enable --now firewalld

    firewall-cmd --permanent --zone=trusted --add-source=172.17.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=172.18.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=172.19.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=172.20.0.0/16
    firewall-cmd --permanent --zone=trusted --add-source=192.168.250.0/24

    firewall-cmd --permanent --add-service=http
    firewall-cmd --permanent --add-service=https

    firewall-cmd --permanent --remove-service=ssh
    firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.21.0.0/21" service name="ssh" accept'
    firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.16.121.0/24" service name="ssh" accept'
    firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.16.21.0/24" service name="ssh" accept'

    firewall-cmd --reload
}

mod_resize_home() {
    step "LVM Home Resizer"
    if ! command -v lvs &>/dev/null; then return; fi
    if ! mountpoint -q /home; then return; fi
    HOME_DEV=$(findmnt -n -o SOURCE /home)
    if [[ "$HOME_DEV" != *"/mapper/"* ]]; then return; fi

    LV_NAME=$(lvs --noheadings -o lv_name "$HOME_DEV" | tr -d ' ')
    VG_NAME=$(lvs --noheadings -o vg_name "$HOME_DEV" | tr -d ' ')
    LV_PATH="/dev/$VG_NAME/$LV_NAME"
    ROOT_LV_PATH="/dev/$VG_NAME/root"
    MAPPER_PATH="/dev/mapper/${VG_NAME}-${LV_NAME}"

    CURRENT_SIZE=$(lvs --noheadings -o lv_size --units g "$LV_PATH" 2>/dev/null | tr -d 'g ' || lvs --noheadings -o L_SIZE --units g "$LV_PATH" | tr -d 'g ')
    if [[ ${CURRENT_SIZE%.*} -le 9 ]]; then return; fi

    tar czf /tmp/home_backup.tar.gz -C /home .
    fuser -km /home || true
    timeout 30s umount /home || timeout 15s umount -l /home || true

    lvremove -y "$LV_PATH"
    lvcreate -L "$HOME_TARGET_SIZE" -n "$LV_NAME" "$VG_NAME" -y
    mkfs.ext4 "$LV_PATH"

    sed -i '/\/home/d' /etc/fstab
    echo "$MAPPER_PATH /home ext4 defaults 0 0" >> /etc/fstab
    systemctl daemon-reload || true

    timeout 30s mount /home || true
    tar xzf /tmp/home_backup.tar.gz -C /home
    if command -v restorecon &>/dev/null; then restorecon -R /home; fi

    lvextend -l +100%FREE "$ROOT_LV_PATH"
    xfs_growfs / || resize2fs "$ROOT_LV_PATH" || true
    rm -f /tmp/home_backup.tar.gz
}

###############################################################################
# 6. DIAGNOSTICS (READ-ONLY)
###############################################################################

_chk() {
    local LABEL="$1"; shift
    if "$@" &>/dev/null; then
        echo -e "  ${GREEN}[PASS]${NC} ${LABEL}"
    else
        echo -e "  ${RED}[FAIL]${NC} ${LABEL}"
    fi
}

mod_doctor() {
    step "System Doctor (read-only diagnostics)"

    echo -e "\n${YELLOW}-- Network & DNS --${NC}"
    _chk "resolv.conf points at DC (${DC_DNS_IP})" grep -q "${DC_DNS_IP}" /etc/resolv.conf
    _chk "DNS resolves ${DOMAIN_FQDN}" timeout 5s getent hosts "${DOMAIN_FQDN}"
    _chk "DC reachable (ping ${DC_DNS_IP})" timeout 5s ping -c1 -W2 "${DC_DNS_IP}"
    _chk "File server reachable (ping ${FILE_SERVER_IP})" timeout 5s ping -c1 -W2 "${FILE_SERVER_IP}"
    _chk "IPv6 disabled" bash -c "sysctl -n net.ipv6.conf.all.disable_ipv6 | grep -q 1 2>/dev/null || sysctl net.ipv6.conf.all.disable_ipv6 2>/dev/null | grep -q '= 1'"

    echo -e "\n${YELLOW}-- Time --${NC}"
    _chk "Timezone is ${TARGET_TIMEZONE}" bash -c "timedatectl show -p Timezone --value | grep -qx '${TARGET_TIMEZONE}' 2>/dev/null || timedatectl | grep -q '${TARGET_TIMEZONE}'"
    if command -v chronyc &>/dev/null; then
        _chk "chrony synchronized" bash -c "chronyc tracking 2>/dev/null | grep -q 'Leap status.*Normal'"
    fi

    echo -e "\n${YELLOW}-- Proxy Path --${NC}"
    _chk "Proxy TCP reachable (${PROXY_URL})" timeout 5s bash -c "exec 3<>/dev/tcp/$(echo "$PROXY_URL" | awk -F/ '{print $3}' | cut -d: -f1)/$(echo "$PROXY_URL" | awk -F: '{print $NF}')"
    _chk "External fetch via proxy (flathub summary.idx)" timeout 20s curl -s -x "${PROXY_URL}" -o /dev/null -w '%{http_code}' https://dl.flathub.org/repo/summary.idx
    _chk "/etc/environment has proxy vars" grep -q "^http_proxy=" /etc/environment
    _chk "/etc/profile.d/proxy.sh present & non-empty" test -s /etc/profile.d/proxy.sh
    _chk "fwupd proxy drop-in present" test -f /etc/systemd/system/fwupd.service.d/http-proxy.conf
    _chk "packagekit proxy drop-in present" test -f /etc/systemd/system/packagekit.service.d/http-proxy.conf
    if [[ -f /var/lib/flatpak/repo/config ]]; then
        _chk "flathub remote URL correct (dl.flathub.org/repo)" awk '/^\[remote "flathub"\]/{f=1;next} /^\[/{f=0} f && $0=="url=https://dl.flathub.org/repo/"{found=1} END{exit !found}' /var/lib/flatpak/repo/config
        _chk "flathub ostree proxy pinned in repo config" awk '/^\[remote "flathub"\]/{f=1;next} /^\[/{f=0} f && /^proxy=/{found=1} END{exit !found}' /var/lib/flatpak/repo/config
    fi

    echo -e "\n${YELLOW}-- Trust & Identity --${NC}"
    _chk "GORTT root cert in trust store" bash -c "ls /etc/pki/ca-trust/source/anchors/${TARGET_CERT_NAME}.pem /usr/local/share/ca-certificates/${TARGET_CERT_NAME}.crt /etc/ca-certificates/trust-source/anchors/${TARGET_CERT_NAME}.crt 2>/dev/null | grep -q ."
    if command -v realm &>/dev/null; then
        _chk "Domain joined (${DOMAIN_FQDN})" bash -c "timeout 10s realm list 2>/dev/null | grep -q '${DOMAIN_FQDN}'"
    fi
    _chk "sssd active" systemctl is-active --quiet sssd
    _chk "sshd active" bash -c "systemctl is-active --quiet sshd || systemctl is-active --quiet ssh"

    echo -e "\n${YELLOW}-- Services --${NC}"
    _chk "firewalld active" systemctl is-active --quiet firewalld
    _chk "fail2ban active" systemctl is-active --quiet fail2ban
    if command -v docker &>/dev/null; then
        _chk "docker active" systemctl is-active --quiet docker
        _chk "docker daemon proxy configured" bash -c "docker info 2>/dev/null | grep -qi 'HTTP Proxy'"
    fi
    if command -v flatpak &>/dev/null; then
        _chk "flathub remote configured" bash -c "flatpak remotes 2>/dev/null | grep -q flathub"
    fi
    _chk "packagekit active" systemctl is-active --quiet packagekit

    echo -e "\n${YELLOW}-- Known Delay Sources --${NC}"
    _chk "systemd-networkd-wait-online masked" bash -c "systemctl is-enabled systemd-networkd-wait-online.service 2>/dev/null | grep -q masked"
    _chk "NetworkManager-wait-online masked" bash -c "systemctl is-enabled NetworkManager-wait-online.service 2>/dev/null | grep -q masked"
    if command -v sqlite3 &>/dev/null && [ -f /var/lib/PackageKit/transactions.db ]; then
        STALE=$(sqlite3 /var/lib/PackageKit/transactions.db "SELECT COUNT(*) FROM proxy WHERE proxy_http IS NULL OR proxy_http = '';" 2>/dev/null || echo "?")
        if [[ "$STALE" != "0" && "$STALE" != "?" ]]; then
            warn "PackageKit has ${STALE} stale (empty) proxy entries — run --gs-fix to scrub."
        else
            echo -e "  ${GREEN}[PASS]${NC} PackageKit proxy table clean"
        fi
    fi

    echo ""
    log "Doctor complete. FAILs above are your troubleshooting starting points."
}

###############################################################################
# 7. CLI ROUTER
###############################################################################

show_help() {
    echo "Usage: $0 [OPTION]"
    echo "Version: ${SCRIPT_VERSION}"
    echo "Supported: RHEL/CentOS/Alma/Rocky/Fedora (dnf/yum), Ubuntu/Debian/Zorin (apt), Arch (pacman)."
    echo ""
    echo "Core Deployment:"
    echo "  --basics        Proxy, Certs, Repos, Network, Firewalld, AD, Cleanup. (Servers)"
    echo "  --full          Everything (Basics + GUI + Docker + Web/DB + Flatpak/Tools + Shell + Cockpit). (Workstations)"
    echo ""
    echo "Modular Execution:"
    echo "  --docker        Install and configure Docker Engine with Proxy/Subnets."
    echo "  --flatpak       Configure Flatpak, Flathub (ostree proxy pin), and GUI App Centers."
    echo "  --gs-fix        Fix GNOME Software slowness (fwupd/PackageKit/ostree/appstream)."
    echo "  --ad-join       Run the SSSD and Realmd AD Join sequence."
    echo "  --certs         Mount CIFS, fetch root cert, update CA trust."
    echo "  --gui-proxy     Configure dconf (GNOME/Cinnamon), KDE, and Firefox Proxies."
    echo "  --proxy-tool    Install the 'toggle-proxy' dynamic CLI tool."
    echo "  --desktop-tools Install Fastfetch, GNOME Tweaks, Flatseal, ExtensionManager."
    echo "  --shell         Starship prompt + zsh/fish/bash integration, plugins, fonts."
    echo "  --web-stack     Install PHP, Nginx, Node, and Java."
    echo "  --db-stack      Install MariaDB and PostgreSQL."
    echo "  --tools         Install zsh, fish, neovim, git, nano, lazydocker."
    echo "  --resize-home   Shrink LVM /home to ${HOME_TARGET_SIZE} (Backup/Restore)."
    echo ""
    echo "Diagnostics:"
    echo "  --doctor        Read-only health check: DNS, proxy path, certs, AD, services."
    echo ""
}

if [[ $# -eq 0 ]]; then show_help; exit 0; fi

if [[ $EUID -ne 0 ]]; then
    echo -e "${RED}This script must be run as root (sudo $0 $*).${NC}"
    exit 1
fi

# Initialize Global Logging
touch "$LOG_FILE" || true
chmod 600 "$LOG_FILE" || true
# Strip ANSI codes for the file copy, keeping colors active on the terminal
exec > >(tee >(sed -u 's/\x1B\[[0-9;]*m//g' >> "$LOG_FILE")) 2>&1

cat > /etc/logrotate.d/m21-setup <<EOF
${LOG_FILE} {
    size 5M
    rotate 4
    compress
    missingok
    notifempty
}
EOF

echo -e "\n=== INVOCATION: $0 $* ===" >> "$LOG_FILE"

detect_and_fix_os
init_header

while [[ "$#" -gt 0 ]]; do
    case $1 in
        --basics) mod_proxy; mod_clock_fix; mod_certs; mod_base_repos; mod_base_tools; mod_network; mod_firewall; mod_domain_users; mod_cleanup ;;
        --full) mod_proxy; mod_gui_proxy; mod_proxy_toggle; mod_clock_fix; mod_certs; mod_base_repos; mod_base_tools; mod_flatpak; mod_desktop_tools; mod_shell; mod_gs_fix; mod_network; mod_firewall; mod_domain_users; mod_docker; mod_web_stack; mod_db_stack; mod_cockpit; mod_cleanup ;;

        --docker) mod_proxy; mod_docker ;;
        --flatpak) mod_proxy; mod_flatpak ;;
        --desktop-tools) mod_proxy; mod_desktop_tools; mod_gs_fix ;;
        --gs-fix) mod_proxy; mod_gs_fix ;;
        --ad-join) mod_domain_users ;;
        --certs) mod_certs ;;
        --gui-proxy) mod_gui_proxy ;;
        --proxy-tool) mod_proxy_toggle ;;
        --shell) mod_proxy; mod_shell ;;
        --web-stack) mod_proxy; mod_web_stack ;;
        --db-stack) mod_proxy; mod_db_stack ;;
        --tools) mod_proxy; mod_base_tools; mod_lazydocker ;;
        --resize-home) mod_resize_home ;;
        --doctor) mod_doctor ;;

        *) echo "Unknown option: $1"; show_help; exit 1 ;;
    esac
    shift
done

echo -e "\n${GREEN}[$(date +'%H:%M:%S')] === Setup Complete (${SCRIPT_VERSION}) ===${NC}"

 

DISABLE WAYLAND FOR SUPPORT MESH COMPATIBILITY

1 From the initial boot after installation on the login screen
2 Select your account and go to the bottom right to click the gear icon
3 Select Gnome on Xorg 
4 Input password to proceed with login

Open Terminal

sudo -i
nano /etc/gdm/custom.conf
1 Go to the line #WaylandEnable=false  and Delete the hashtag '#' 
2 To exit:     CTRL      +      'X' 
3 Select     'Y'    for    yes
4 To save:    'enter'   key
sudo dnf update -y
sudo reboot

******************************COMPLETED*******************************


CHANGE COMPUTER NAME

1

Open Terminal

PC Name example:   MYDNS-IT-C12-L.M21.GOV.LOCAL

2 sudo hostnamectl set-hostname mydns-it-c12-l.m21.gov.local

******************************COMPLETED*******************************


TO JOIN THE DOMAIN

Open Terminal

sudo nano /etc/environment

Add the following line to the file:

http_proxy="http://172.40.4.14:8080/"
https_proxy="http://172.40.4.14:8080/"
ftp_proxy="http://172.40.4.14:8080/"
no_proxy=127.0.0.1,localhost,.localdomain,172.30.0.0/20,172.26.21.0/24
HTTP_PROXY="http://172.40.4.14:8080/"
HTTPS_PROXY="http://172.40.4.14:8080/"
FTP_PROXY="http://172.40.4.14:8080/"
NO_PROXY=127.0.0.1,localhost,.localdomain,172.30.0.0/20,172.26.21.0/24
1 To exit:     CTRL      +      'X' 
2 Select     'Y'    for    yes
3

To save:    'enter'   key

4

Log out and back in again

sudo nano /etc/dnf/dnf.conf

Add the following line to the file:

fastestmirror=1
1 To exit:     CTRL      +      'X' 
2 Select     'Y'    for    yes
3

To save:    'enter'   key

On Fedora

sudo dnf -y install epel-release && sudo dnf -y install realmd sssd oddjob oddjob-mkhomedir adcli samba-common-tools authselect nano curl wget htop btop net-tools git zip unzip tar freeipa-client tmux

On Ubuntu

sudo apt -y install realmd sssd sssd-tools libnss-sss libpam-sss adcli samba-common-bin oddjob oddjob-mkhomedir packagekit nano curl wget htop btop net-tools git zip unzip tar freeipa-client tmux

Fix DNS

Input the IP Address and the Domain Name into file

search	m21.gov.local
nameserver	172.16.21.161
1 To exit:     CTRL      +      'X' 
2 Select     'Y'    for    yes
3 To save:    'enter'   key
sudo nano /etc/hosts

Input the following lines into file

172.16.21.161	m21.gov.local M21.GOV.LOCAL
172.16.21.16	mydns-0ic16.m21.gov.local mydns-0ic16
1 To exit:     CTRL      +      'X' 
2 Select     'Y'    for    yes
3 To save:    'enter'   key
sudo realm discover M21.GOV.LOCAL
ping -c 4 M21.GOV.LOCAL
To stop ping:       CTRL     +      'C'
sudo realm join -U ent_username@M21.GOV.LOCAL m21.gov.local -v

Input Ent Account Password

To ensure that it was successful run the realm join code again and you should see "Already joined to this domain"

******************************COMPLETED*******************************


GROUP POLICY CONFLICT RESOLVE (to login without wifi)

Open Terminal

sudo nano /etc/sssd/sssd.conf

Input at the end of the file 

ad_gpo_access_control = permissive
Your "/etc/sssd/sssd.conf" should look like this. Make all necessary changes or copy and paste this into the file replacing everything. Can use CTRL + K to cut entire lines until the file is empty.

[sssd]
domains = m21.gov.local
config_file_version = 2
services = nss, pam

[nss]
homedir_substring = /home

[domain/m21.gov.local]
default_shell = /bin/bash
krb5_store_password_if_offline = True
cache_credentials = True
krb5_realm = M21.GOV.LOCAL
realmd_tags = manages-system joined-with-adcli 
id_provider = ad
fallback_homedir = /home/%u
ad_domain = m21.gov.local
use_fully_qualified_names = False
ldap_id_mapping = True
access_provider = ad
ad_gpo_access_control = permissive

1 To exit:     CTRL      +      'X' 
2 Select     'Y'    for    yes
3 To save:    'enter'   key

On Fedora

sudo authselect select sssd with-mkhomedir
sudo systemctl restart sssd

On Ubuntu

sudo pam-auth-update --enable mkhomedir
sudo systemctl restart sssd

On CentOS 7

sudo authconfig --enablesssdauth --enablesssd --enablemkhomedir --updateall
sudo systemctl restart sssd

******************************COMPLETED*******************************


TO MAKE AD ACCOUNT A SUDOER

Open Terminal

sudo nano /etc/sudoers.d/domain_admins
1

Input line : firstname.lastname ALL=(ALL) ALL

2

To allow all ICT Staff:

%ICT\ Staff\ SG\ M21  ALL=(ALL:ALL) ALL


cn=mydns ict staff sg,ou=security groups_m21,ou=mydns,dc=m21,dc=gov,dc=local

3 To exit:     CTRL      +      'X' 
4 Select     'Y'    for    yes
5 To save:    'enter'   key

******************************COMPLETED*******************************


TO MOUNT SHARE DRIVE

1 Launch the Files app  ->  OTHER LOCATIONS   ->   Bottom of window to enter address
2 Input: smb://172.16.21.16/
3 Toggle on REGISTERED USER
4 Input: YOUR DOMAIN ACCOUNT USERNAME and PASSWORD
5 Domain: M21.GOV.LOCAL  or 172.16.21.161

******************************COMPLETED*******************************


TO ADD PRINTER

Open Terminal

HP Printers

dnf search hplip
sudo dnf install hplip hplip-gui -y
hp-setup
hp-setup ‘printer IP Address’
1 Select detected printer
2 Follow next prompt until the end

XEROX Printers

Open Terminal
wget http://download.support.xerox.com/pub/drivers/CQ8580/drivers/linux/pt_BR/XeroxOfficev5Pkg-Linuxx86_64-5.20.661.4684.rpm
sudo dnf -y localinstall XeroxOfficev5Pkg-Linuxx86_64-5.20.661.4684.rpm


NOTE:   DO NOT PRINT A TEST PAGE!!  Print a regular text document to test

******************************COMPLETED*******************************


TO REPLACE FEDORA LOGO

Download Image and rename as: MYDNS-Logo

MYDNS-Logo.png

1 Go to EXTENSION MANAGER   ->  SYSTEM EXTENSIONS   ->   BACKGROUND LOGO
2 Click on the gear icon to get the background settings
3

Go to LOGO   ->   Filename to attach the MYDNS-Logo.png file

                           ->   Filename (dark) to attach the MYDNS-Logo.png file

4 Scroll down to OPTIONS   ->  Toggle on Show for all backgrounds

******************************COMPLETED*******************************



Browse to 172.16.21.16>fileserver2>General>IT FILES>prx and copy the GORTT.pem file to a folder on the local machine.

  1. Navigate to the location of the certificate file in Terminal (or right click and open from the location)
  2. Move the ceritficate file to the proper location with the following command:
    sudo mv GORTT.pem /etc/pki/ca-trust/source/anchors/GORTT.pem
  3. Update trusted certificates with the following command:
    sudo update-ca-trust

Adding Certificate File to Local Machine (Ubuntu)

Browse to 172.16.21.16>fileserver2>General>IT FILES>prx and copy the GORTT.pem file to a folder on the local machine.

sudo apt-get install -y ca-certificates
  1. Navigate to the location of the certificate file in Terminal (or right click and open from the location)
openssl x509 -in GORTT.pem -out GORTT.crt
  1. Move the ceritficate file to the proper location with the following command:
    sudo mv GORTT.crt /usr/local/share/ca-certificates

  2. Update trusted certificates with the following command:
    sudo update-ca-certificates


HELPFUL  APPS

1

Extension Manager

 

flatpak install flathub com.mattjakeman.ExtensionManager

2 GNOME Tweaks ( sudo dnf install gnome-tweaks )
3

OnlyOffice

https://download.onlyoffice.com/install/desktop/editors/linux/onlyoffice-desktopeditors.x86_64.rpm

sudo dnf -y localinstall onlyoffice-desktopeditors.x86_64.rpm

4

Element

 

flatpak install flathub im.riot.Riot

5

Google Chome (Fedora)


wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm

sudo dnf -y localinstall google-chrome-stable_current_x86_64.rpm

6

Google Chrome (Ubuntu)


sudo apt install curl software-properties-common apt-transport-https ca-certificates -y


curl -fSsL https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor | sudo tee /usr/share/keyrings/google-chrome.gpg > /dev/null


echo deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main | sudo tee /etc/apt/sources.list.d/google-chrome.list


sudo apt update


sudo apt -y install google-chrome-stable


HELPFUL EXTENSIONS

1 Dash to Dock - Displays a dynamic centered Taskbar
2 Dash to Panel - Displays screen width static Taskbar
3 Vitals - displays the PC health at the top right
4 Desktop icons NG (Ding) - display anything saved to desktop
5 Clipboard History - enables clipboard history tool

******************************COMPLETED*******************************