├── EASY_INSTALL ├── LICENSE ├── EASY_INSTALL_PATCHES ├── dnf-smb-mon ├── README ├── patch.sh ├── DC-Installer.sh ├── patches └── samba-dnf-update-patch.sh ├── samba-dnf-pkg-update ├── MEMInstall.sh ├── DC1-Install.sh └── DCInstall.sh /EASY_INSTALL: -------------------------------------------------------------------------------- 1 | #Installing 2 | #Install Rocky Minimal 3 | #Make sure you specify the domain name you want to use for AD. 4 | #After the GUI install: 5 | #(Just copy and paste the following line on the Rocky terminal or remote SSH terminal- remote suggested) 6 | 7 | dnf -y install wget && cd /root && bash <(wget -qO- https://raw.githubusercontent.com/fumatchu/RADS/main/DC-Installer.sh) 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /EASY_INSTALL_PATCHES: -------------------------------------------------------------------------------- 1 | 2 | This is a patch script for any new bugs. It will display a dialog script and allow the End user to choose the patch to apply 3 | 4 | #Current patches: 5 | v1.0.1 6 | 7 | #Samba-dnf-pkgupdate- If Rocky was installed before January 11 2025, pkg update would not install dc rpm on upgrade. corrected. 8 | #Please make sure you have manually set your DNS to something other than the Samba service before attempting to run this file 9 | for example, use "nmtui" to edit the dns interface to 208.67.222.222 (OpenDNS), save and then run the commnad "systemctl restart NetworkManager", followed by nmcli to validate that the settings took effect. 10 | 11 | If you receieve the error of: 12 | Unable to save connection: 13 | 802-3-ethernet.mac-address-denylist: 14 | unknown property 15 | (In nmtui) 16 | you must "systemctl restart NetworkManager" 17 | Modify the DNS entry to the DNS other than AD, 18 | then restart NetworkManager again before downloading the script 19 | 20 | Please keep in mind that this is also true if the script finds updates. YOU MAY run into this scenario above even if AD is operational and resolving 21 | ##END NOTE FOR SAMBA-DNF-PKG-UPDATE## 22 | 23 | #(Just copy and paste the following line on the Rocky terminal or remote SSH terminal- remote suggested) 24 | dnf -y install wget && cd /root && bash <(wget -qO- https://raw.githubusercontent.com/fumatchu/RADS/main/patch.sh) 25 | -------------------------------------------------------------------------------- /dnf-smb-mon: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #Patch 1.0 3 | # dnf_smb_mon 4 | # Monitoring script that will compare the @System Repository with the Upstream Repository for Samba DC 5 | # If a match between the files are resolved, there will be no update 6 | # If a match is NOT resolved between files, a message will be sent to MOTD and upon next login 7 | # the (root) User should run "samba-dnf-pkg-update" 8 | 9 | # Update the DNF cache 10 | dnf makecache 11 | 12 | # Get the Samba versions from the provides output 13 | VERSIONS=$(dnf provides samba | grep -oP 'samba = \K[0-9]+\.[0-9]+\.[0-9]+-[0-9]+') 14 | 15 | # Convert the versions to an array 16 | VERSION_ARRAY=($VERSIONS) 17 | 18 | # Check if two versions were found 19 | if [ ${#VERSION_ARRAY[@]} -lt 2 ]; then 20 | echo "Not enough version information found. Please check your DNF configuration." 21 | exit 1 22 | fi 23 | 24 | # Extract the first and second versions 25 | VERSION1=${VERSION_ARRAY[0]} 26 | VERSION2=${VERSION_ARRAY[1]} 27 | 28 | # Compare the versions 29 | if [ "$VERSION1" == "$VERSION2" ]; then 30 | logger -s "dnf-smb-mon: The Samba versions match: $VERSION1. Repositories are in sync." 2>>/var/log/dnf-smb-mon.log 31 | else 32 | cat </etc/motd 33 | ********************************************* 34 | ATTENTION! 35 | ********************************************* 36 | dnf_smb_mon sees a difference between the @System dnf repository 37 | for Samba and the dnf repository upstream. This probably means that 38 | the upstream Samba packages are a new version and the --dc packages 39 | are now out of date. 40 | 41 | Found versions: $VERSION1 and $VERSION2 42 | You should probably run the command samba-dnf-pkg-update from the cli 43 | or samba service management --> samba update (from server-manager). 44 | EOF 45 | 46 | logger -s "dnf-smb-mon: Repositories are NOT in sync. Found versions: $VERSION1 and $VERSION2. Review and run samba-dnf-pkg-update." 2>>/var/log/dnf-smb-mon.log 47 | fi 48 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | (R)ocky (A)ctive (D)irectory (S)cript Builder 2 | 3 | ############################################### 4 | RADS is NOW FULLY SUPPORTED ON 9.5 and 9.6 for a fresh install and also upgrades from previous versions of Samba 5 | Version 10 support is currently UNSUPPORTED (for now) 6 | ############################################### 7 | A walk-through can also be seen here: 8 | https://youtu.be/daaX67Ovegk 9 | 10 | This is a script to allow a Rocky (RHEL) Server (9.x) to become an AD/DC Server from Samba Source. 11 | Yes, there are others that build RPMS, but why not build it yourself? 12 | I took this approach because there is not one binary that this script modifies and therefore can be a trusted source. 13 | 14 | 15 | ####Pre-requisites 16 | You should install Rocky from scratch. 17 | You should make sure the server has a static IP (If It does not, the installer will walk you through modifying it). 18 | You do not need to (nor should you) install anything. Just a fresh, minimal install. The installer will do the rest for you. 19 | 20 | 21 | The Script will do the following: 22 | Validate that you have a static IP setup. If you do not it will prompt you. 23 | Sets SElinux 24 | Adds Firewall allowances 25 | Enable the Rocky REPOS needed to build 26 | EPEL 27 | CRB 28 | Install the requirements needed by the samba source 29 | Modify chrony to point to 2.rocky.pool.ntp.org 30 | Download and install (Rocky) samba-latest (.src.rpm) 31 | Prompt you for domain provisioning 32 | Add the samba service and enable it 33 | Clean up all the install files (We like to be tidy) 34 | Provide basic AD testing from the console 35 | Kerberos 36 | Kerberos udp (NS) 37 | LDAP (NS 38 | Anonymous login 39 | Authenticated Login 40 | 41 | Install a monitoring script to compare the version of compiled samba to upstream and alert you if 42 | an updated is needed 43 | update the .src.rpm to latest version 44 | Provide you with a "next steps" for samba administration 45 | Suggest a reverse zone from the command line after install, based on your topology 46 | Provide examples for password complexity, history, etc 47 | Provide default user creation command to create your first AD user 48 | 49 | ####Sounds great! How do I get it? 50 | 51 | Installing 52 | Please see the EASY_INSTALL File 53 | 54 | #Installing 55 | #Install Rocky Minimal 56 | #https://rockylinux.org/download/ 57 | #Make sure you specify the domain name you want to use for AD. 58 | #After the GUI install: 59 | #(Just copy and paste the following line on the Rocky terminal) 60 | 61 | dnf -y install wget && cd /root && bash <(wget -qO- https://raw.githubusercontent.com/fumatchu/RADS/main/DC-Installer.sh) 62 | -------------------------------------------------------------------------------- /patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #DC-Installer.sh #Bootstrap to GIT REPO 3 | TEXTRESET=$(tput sgr0) 4 | RED=$(tput setaf 1) 5 | YELLOW=$(tput setaf 3) 6 | GREEN=$(tput setaf 2) 7 | USER=$(whoami) 8 | MAJOROS=$(cat /etc/redhat-release | grep -Eo "[0-9]" | sed '$d') 9 | 10 | #Checking for user permissions 11 | if [ "$USER" = "root" ]; then 12 | echo " " 13 | else 14 | echo ${RED}"This program must be run as root ${TEXTRESET}" 15 | echo "Exiting" 16 | fi 17 | #Checking for version Information 18 | if [ "$MAJOROS" = "9" ]; then 19 | echo " " 20 | else 21 | echo ${RED}"Sorry, but this installer only works on Rocky 9.X ${TEXTRESET}" 22 | echo "Please upgrade to ${GREEN}Rocky 9.x${TEXTRESET}" 23 | echo "Exiting the installer..." 24 | exit 25 | fi 26 | 27 | cat <&1 >/dev/tty); do 128 | case $choice in 129 | 1) clear && cd /root && chmod 700 /root/RADSPatch/patches/samba-dnf-update-patch.sh && /root/RADSPatch/patches/samba-dnf-update-patch.sh;; 130 | 2) exit ;; 131 | 132 | esac 133 | done 134 | clear # clear after user pressed Cancel 135 | -------------------------------------------------------------------------------- /DC-Installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #Bootstrap to GIT REPO 3 | GREEN="\033[0;32m" 4 | RED="\033[0;31m" 5 | YELLOW="\033[1;33m" 6 | TEXTRESET="\033[0m" 7 | CYAN="\e[36m" 8 | RESET="\e[0m" 9 | USER=$(whoami) 10 | MAJOROS=$(cat /etc/redhat-release | grep -Eo "[0-9]" | sed '$d') 11 | clear 12 | echo -e "[${GREEN}SUCCESS${TEXTRESET}] Rocky ${CYAN}RADS/FR-RADS${TEXTRESET} ${YELLOW}Bootstrap${TEXTRESET}" 13 | # Checking for user permissions 14 | if [ "$USER" = "root" ]; then 15 | echo -e "[${GREEN}SUCCESS${TEXTRESET}] Running as root user." 16 | sleep 2 17 | else 18 | echo -e "[${RED}ERROR${TEXTRESET}] This program must be run as root." 19 | echo "Exiting..." 20 | exit 1 21 | fi 22 | 23 | # Extract the major OS version from /etc/redhat-release 24 | if [ -f /etc/redhat-release ]; then 25 | MAJOROS=$(grep -oP '\d+' /etc/redhat-release | head -1) 26 | else 27 | echo -e "[${RED}ERROR${TEXTRESET}] /etc/redhat-release file not found. Cannot determine OS version." 28 | echo "Exiting the installer..." 29 | exit 1 30 | fi 31 | 32 | # Checking for version information 33 | if [ "$MAJOROS" -ge 9 ]; then 34 | echo -e "[${GREEN}SUCCESS${TEXTRESET}] Detected compatible OS version: Rocky 9.x or greater" 35 | sleep 2 36 | else 37 | echo -e "[${RED}ERROR${TEXTRESET}] Sorry, but this installer only works on Rocky 9.X or greater" 38 | echo -e "Please upgrade to ${GREEN}Rocky 9.x${TEXTRESET} or later" 39 | echo "Exiting the installer..." 40 | exit 1 41 | fi 42 | 43 | echo -e "${CYAN}==>Retrieving requirements for the installer...${TEXTRESET}" 44 | 45 | # Function to show an animated spinner 46 | spinner() { 47 | local pid=$1 48 | local delay=0.1 49 | local spinstr='|/-\' 50 | 51 | while ps -p $pid > /dev/null; do 52 | for i in $(seq 0 3); do 53 | printf "\r[${YELLOW}INFO${TEXTRESET}] Installing... ${spinstr:$i:1}" 54 | sleep $delay 55 | done 56 | done 57 | printf "\r[${GREEN}SUCCESS${TEXTRESET}] Installation complete! \n" 58 | } 59 | 60 | # Run dnf in the background 61 | dnf -y install wget git ipcalc dialog >/dev/null 2>&1 & 62 | 63 | # Get the PID of the last background process 64 | dnf_pid=$! 65 | 66 | # Start the spinner while waiting for dnf to complete 67 | spinner $dnf_pid 68 | 69 | echo -e "${CYAN}==>Retrieving files from Github...${TEXTRESET}" 70 | 71 | sleep 1 72 | 73 | 74 | #Clone RADS 75 | 76 | mkdir -p /root/ADDCInstaller 77 | rm -rf /root/ADDCInstaller && git clone https://github.com/fumatchu/RADS.git /root/ADDCInstaller 78 | chmod 700 /root/ADDCInstaller/* 79 | 80 | 81 | #Clone FR 82 | 83 | mkdir -p /root/FR-Installer 84 | rm -rf /root/FR-Installer && git clone https://github.com/fumatchu/FR-RADS.git /root/FR-Installer 85 | 86 | 87 | echo -e "[${YELLOW}INFO${TEXTRESET}] Removing Git" 88 | dnf -y remove git >/dev/null 2>&1 89 | 90 | clear 91 | echo -e "${GREEN} 92 | .*((((((((((((((((* 93 | .(((((((((((((((((((((((((((/ 94 | ,((((((((((((((((((((((((((((((((((. 95 | (((((((((((((((((((((((((((((((((((((((/ 96 | (((((((((((((((((((((((((((((((((((((((((((/ 97 | .((((((((((((((((((((((((((((((((((((((((((((( 98 | ,((((((((((((((((((((((((((((((((((((((((((((((((. 99 | ((((((((((((((((((((((((((((((/ ,((((((((((((((( 100 | /((((((((((((((((((((((((((((. /((((((((((((* 101 | ((((((((((((((((((((((((((/ (((((((((( 102 | (((((((((((((((((((((((( *((((((/ 103 | /((((((((((((((((((((* (((((* 104 | (((((((((((((((((( (((* ,(( 105 | .((((((((((((((. /((((((( 106 | ((((((((((/ (((((((((((((/ 107 | *((((((. /((((((((((((((((((. 108 | *(*) ,(((((((((((((((((((((((, 109 | (((((((((((((((((((((((/ 110 | /((((((((((((((((((((((. 111 | ,((((((((((((((, 112 | ${RESET}" 113 | echo -e " ${GREEN}Rocky Linux${RESET} ${CYAN}RADS/FR-FADS${RESET} ${YELLOW}Builder${TEXTRESET}" 114 | 115 | sleep 2 116 | 117 | echo " " 118 | echo -e " 119 | ********************************************* 120 | 121 | This script was created for ${GREEN}Rocky 9.x${TEXTRESET} 122 | This will install: 123 | 1. A primary Samba AD/DC (and create the Forest/Domain) 124 | ${YELLOW}-OR-${TEXTRESET} 125 | 2. An additional AD server and provision it. 126 | ${YELLOW}-OR-${TEXTRESET} 127 | 3. A Member Server to a Domain for File/Print Services 128 | ${YELLOW}-OR-${TEXTRESET} 129 | 4. Provision and integrate a FreeRADIUS server 130 | 131 | ${RED}Each Server must be installed on a separate server (VM/Hardware) instance${TEXTRESET} 132 | 133 | What this script does: 134 | 1. Apply appropriate SELinux context and Firewall rules 135 | 2. Install the REPO(s) and any needed dependencies 136 | 3. Compile Samba RPMS (if deploying AD) 137 | 4. Configure the system as needed, based on your answers 138 | 5. Provide testing for the configured platform 139 | 6. Install Server Management Tools 140 | 141 | ********************************************* 142 | " 143 | echo " " 144 | read -p "Press Enter to Continue" 145 | 146 | 147 | items=(1 "Install First AD Server/Create Domain" 148 | 2 "Install Secondary/Tertiary AD Server" 149 | 3 "Install a Member Server for File/Print Services" 150 | 4 "Install FreeRADIUS Server" 151 | ) 152 | 153 | while choice=$(dialog --title "$TITLE" \ 154 | --backtitle "Server Installer" \ 155 | --menu "Please select the install type" 15 65 3 "${items[@]}" \ 156 | 2>&1 >/dev/tty); do 157 | case $choice in 158 | 1) /root/ADDCInstaller/DCInstall.sh ;; 159 | 2) /root/ADDCInstaller/DC1-Install.sh ;; 160 | 3) /root/ADDCInstaller/MEMInstall.sh ;; 161 | 4) /root/FR-Installer/install.sh ;; 162 | 163 | 164 | esac 165 | done 166 | clear 167 | -------------------------------------------------------------------------------- /patches/samba-dnf-update-patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This patch file will update dnf-update errors where dc rpm does not install 3 | # Updates dnf-smb-mon for makecache versions 4 | # puts a checker into server manager for motd content if dnf-smb-mon sees variance 5 | # Define colors for output 6 | TEXTRESET=$(tput sgr0) 7 | RED=$(tput setaf 1) 8 | YELLOW=$(tput setaf 3) 9 | GREEN=$(tput setaf 2) 10 | 11 | # Get current user 12 | USER=$(whoami) 13 | 14 | # Checking for user permissions 15 | if [ "$USER" != "root" ]; then 16 | echo "${RED}This program must be run as root${TEXTRESET}" 17 | echo "Exiting" 18 | exit 1 19 | fi 20 | 21 | # Checking for version Information 22 | MAJOROS=$(grep -oP '(?<=^VERSION_ID=")[0-9]+' /etc/os-release) 23 | 24 | if [ "$MAJOROS" != "9" ]; then 25 | echo "${RED}Sorry, but this installer only works on Rocky 9.X${TEXTRESET}" 26 | echo "Please upgrade to ${GREEN}Rocky 9.x${TEXTRESET}" 27 | echo "Exiting the installer..." 28 | exit 1 29 | fi 30 | 31 | # Define file paths 32 | CURRENT_FILE="/usr/bin/samba-dnf-pkg-update" 33 | PATCH_FILE="/root/RADSPatch/samba-dnf-pkg-update" 34 | DESTINATION_FILE="/usr/bin/samba-dnf-pkg-update" 35 | 36 | # Check if the line #Patch1.0 is present in the current file 37 | if ! grep -q "#Patch1.0" "$CURRENT_FILE"; then 38 | echo "${YELLOW}The file is an older version and will be updated${TEXTRESET}" 39 | sleep 2 40 | 41 | # Move the patch file to the destination 42 | if \cp -f "$PATCH_FILE" "$DESTINATION_FILE"; then 43 | chmod 700 "$DESTINATION_FILE" 44 | echo "${GREEN}The patch file was successfully moved to $DESTINATION_FILE.${TEXTRESET}" 45 | sleep 2 46 | 47 | # Compare the moved file with the original file 48 | echo "${YELLOW}Comparing the Patch file to the file on the system" ${TEXTRESET} 49 | if diff -q "$DESTINATION_FILE" "$PATCH_FILE" > /dev/null; then 50 | echo "${GREEN}The patch file matches the active file.${TEXTRESET}" 51 | else 52 | echo "${RED}The patch file does not match the active file.${TEXTRESET}" 53 | echo "Exiting the script." 54 | sleep 5 55 | exit 1 56 | fi 57 | #Update RADS-SM for the update script 58 | # Define the file path 59 | FILE_PATH="/root/.servman/SambaMan" 60 | 61 | # Use sed to replace the specific line 62 | sed -i 's|6) /root/.servman/SambaManager/sm-samba-dnf-pkg-update ;;|6) /usr/bin/samba-dnf-pkg-update ;;|' "$FILE_PATH" 63 | 64 | # Check if the operation was successful 65 | if [ $? -eq 0 ]; then 66 | echo "The line was successfully updated in $FILE_PATH." 67 | else 68 | echo "Failed to update the line in $FILE_PATH." 69 | fi 70 | #Cleanup 71 | rm -r -f /root/RADSPatch/ 72 | 73 | 74 | #UPDATE server-manager for smb-mon 75 | # Define the path for the server-manager script 76 | echo ${GREEN}"Updating Server-Manager"${TEXTRESET} 77 | SERVER_MANAGER_PATH="/usr/bin/server-manager" 78 | 79 | # Remove the existing server-manager file if it exists 80 | if [ -f "$SERVER_MANAGER_PATH" ]; then 81 | rm "$SERVER_MANAGER_PATH" 82 | echo ${GREEN}"Deleted existing $SERVER_MANAGER_PATH."${TEXTRESET} 83 | fi 84 | 85 | # Create a new server-manager file with the given content 86 | cat << 'EOF' > "$SERVER_MANAGER_PATH" 87 | #!/bin/bash 88 | TEXTRESET=$(tput sgr0) 89 | RED=$(tput setaf 1) 90 | YELLOW=$(tput setaf 3) 91 | GREEN=$(tput setaf 2) 92 | user=$(whoami) 93 | 94 | # Checking for user permissions 95 | if [ "$user" != "root" ]; then 96 | echo ${RED}"This program must be run as root ${TEXTRESET}" 97 | echo "Exiting" 98 | exit 99 | else 100 | echo "Running Program" 101 | fi 102 | 103 | MOTD_FILE="/etc/motd" 104 | EXECUTABLE="/usr/bin/samba-dnf-pkg-update" 105 | 106 | # Check if the MOTD file is empty 107 | if [ -s "$MOTD_FILE" ]; then 108 | # Display the contents of the MOTD file 109 | cat "$MOTD_FILE" 110 | 111 | # Prompt the user if they want to run the executable 112 | read -p "Do you want to run the $EXECUTABLE? (y/n): " response 113 | 114 | # Convert the response to lowercase 115 | response=$(echo "$response" | tr '[:upper:]' '[:lower:]') 116 | 117 | # Run the executable if the user agrees 118 | if [ "$response" == "y" ] || [ "$response" == "yes" ]; then 119 | if [ -x "$EXECUTABLE" ]; then 120 | echo "Running $EXECUTABLE..." 121 | "$EXECUTABLE" 122 | else 123 | echo "Error: $EXECUTABLE is not executable or not found." 124 | fi 125 | else 126 | echo "The executable was not run." 127 | fi 128 | else 129 | echo "" 130 | fi 131 | 132 | items=( 133 | 1 "Active Directory Management" 134 | 2 "DHCP Management" 135 | 3 "Samba Service Management" 136 | 4 "System Management" 137 | 5 "Server Management Options" 138 | 6 "System Tools" 139 | 7 "Welcome to Server Manager" 140 | ) 141 | 142 | while choice=$(dialog --title "Server Manager" \ 143 | --backtitle "Server Management" \ 144 | --menu "Please select" 20 40 3 "${items[@]}" \ 145 | 2>&1 >/dev/tty); do 146 | case $choice in 147 | 1) /root/.servman/ADMan ;; # some action on 1 148 | 2) /root/.servman/DHCPMan ;; # some action on 2 149 | 3) /root/.servman/SambaMan ;; 150 | 4) /root/.servman/SYSMan ;; # some action on other 151 | 5) /root/.servman/SERVMan ;; 152 | 6) /root/.servman/TOOLMan ;; 153 | 7) /root/.servman/welcome.readme | more ;; 154 | esac 155 | done 156 | 157 | clear # clear after user pressed Cancel 158 | EOF 159 | 160 | # Set the script as executable with permissions 700 161 | chmod 700 "$SERVER_MANAGER_PATH" 162 | echo ${GREEN}"Created and set permissions for $SERVER_MANAGER_PATH."${TEXTRESET} 163 | 164 | # Validate the contents of the new file 165 | if grep -q "#!/bin/bash" "$SERVER_MANAGER_PATH"; then 166 | echo ${GREEN}"The contents of $SERVER_MANAGER_PATH have been verified."${TEXTRESET} 167 | else 168 | echo "Error: The contents of $SERVER_MANAGER_PATH could not be verified." 169 | fi 170 | 171 | #UPDATE smb-mon 172 | 173 | echo ${GREEN}"Updating smb-mon"${TEXTRESET} 174 | # Define the file path 175 | FILE_PATH="/usr/bin/dnf-smb-mon" 176 | 177 | # Remove the existing file, if it exists 178 | if [ -f "$FILE_PATH" ]; then 179 | rm -f "$FILE_PATH" 180 | echo "Removed existing file at $FILE_PATH" 181 | fi 182 | 183 | # Create a new file and add the specified content 184 | cat << 'EOF' > "$FILE_PATH" 185 | #!/bin/bash 186 | #Patch 1.0 187 | #dnf_smb_mon 188 | #Monitoring script that will compare the @System Repository with the Upstream Repository for Samba DC 189 | #If a match between the files are resolved, there will be no update 190 | #If a match is NOT resolved between files, a message will be sent to MOTD and upon next login 191 | #the (root) User should run "samba-dnf-pkg-update" 192 | 193 | #Update the DNF cache 194 | dnf makecache 195 | 196 | #Get the Samba versions from the provides output 197 | VERSIONS=$(dnf provides samba | grep -oP 'samba = \K[0-9]+.[0-9]+.[0-9]+-[0-9]+') 198 | 199 | #Convert the versions to an array 200 | VERSION_ARRAY=($VERSIONS) 201 | 202 | #Check if two versions were found 203 | if [ ${#VERSION_ARRAY[@]} -lt 2 ]; then 204 | echo "Not enough version information found. Please check your DNF configuration." 205 | exit 1 206 | fi 207 | 208 | #Extract the first and second versions 209 | VERSION1=${VERSION_ARRAY[0]} 210 | VERSION2=${VERSION_ARRAY[1]} 211 | 212 | #Compare the versions 213 | if [ "$VERSION1" == "$VERSION2" ]; then 214 | logger -s "dnf-smb-mon: The Samba versions match: $VERSION1. Repositories are in sync." 2>>/var/log/dnf-smb-mon.log 215 | else 216 | cat < /etc/motd 217 | ATTENTION! 218 | dnf_smb_mon sees a difference between the @System dnf repository 219 | for Samba and the dnf repository upstream. This probably means that 220 | the upstream Samba packages are a new version and the --dc packages 221 | are now out of date. 222 | Found versions: $VERSION1 and $VERSION2 223 | You should probably run the command samba-dnf-pkg-update from the cli 224 | or samba service management --> samba update (from server-manager). 225 | EOM 226 | logger -s "dnf-smb-mon: Repositories are NOT in sync. Found versions: $VERSION1 and $VERSION2. Review and run samba-dnf-pkg-update." 2>>/var/log/dnf-smb-mon.log 227 | fi 228 | EOF 229 | 230 | # Make the new file executable with 700 permissions 231 | chmod 700 "$FILE_PATH" 232 | echo ${GREEN}"Created new file at $FILE_PATH with 700 permissions"${TEXTRESET} 233 | 234 | # Validate the file was updated 235 | if [ -f "$FILE_PATH" ]; then 236 | echo ${GREEN}"File $FILE_PATH successfully created and updated."${TEXTRESET} 237 | else 238 | echo ${RED}"Failed to create or update $FILE_PATH."${TEXTRESET} 239 | exit 1 240 | fi 241 | 242 | 243 | 244 | 245 | # Ask the user if they want to run the file 246 | read -p "Do you want to run the file samba-dnf-pkg-update now (This will start the samba update to latest version using mock)? (yes/no): " response 247 | 248 | # Convert the response to lowercase 249 | response=$(echo "$response" | tr '[:upper:]' '[:lower:]') 250 | 251 | # Run the file if the user agrees 252 | if [ "$response" = "yes" ]; then 253 | bash "$DESTINATION_FILE" 254 | else 255 | echo "${RED}The file was not run.${TEXTRESET}" 256 | echo "You have updated the package updater file, but it has not been run. You have only patched the file." 257 | echo "In order to actually update the server to the latest version of samba, you must run /usr/bin/samba-dnf-pkg-update as root." 258 | fi 259 | else 260 | echo "${RED}The file was not successfully moved.${TEXTRESET}" 261 | fi 262 | else 263 | echo "The file already contains #Patch1.0." 264 | fi 265 | 266 | read -p "Press Enter to continue..." 267 | -------------------------------------------------------------------------------- /samba-dnf-pkg-update: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #samba-dnf-pkg-update 3 | #Patch1.0 4 | TEXTRESET=$(tput sgr0) 5 | RED=$(tput setaf 1) 6 | YELLOW=$(tput setaf 3) 7 | GREEN=$(tput setaf 2) 8 | DOMAIN=$(hostname | sed 's/^[^.:]*[.:]//' | sed -e 's/\(.*\)/\U\1/') 9 | FQDN=$(hostname) 10 | user=$(whoami) 11 | DNSSERVER=208.67.222.222 12 | IP=$(hostname -I) 13 | INTERFACE=$(nmcli | grep "connected to" | cut -d " " -f4) 14 | #Checking for user permissions 15 | if [ "$user" != "root" ]; then 16 | echo ${red}"This program must be run as root ${textreset}" 17 | echo "Exiting" 18 | exit 19 | else 20 | echo "Running Program" 21 | fi 22 | 23 | #Cleanup 24 | rm -f /root/samba*.rpm 25 | 26 | echo ${GREEN}"Restarting NetworkManager"${TEXTRESET} 27 | systemctl restart NetworkManager 28 | sleep 5 29 | 30 | #Set DNS to Something other than itself(Use ODNS) 31 | echo ${YELLOW}"Setting DNS to OpenDNS Server (Temporarily) for Name Resolution"${TEXTRESET} 32 | nmcli con mod $INTERFACE ipv4.dns $DNSSERVER 33 | systemctl restart NetworkManager 34 | sleep 1 35 | 36 | 37 | #Check for Network Connectivity 38 | echo "Checking for Internet Connectivity" 39 | echo " " 40 | sleep 3 41 | # Function to check DNS resolution 42 | check_dns_resolution() { 43 | local domain=$1 44 | ping -c 1 $domain &> /dev/null 45 | return $? 46 | } 47 | 48 | # Function to ping an address 49 | ping_address() { 50 | local address=$1 51 | ping -c 1 $address &> /dev/null 52 | return $? 53 | } 54 | 55 | # Flag to track if any test fails 56 | test_failed=false 57 | 58 | # Check DNS resolution for google.com 59 | echo "Checking DNS resolution for google.com via ping..." 60 | if check_dns_resolution "google.com"; then 61 | echo "DNS resolution for google.com is successful." 62 | else 63 | echo "DNS resolution for google.com failed." 64 | test_failed=true 65 | fi 66 | 67 | # Ping 8.8.8.8 68 | echo "Trying to ping 8.8.8.8..." 69 | if ping_address "8.8.8.8"; then 70 | echo "Successfully reached 8.8.8.8." 71 | else 72 | echo "Cannot reach 8.8.8.8." 73 | test_failed=true 74 | fi 75 | 76 | # Provide final results summary 77 | echo 78 | echo "===== TEST RESULTS =====" 79 | echo "DNS Resolution for google.com: $(if check_dns_resolution "google.com"; then echo "${GREEN}Passed"${TEXTRESET}; else echo "${RED}Failed"${TEXTRESET}; fi)" 80 | echo "Ping to 8.8.8.8: $(if ping_address "8.8.8.8"; then echo "${GREEN}Passed"${TEXTRESET}; else echo "${RED}Failed"${TEXTRESET}; fi)" 81 | echo "========================" 82 | echo 83 | 84 | # Prompt the user only if any test fails 85 | if $test_failed; then 86 | read -p "One or more tests failed. Please make sure you can resolve DNS names before Proceeding. Do you want to continue the script? (y/n): " user_input 87 | if [[ $user_input == "y" || $user_input == "Y" ]]; then 88 | echo "Continuing the script with failures" 89 | sleep 1 90 | # Place additional script logic here 91 | else 92 | echo "Please make sure that you have full Connectivty to the Internet Before Proceeding." 93 | exit 1 94 | fi 95 | else 96 | echo "All tests passed successfully." 97 | echo " " 98 | sleep 3 99 | # Continue with the script or exit as needed 100 | fi 101 | 102 | #In the event a samba update arrives from Rocky Linux, rebuild Samba packages 103 | dnfremote=$(dnf provides samba | grep Provide | sed 's/.dc//' | sed '1d') 104 | dnflocal=$(dnf provides samba | grep Provide | sed 's/.dc//' | sed '$d') 105 | mocksmbver=$(dnf provides samba | grep 'Provide' | awk -F' = ' '{print $2}' | sort -V | tail -n 1) 106 | majoros=$(cat /etc/redhat-release | grep -Eo "[0-9]" | sed '$d') 107 | minoros=$(cat /etc/redhat-release | grep -Eo "[0-9]" | sed '1d') 108 | 109 | cat < /dev/null 2>&1; then 144 | echo ${GREEN}"Samba RPM file found. Continuing with the script..."${TEXTRESET} 145 | 146 | mock -r rocky-"$majoros"-x86_64 --enablerepo=devel --define 'dist .el'"$majoros"'_'"$minoros"'.dc' --with dc samba-"$mocksmbver".src.rpm 147 | cp /var/lib/mock/rocky-"$majoros"-x86_64/result/*.rpm /root/.samba 148 | createrepo /root/.samba 149 | dnf config-manager --add-repo /root/.samba 150 | dnf -y update --nogpgcheck --repofrompath=samba,/root/.samba 151 | dnf -y install --nogpgcheck samba-dc 152 | # Reset MOTD 153 | sed -i d /etc/motd 154 | else 155 | echo ${RED}"The Samba source rpm file did not download correctly. Please check your network settings."${TEXTRESET} 156 | 157 | # Check DNS resolution 158 | if nslookup google.com > /dev/null 2>&1; then 159 | echo ${GREEN}"DNS is operational."${TEXTRESET} 160 | else 161 | echo ${RED}"DNS is not operational. Please check your DNS settings."${TEXTRESET} 162 | fi 163 | 164 | exit 1 165 | fi 166 | 167 | break 168 | ;; 169 | [nN]) 170 | echo "Exiting..." 171 | exit 172 | ;; 173 | *) 174 | echo "Invalid response" 175 | ;; 176 | esac 177 | done 178 | 179 | #Cleanup 180 | rm -f /root/samba*.rpm 181 | echo ${GREEN}"Restarting NetworkManager"${TEXTRESET} 182 | systemctl restart NetworkManager 183 | sleep 5 184 | #Set DNS back to AD 185 | echo ${YELLOW}"Setting DNS entry back to the local interface from OPENDNS to AD"${TEXTRESET} 186 | nmcli con mod $INTERFACE ipv4.dns $IP 187 | echo " " 188 | sleep 2 189 | echo ${GREEN}"Restarting NetworkManager"${TEXTRESET} 190 | systemctl restart NetworkManager 191 | sleep 5 192 | echo ${GREEN}"Attempting to enable samba.service"${TEXTRESET} 193 | sleep 1 194 | systemctl enable samba.service 195 | echo " " 196 | echo ${GREEN}"Attempting to start samba.service"${TEXTRESET} 197 | systemctl start samba.service 198 | sleep 1 199 | #return status 200 | # Check the status of the samba service 201 | SERVICE="samba" 202 | STATUS=$(systemctl is-active "$SERVICE") 203 | 204 | # Provide feedback based on the service status 205 | if [ "$STATUS" = "active" ]; then 206 | echo ${GREEN}"The $SERVICE service is running."${TEXTRESET} 207 | else 208 | echo ${RED}"The $SERVICE service is not running."${TEXTRESET} 209 | fi 210 | 211 | #Validate that the server can resolve SRV records 212 | echo ${GREEN}"Setting up Query for SRV records"${TEXTRESET} 213 | sleep 1 214 | 215 | host -t SRV _ldap._tcp.${DOMAIN}. 216 | host -t SRV _kerberos._udp.${DOMAIN}. 217 | host -t A ${FQDN}. 218 | 219 | # Extract hostname from FQDN 220 | hostname_part=$(echo "$FQDN" | cut -d '.' -f 1) 221 | 222 | # Function to extract the target hostnames from SRV records 223 | get_srv_hostnames() { 224 | local srv_records=$1 225 | echo "$srv_records" | awk '{print $NF}' | cut -d '.' -f 1 # Get the last field and extract the hostname part 226 | } 227 | 228 | # Get SRV records for LDAP and Kerberos 229 | ldap_srv=$(host -t SRV _ldap._tcp."$DOMAIN") 230 | kerberos_srv=$(host -t SRV _kerberos._udp."$DOMAIN") 231 | 232 | # Extract target hostnames from SRV records 233 | ldap_hostnames=$(get_srv_hostnames "$ldap_srv") 234 | kerberos_hostnames=$(get_srv_hostnames "$kerberos_srv") 235 | 236 | # Function to check if at least one hostname matches 237 | check_any_hostnames_match() { 238 | local hostnames=$1 239 | local hostname=$2 240 | for hn in $hostnames; do 241 | if [ "$hn" == "$hostname" ]; then 242 | echo "At least one hostname matches: ${GREEN}$hn"${TEXTRESET} 243 | return 0 244 | fi 245 | done 246 | return 1 247 | } 248 | 249 | # Check if any LDAP or Kerberos hostnames match the hostname part of FQDN 250 | if check_any_hostnames_match "$ldap_hostnames" "$hostname_part" || check_any_hostnames_match "$kerberos_hostnames" "$hostname_part"; then 251 | echo "${GREEN}Success:${TEXTRESET}Hostname from SRV record matches.." 252 | echo "AD is resolvable" 253 | exit_status=0 254 | else 255 | echo ${RED}"Error:${TEXTRESET} No hostnames from SRV records match the hostname part of FQDN. The samba service has failed to start or DNS is not configured correctly." 256 | echo "AD Failed to resolve" 257 | exit_status=1 258 | read -p "Press Enter" 259 | fi 260 | 261 | # Handle exit status 262 | if [ $exit_status -eq 0 ]; then 263 | read -p "It is reccomended to restart the server. Would you like to do that now? (y/n): " user_input 264 | if [ "$user_input" == "y" ] || [ "$user_input" == "Y" ]; then 265 | echo ${RED}"Restarting the server..."${TEXTRESET} 266 | sleep 2 267 | sudo shutdown -r now 268 | else 269 | echo "Server restart canceled." 270 | fi 271 | else 272 | exit 1 273 | fi 274 | -------------------------------------------------------------------------------- /MEMInstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #MEMinstall.sh 3 | #This installer will install a member server to a pre-existing domain 4 | clear 5 | dnf -y install net-tools dmidecode 6 | TEXTRESET=$(tput sgr0) 7 | RED=$(tput setaf 1) 8 | YELLOW=$(tput setaf 3) 9 | GREEN=$(tput setaf 2) 10 | INTERFACE=$(nmcli | grep "connected to" | cut -d " " -f4) 11 | FQDN=$(hostname) 12 | IP=$(hostname -I) 13 | FQDN=$(hostname) 14 | DOMAIN=$(hostname | sed 's/^[^.:]*[.:]//' | sed -e 's/\(.*\)/\U\1/') 15 | ADDOMAIN=$(hostname | sed 's/^[^.:]*[.:]//' | cut -d. -f1 | sed -e 's/\(.*\)/\U\1/') 16 | USER=$(whoami) 17 | MAJOROS=$(cat /etc/redhat-release | grep -Eo "[0-9]" | sed '$d') 18 | DETECTIP=$(nmcli -f ipv4.method con show $INTERFACE) 19 | NMCLIIP=$(nmcli | grep inet4 | sed '$d' | cut -c7- | cut -d / -f1) 20 | HWKVM=$(dmidecode | grep -i -e manufacturer -e product -e vendor | grep KVM | cut -c16-) 21 | HWVMWARE=$(dmidecode | grep -i -e manufacturer -e product -e vendor | grep Manufacturer | grep "VMware, Inc." | cut -c16- | cut -d , -f1) 22 | n='([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' 23 | m='([0-9]|[12][0-9]|3[012])' 24 | #Bracketed pasting...yuck! 25 | sed -i '8i set enable-bracketed-paste off' /etc/inputrc 26 | #Checking for user permissions 27 | if [ "$USER" = "root" ]; then 28 | echo " " 29 | else 30 | echo ${RED}"This program must be run as root ${TEXTRESET}" 31 | echo "Exiting" 32 | fi 33 | #Checking for version Information 34 | if [ "$MAJOROS" = "9" ]; then 35 | echo " " 36 | else 37 | echo ${RED}"Sorry, but this installer only works on Rocky 9.X ${TEXTRESET}" 38 | echo "Please upgrade to ${GREEN}Rocky 9.x${TEXTRESET}" 39 | echo "Exiting the installer..." 40 | exit 41 | fi 42 | clear 43 | cat <" 51 | exit 1 52 | fi 53 | 54 | if [ "$DETECTIP" = "ipv4.method: auto" ]; then 55 | echo ${RED}"Interface $INTERFACE is using DHCP${TEXTRESET}" 56 | read -p "Please provide a static IP address in CIDR format (i.e 192.168.24.2/24): " IPADDR 57 | while [ -z "$IPADDR" ]; do 58 | echo ${RED}"The response cannot be blank. Please Try again${TEXTRESET}" 59 | read -p "Please provide a static IP address in CIDR format (i.e 192.168.24.2/24): " IPADDR 60 | done 61 | while [[ ! $IPADDR =~ ^$n(\.$n){3}/$m$ ]]; do 62 | read -p ${RED}"The entry is not in valid CIDR notation. Please Try again:${TEXTRESET} " IPADDR 63 | done 64 | read -p "Please Provide a Default Gateway Address: " GW 65 | while [ -z "$GW" ]; do 66 | echo ${RED}"The response cannot be blank. Please Try again${TEXTRESET}" 67 | read -p "Please Provide a Default Gateway Address: " GW 68 | done 69 | read -p "Please provide the FQDN of this machine: " HOSTNAME 70 | while [ -z "$HOSTNAME" ]; do 71 | echo ${RED}"The response cannot be blank. Please Try again${TEXTRESET}" 72 | read -p "Please provide the FQDN of this machine: " HOSTNAME 73 | done 74 | read -p "Please provide the IP address of the Active Directory server: " DNSSERVER 75 | while [ -z "$DNSSERVER" ]; do 76 | echo ${RED}"The response cannot be blank. Please Try again${TEXTRESET}" 77 | read -p "Please provide the IP address of the Active Directory server: " DNSSERVER 78 | done 79 | read -p "Please provide the domain search name: " DNSSEARCH 80 | while [ -z "$DNSSEARCH" ]; do 81 | echo ${RED}"The response cannot be blank. Please Try again${TEXTRESET}" 82 | read -p "Please provide the domain search name: " DNSSEARCH 83 | done 84 | 85 | clear 86 | 87 | cat <>/root/.bash_profile 112 | reboot 113 | exit 114 | else 115 | echo ${GREEN}"Interface $INTERFACE is using a static IP address ${TEXTRESET}" 116 | fi 117 | clear 118 | 119 | if [ "$FQDN" = "localhost.localdomain" ]; then 120 | cat <>/root/.bash_profile 140 | reboot 141 | exit 142 | fi 143 | 144 | clear 145 | 146 | cat </etc/issue 375 | \S 376 | Kernel \r on an \m 377 | Hostname: \n 378 | IP Address: \4 379 | EOF 380 | 381 | clear 382 | 383 | #Add option for cockpit install 384 | cat </etc/samba/smb.tmp 404 | 405 | #Create a writable share to be used by an AD group 406 | #Make sure that before you enable the share, you chown, recursively 407 | #(i.e.) chown -R root."${ADDOMAIN}\domain users" /path/to/share 408 | #Also change your permissions for the files/directories. Default is RWX(770) for Users and Groups 409 | # chmod -R 770 /path/to/share 410 | #SELinux also comes into play: 411 | #(top directory-root of share) 412 | #chcon -t samba_share_t /path/to/share/ -R 413 | #Remember to uncomment and modify the lines below 414 | #[SHARE_NAME] 415 | #writeable = yes 416 | #write list = @"${ADDOMAIN}\domain users" 417 | #path = /path/to/share 418 | #force group = "${ADDOMAIN}\domain users" 419 | #force create mode = 2770 420 | #comment = Data Share 421 | #valid users = @"${ADDOMAIN}\domain users" 422 | #create mode = 2770 423 | #directory mode = 2770 424 | #directory mask = 2770 425 | EOF 426 | 427 | cat /etc/samba/smb.tmp >> /etc/samba/smb.conf 428 | 429 | #Enable and start samba services 430 | systemctl enable smb 431 | systemctl start smb 432 | 433 | #Clean up Install files 434 | sed -i '/MEMInstall.sh/d' /root/.bash_profile 435 | rm -r -f /root/FR-Installer 436 | rm -r -f /root/DC-Installer.sh 437 | rm -r -f /root/ADDCInstaller 438 | rm -r -f /root/FR-Installer.sh 439 | 440 | clear 441 | cat <> "$PROFILE" 54 | 55 | ## Run RADS installer on every interactive login ## 56 | if [[ $- == *i* ]]; then 57 | /root/ADDCInstaller/DC1-Install.sh 58 | fi 59 | EOF 60 | if [[ -f "$INSTALLER" ]]; then 61 | chmod +x "$INSTALLER" 62 | else 63 | echo "WARNING: Installer not found at $INSTALLER" 64 | fi 65 | 66 | 67 | # ========= VALIDATION HELPERS ========= 68 | validate_cidr() { [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; } 69 | validate_ip() { [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; } 70 | validate_fqdn() { [[ "$1" =~ ^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$ ]]; } 71 | 72 | is_host_ip() { 73 | local cidr="$1" 74 | local ip_part="${cidr%/*}" 75 | local mask="${cidr#*/}" 76 | 77 | IFS='.' read -r o1 o2 o3 o4 <<< "$ip_part" 78 | ip_dec=$(( (o1 << 24) + (o2 << 16) + (o3 << 8) + o4 )) 79 | 80 | netmask=$(( 0xFFFFFFFF << (32 - mask) & 0xFFFFFFFF )) 81 | network=$(( ip_dec & netmask )) 82 | broadcast=$(( network | ~netmask & 0xFFFFFFFF )) 83 | 84 | [[ "$ip_dec" -eq "$network" || "$ip_dec" -eq "$broadcast" ]] && return 1 || return 0 85 | } 86 | 87 | check_hostname_in_domain() { 88 | local fqdn="$1" 89 | local hostname="${fqdn%%.*}" 90 | local domain="${fqdn#*.}" 91 | [[ ! "$domain" =~ (^|\.)"$hostname"(\.|$) ]] 92 | } 93 | isValidIP() { 94 | [[ $1 =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1 95 | IFS=. read -r o1 o2 o3 o4 <<< "$1" 96 | (( o1 <= 255 && o2 <= 255 && o3 <= 255 && o4 <= 255 )) || return 1 97 | return 0 98 | } 99 | 100 | isValidNetmask() { 101 | local valid=( 102 | 255.255.255.0 255.255.0.0 255.0.0.0 103 | 255.255.254.0 255.255.252.0 255.255.248.0 255.255.240.0 104 | 255.255.224.0 255.255.192.0 255.255.128.0 105 | ) 106 | [[ " ${valid[*]} " =~ " $1 " ]] 107 | } 108 | 109 | isIPInRange() { 110 | local ip=$1 111 | local ipnum=$(ipToNumber "$ip") 112 | local netnum=$(ipToNumber "$NETWORK") 113 | local broadnum=$(ipToNumber "$BROADCAST") 114 | [[ $ipnum -ge $netnum && $ipnum -le $broadnum ]] 115 | } 116 | 117 | # ========= SYSTEM CHECKS ========= 118 | check_root_and_os() { 119 | if [[ "$EUID" -ne 0 ]]; then 120 | dialog --aspect 9 --title "Permission Denied" --msgbox "This script must be run as root." 7 50 121 | clear; exit 1 122 | fi 123 | 124 | if [[ -f /etc/redhat-release ]]; then 125 | MAJOROS=$(grep -oP '\d+' /etc/redhat-release | head -1) 126 | else 127 | dialog --title "OS Check Failed" --msgbox "/etc/redhat-release not found. Cannot detect OS." 7 50 128 | exit 1 129 | fi 130 | 131 | if [[ "$MAJOROS" -lt 9 ]]; then 132 | dialog --title "Unsupported OS" --msgbox "This installer requires Rocky Linux 9.x or later." 7 50 133 | exit 1 134 | fi 135 | } 136 | 137 | # ========= SELINUX CHECK ========= 138 | check_and_enable_selinux() { 139 | local current_status=$(getenforce) 140 | 141 | if [[ "$current_status" == "Enforcing" ]]; then 142 | dialog --backtitle "Checking and Enabling SELinux" --title "SELinux Status" --infobox "SELinux is already enabled and enforcing." 6 50 143 | sleep 4 144 | else 145 | dialog --backtitle "Checking and Enabling SELinux" --title "SELinux Disabled" --msgbox "SELinux is not enabled. Enabling SELinux now..." 6 50 146 | sed -i 's/SELINUX=disabled/SELINUX=enforcing/' /etc/selinux/config 147 | setenforce 1 148 | 149 | if [[ "$(getenforce)" == "Enforcing" ]]; then 150 | dialog --backtitle "Checking and Enabling SELinux" --title "SELinux Enabled" --msgbox "SELinux has been successfully enabled and is now enforcing." 6 50 151 | else 152 | dialog --backtitle "Checking and Enabling SELinux" --title "SELinux Error" --msgbox "Failed to enable SELinux. Please check the configuration manually." 6 50 153 | exit 1 154 | fi 155 | fi 156 | } 157 | 158 | # ========= NETWORK DETECTION ========= 159 | detect_active_interface() { 160 | dialog --backtitle "Network Setup" --title "Interface Check" --infobox "Checking active network interface..." 5 50 161 | sleep 3 162 | 163 | # Attempt 1: Use nmcli to find connected Ethernet 164 | INTERFACE=$(nmcli -t -f DEVICE,TYPE,STATE device | grep "ethernet:connected" | cut -d: -f1 | head -n1) 165 | 166 | # Attempt 2: Fallback to any interface with an IP if nmcli fails 167 | if [[ -z "$INTERFACE" ]]; then 168 | INTERFACE=$(ip -o -4 addr show up | grep -v ' lo ' | awk '{print $2}' | head -n1) 169 | fi 170 | 171 | # Get the matching connection profile name 172 | if [[ -n "$INTERFACE" ]]; then 173 | CONNECTION=$(nmcli -t -f NAME,DEVICE connection show | grep ":$INTERFACE" | cut -d: -f1) 174 | fi 175 | 176 | # Log to /tmp in case of failure 177 | echo "DEBUG: INTERFACE=$INTERFACE" >> /tmp/kvm_debug.log 178 | echo "DEBUG: CONNECTION=$CONNECTION" >> /tmp/kvm_debug.log 179 | 180 | if [[ -z "$INTERFACE" || -z "$CONNECTION" ]]; then 181 | dialog --clear --no-ok --backtitle "Network Setup" --title "Interface Error" --aspect 9 --msgbox "No active network interface with IP found. Check /tmp/kvm_debug.log for d 182 | etails." 5 70 183 | exit 1 184 | fi 185 | 186 | export INTERFACE CONNECTION 187 | } 188 | 189 | # ========= STATIC IP CONFIG ========= 190 | prompt_static_ip_if_dhcp() { 191 | IP_METHOD=$(nmcli -g ipv4.method connection show "$CONNECTION" | tr -d '' | xargs) 192 | 193 | if [[ "$IP_METHOD" == "manual" ]]; then 194 | dialog --title "Static IP Detected" --infobox "Interface '$INTERFACE' is already using a static IP" 6 70 195 | sleep 3 196 | return 197 | elif [[ "$IP_METHOD" == "auto" ]]; then 198 | while true; do 199 | while true; do 200 | IPADDR=$(dialog --backtitle "Interface Setup" --title "Static IP Address Required" --inputbox "***DHCP DETECTED on '$INTERFACE'***\n\nEnter static IP in CIDR format (e.g., 192.168.1.100/24):" 8 80 3>&1 1>&2 2>&3) 201 | validate_cidr "$IPADDR" && break || dialog --msgbox "Invalid CIDR format. Try again." 6 40 202 | done 203 | 204 | while true; do 205 | GW=$(dialog --backtitle "Interface Setup" --title "Gateway" --inputbox "Enter default gateway:" 8 60 3>&1 1>&2 2>&3) 206 | validate_ip "$GW" && break || dialog --msgbox "Invalid IP address. Try again." 6 40 207 | done 208 | 209 | while true; do 210 | DNSSERVER=$(dialog --backtitle "Interface Setup" --title "DNS Server" --inputbox "(Enter the Primary AD Server IP address)\nEnter DNS server IP:" 8 60 3>&1 1>&2 2>&3) 211 | validate_ip "$DNSSERVER" && break || dialog --msgbox "Invalid IP address. Try again." 6 40 212 | done 213 | 214 | while true; do 215 | HOSTNAME=$(dialog --backtitle "Interface Setup" --title "FQDN" --inputbox "Enter FQDN For this Server (e.g., host.domain.com):" 8 80 3>&1 1>&2 2>&3) 216 | if validate_fqdn "$HOSTNAME" && check_hostname_in_domain "$HOSTNAME"; then break 217 | else dialog --msgbox "Invalid FQDN or hostname repeated in domain. Try again." 7 60 218 | fi 219 | done 220 | 221 | while true; do 222 | DNSSEARCH=$(dialog --backtitle "Interface Setup" --title "DNS Search" --inputbox "Enter domain search suffix (e.g., localdomain):" 8 60 3>&1 1>&2 2>&3) 223 | [[ -n "$DNSSEARCH" ]] && break || dialog --msgbox "Search domain cannot be blank." 6 40 224 | done 225 | 226 | dialog --backtitle "Interface Setup" --title "Confirm Settings" --yesno "Apply these settings?\n\nInterface: $INTERFACE\nIP: $IPADDR\nGW: $GW\nFQDN: $HOSTNAME\nDNS: $DNSSERVER\nSearch: $DNSSEARCH" 12 60 227 | 228 | if [[ $? -eq 0 ]]; then 229 | nmcli con mod "$CONNECTION" ipv4.address "$IPADDR" 230 | nmcli con mod "$CONNECTION" ipv4.gateway "$GW" 231 | nmcli con mod "$CONNECTION" ipv4.method manual 232 | nmcli con mod "$CONNECTION" ipv4.dns "$DNSSERVER" 233 | nmcli con mod "$CONNECTION" ipv4.dns-search "$DNSSEARCH" 234 | hostnamectl set-hostname "$HOSTNAME" 235 | 236 | 237 | dialog --clear --no-shadow --no-ok --backtitle "REBOOT REQUIRED" --title "Reboot Required" --aspect 9 --msgbox "Network stack set. The System will reboot. Reconnect at: ${IPADDR%%/*}" 5 95 238 | reboot 239 | fi 240 | done 241 | fi 242 | } 243 | # ========= UI SCREENS ========= 244 | show_welcome_screen() { 245 | clear 246 | echo -e "${GREEN} 247 | .*((((((((((((((((* 248 | .(((((((((((((((((((((((((((/ 249 | ,((((((((((((((((((((((((((((((((((. 250 | (((((((((((((((((((((((((((((((((((((((/ 251 | (((((((((((((((((((((((((((((((((((((((((((/ 252 | .((((((((((((((((((((((((((((((((((((((((((((( 253 | ,((((((((((((((((((((((((((((((((((((((((((((((((. 254 | ((((((((((((((((((((((((((((((/ ,((((((((((((((( 255 | /((((((((((((((((((((((((((((. /((((((((((((* 256 | ((((((((((((((((((((((((((/ (((((((((( 257 | (((((((((((((((((((((((( *((((((/ 258 | /((((((((((((((((((((* (((((* 259 | (((((((((((((((((( (((* ,(( 260 | .((((((((((((((. /((((((( 261 | ((((((((((/ (((((((((((((/ 262 | *((((((. /((((((((((((((((((. 263 | *(*) ,(((((((((((((((((((((((, 264 | (((((((((((((((((((((((/ 265 | /((((((((((((((((((((((. 266 | ,((((((((((((((, 267 | ${RESET}" 268 | echo -e " ${GREEN}Rocky Linux${RESET} ${CYAN}RADS SECONDARY/TERTIARY${RESET} ${YELLOW}Builder${RESET}" 269 | 270 | sleep 2 271 | } 272 | 273 | # ========= INTERNET CONNECTIVITY CHECK ========= 274 | check_internet_connectivity() { 275 | dialog --backtitle "Checking Internet Connectivity" --title "Network Test" --infobox "Checking internet connectivity..." 5 50 276 | sleep 2 277 | 278 | local dns_test="FAILED" 279 | local ip_test="FAILED" 280 | 281 | if ping -c 1 -W 2 google.com &>/dev/null; then 282 | dns_test="SUCCESS" 283 | fi 284 | 285 | if ping -c 1 -W 2 8.8.8.8 &>/dev/null; then 286 | ip_test="SUCCESS" 287 | fi 288 | 289 | dialog --backtitle "Checking Internet Connectivity" --title "Connectivity Test Results" --infobox "DNS Resolution: $dns_test 290 | Direct IP (8.8.8.8): $ip_test " 7 50 291 | sleep 4 292 | 293 | if [[ "$dns_test" == "FAILED" || "$ip_test" == "FAILED" ]]; then 294 | dialog --backtitle "Checking Internet Connectivity" --title "Network Warning" --yesno "Internet connectivity issues detected. Do you want to continue?" 7 50 295 | if [[ $? -ne 0 ]]; then 296 | exit 1 297 | fi 298 | fi 299 | } 300 | 301 | # ========= HOSTNAME VALIDATION ========= 302 | validate_and_set_hostname() { 303 | local current_hostname 304 | current_hostname=$(hostname) 305 | 306 | if [[ "$current_hostname" == "localhost.localdomain" ]]; then 307 | while true; do 308 | NEW_HOSTNAME=$(dialog --backtitle "Configure Hostname" --title "Hostname Configuration" --inputbox \ 309 | "Current hostname is '$current_hostname'. Please enter a new FQDN (e.g., server.example.com):" \ 310 | 8 60 3>&1 1>&2 2>&3) 311 | 312 | if validate_fqdn "$NEW_HOSTNAME" && check_hostname_in_domain "$NEW_HOSTNAME"; then 313 | hostnamectl set-hostname "$NEW_HOSTNAME" 314 | dialog --backtitle "Configure Hostname" --title "Hostname Set" --msgbox "Hostname updated to: $NEW_HOSTNAME" 6 50 315 | break 316 | else 317 | dialog --backtitle "Configure Hostname" --title "Invalid Hostname" --msgbox "Invalid hostname. Please try again." 6 50 318 | fi 319 | done 320 | else 321 | # Show a temporary info box with current hostname, no OK button 322 | dialog --backtitle "Configure Hostname" --title "Hostname Check" --infobox \ 323 | "Hostname set to: $current_hostname" 6 60 324 | sleep 3 325 | fi 326 | } 327 | 328 | # ========= SHOW CHECKLIST TO USER ========= 329 | 330 | show_ad_server_checklist() { 331 | dialog --backtitle "Welcome to the RADS Domain/Forest Installer" --title "First AD Server Installation Checklist" --msgbox "\ 332 | ********************************************* 333 | This will Install another AD Server to a pre-existing Forest/Domain 334 | 335 | Checklist: 336 | Before the Installer starts, please make sure you have the following information 337 | 338 | 1. THE FQDN of the Pre-existing DC 339 | 2. An Administrator password that you will use to join the domain 340 | 3. An NTP Subnet for your clients. This server will provide syncronized time 341 | 4. The beginning and ending lease range for DHCP (optional) 342 | 5. The client default gateway IP Address for the DHCP Scope (optional) 343 | 6. A Friendly name as a description to the DHCP scope created (optional 344 | 345 | 346 | *********************************************" 20 100 347 | } 348 | 349 | #===========INSTALL PACKAGE REQUIREMENTS============= 350 | install_requirements() { 351 | local LOG_FILE="/tmp/install_openldap_clients.log" 352 | : > "$LOG_FILE" 353 | 354 | local PACKAGES=("openldap-clients" "expect" "bind-utils") 355 | 356 | dialog --backtitle "Installing Requirement" --title "Installing..." \ 357 | --infobox "Installing required packages...\nPlease wait." 5 50 358 | 359 | { 360 | echo "Starting installation of: ${PACKAGES[*]}" 361 | dnf -y install "${PACKAGES[@]}" &>> "$LOG_FILE" 362 | echo $? > /tmp/ldap_install_status 363 | } & 364 | 365 | for i in {1..10}; do 366 | sleep 0.3 367 | echo $((i * 10)) 368 | done | dialog --backtitle "Installing Requirement" --gauge "Installing required packages..." 6 50 369 | 370 | INSTALL_EXIT=$(cat /tmp/ldap_install_status) 371 | rm -f /tmp/ldap_install_status 372 | 373 | if [[ "$INSTALL_EXIT" -eq 0 ]]; then 374 | dialog --backtitle "Installing Requirement" --infobox "Packages installed successfully." 6 50 375 | return 0 376 | else 377 | dialog --backtitle "Installing Requirement" --title "Error" \ 378 | --msgbox "Failed to install required packages.\nCheck log: $LOG_FILE" 8 60 379 | return 1 380 | fi 381 | } 382 | 383 | 384 | #===========VALIDATE AD SERVER AND EXPORT============= 385 | 386 | validate_ad_server() { 387 | while true; do 388 | ADDC=$(dialog --clear --backtitle "AD Server Validation" --inputbox "Enter the FQDN of the pre-existing AD Server:" 10 60 3>&1 1>&2 2>&3 3>&-) 389 | [ $? -ne 0 ] && clear && return 1 390 | 391 | if [ -z "$ADDC" ]; then 392 | dialog --backtitle "AD Server Validation" --msgbox "The response cannot be blank. Please try again." 6 50 393 | continue 394 | fi 395 | 396 | DNS_RESULT="DNS resolution failed" 397 | PING_RESULT="Ping failed" 398 | LDAP_RESULT="LDAP SRV record not found" 399 | KRB_RESULT="Kerberos SRV record not found" 400 | ALL_OK=true 401 | 402 | IP_ADDRESS=$(dig +short "$ADDC" | head -n 1) 403 | if [ -n "$IP_ADDRESS" ]; then 404 | DNS_RESULT="DNS resolved to $IP_ADDRESS" 405 | else 406 | ALL_OK=false 407 | fi 408 | 409 | if [ -n "$IP_ADDRESS" ] && ping -c 1 "$IP_ADDRESS" &> /dev/null; then 410 | PING_RESULT="Ping successful to $IP_ADDRESS" 411 | else 412 | ALL_OK=false 413 | fi 414 | 415 | DOMAIN="${ADDC#*.}" 416 | 417 | LDAP_SRV=$(host -t SRV _ldap._tcp."${DOMAIN}") 418 | if echo "$LDAP_SRV" | grep -q "$ADDC"; then 419 | LDAP_RESULT="LDAP SRV record found for $ADDC" 420 | else 421 | ALL_OK=false 422 | fi 423 | 424 | KRB_SRV=$(host -t SRV _kerberos._udp."${DOMAIN}") 425 | if echo "$KRB_SRV" | grep -q "$ADDC"; then 426 | KRB_RESULT="Kerberos SRV record found for $ADDC" 427 | else 428 | ALL_OK=false 429 | fi 430 | 431 | # Correct vertical formatting using literal newlines 432 | RESULT_MSG=$(cat <&1 1>&2 2>&3 3>&-) 466 | 467 | [ $? -ne 0 ] && clear && return 1 468 | 469 | if [ -z "$ADMINPASS" ]; then 470 | dialog --backtitle "Validate Administrator Password" \ 471 | --msgbox "Password cannot be blank. Please try again." 6 50 472 | continue 473 | fi 474 | 475 | # Try secure bind using LDAPS with relaxed cert validation 476 | LDAPTLS_REQCERT=never ldapwhoami -x -H "ldaps://$DC_IP_ADDRESS" \ 477 | -D "Administrator@$DOMAIN" -w "$ADMINPASS" >/tmp/ldap_test.out 2>&1 478 | 479 | if [ $? -eq 0 ]; then 480 | dialog --backtitle "Validate Administrator Password" \ 481 | --infobox "Administrator credentials validated successfully." 5 60 482 | sleep 2 483 | export ADMINPASS 484 | clear 485 | return 0 486 | else 487 | ERROR_MSG=$(cat /tmp/ldap_test.out | tail -n 1) 488 | dialog --backtitle "Validate Administrator Password" \ 489 | --msgbox "Authentication failed:\n\n$ERROR_MSG" 10 60 490 | dialog --backtitle "Validate Administrator Password" \ 491 | --yesno "Would you like to try again?" 7 50 492 | [ $? -ne 0 ] && clear && return 1 493 | fi 494 | done 495 | } 496 | #===========CONFIGURE NTP============= 497 | configure_chrony_from_addc() { 498 | local LOG_NTP="/tmp/chrony_ntp_configure.log" 499 | local ALLOW_NET="" 500 | touch "$LOG_NTP" 501 | 502 | log_ntp() { 503 | echo "$(date '+%Y-%m-%d %H:%M:%S') - $*" >> "$LOG_NTP" 504 | } 505 | 506 | validate_cidr() { 507 | [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]] 508 | } 509 | 510 | prompt_allow_networks() { 511 | while true; do 512 | ALLOW_NET=$(dialog --title "Allow NTP Access" \ 513 | --backtitle "Configure NTP" --inputbox "Enter the CIDR range to allow NTP access (e.g., 192.168.1.0/24):" 8 80 \ 514 | 3>&1 1>&2 2>&3 3>&-) 515 | exit_status=$? 516 | if [ $exit_status -ne 0 ]; then 517 | return 1 518 | fi 519 | 520 | if validate_cidr "$ALLOW_NET"; then 521 | return 0 522 | else 523 | dialog --backtitle "Configure NTP" --msgbox "Invalid CIDR format. Please try again." 6 40 524 | fi 525 | done 526 | } 527 | 528 | check_ntp_operational_on_addc() { 529 | dialog --backtitle "Configure NTP" --title "NTP Check" \ 530 | --infobox "Checking NTP availability on $ADDC..." 4 60 531 | sleep 1 532 | 533 | local was_active=0 534 | if systemctl is-active --quiet chronyd; then 535 | systemctl stop chronyd 536 | was_active=1 537 | log_ntp "Stopped chronyd temporarily for NTP test" 538 | fi 539 | 540 | chronyd -q "server $ADDC iburst" &> /tmp/chrony_test_ntp.out 541 | local result=$? 542 | 543 | if (( was_active == 1 )); then 544 | systemctl start chronyd 545 | log_ntp "Restarted chronyd after NTP test" 546 | fi 547 | 548 | if grep -qE "System clock wrong|Leap status" /tmp/chrony_test_ntp.out && [ $result -eq 0 ]; then 549 | log_ntp "ADDC $ADDC is responding to NTP" 550 | return 0 551 | else 552 | dialog --backtitle "Configure NTP" --title "Chrony NTP Check Failed" \ 553 | --yesno "The ADDC ($ADDC) does not appear to be serving NTP.\n\nDo you want to proceed anyway?" 10 60 554 | return $? 555 | fi 556 | } 557 | 558 | update_chrony_config() { 559 | cp /etc/chrony.conf /etc/chrony.conf.bak 560 | sed -i '/^\(server\|pool\|allow\)[[:space:]]/d' /etc/chrony.conf 561 | 562 | echo "server $ADDC iburst" >> /etc/chrony.conf 563 | log_ntp "Set server $ADDC as NTP source in chrony.conf" 564 | 565 | if [[ -n "$ALLOW_NET" ]]; then 566 | echo "allow $ALLOW_NET" >> /etc/chrony.conf 567 | log_ntp "Allowed subnet $ALLOW_NET in chrony.conf" 568 | fi 569 | 570 | systemctl restart chronyd 571 | sleep 2 572 | } 573 | 574 | validate_time_sync() { 575 | local attempt=1 576 | local success=0 577 | 578 | while (( attempt <= 3 )); do 579 | dialog --backtitle "Configure NTP" --title "Chrony NTP Configuration" --infobox "Validating time sync... Attempt $attempt/3" 4 50 580 | sleep 5 581 | 582 | TRACKING=$(chronyc tracking 2>&1) 583 | echo "$TRACKING" >> "$LOG_NTP" 584 | 585 | if echo "$TRACKING" | grep -q "Leap status[[:space:]]*:[[:space:]]*Normal"; then 586 | success=1 587 | break 588 | fi 589 | ((attempt++)) 590 | done 591 | 592 | if [[ "$success" -eq 1 ]]; then 593 | dialog --backtitle "Configure NTP" --title "NTP Configuration" --infobox "Time synchronized successfully:\n\n$TRACKING" 15 100 594 | sleep 3 595 | else 596 | dialog --backtitle "Configure NTP" --title "NTP Configuration" --yesno "Time sync failed after 3 attempts.\nDo you want to proceed anyway?" 8 100 597 | [[ $? -eq 0 ]] || return 1 598 | fi 599 | return 0 600 | } 601 | 602 | # Ensure ADDC is defined 603 | if [[ -z "$ADDC" ]]; then 604 | dialog --backtitle "Configure NTP" --title "NTP Configuration" \ 605 | --msgbox "ADDC variable not set. Please run domain controller validation first." 6 60 606 | return 1 607 | fi 608 | 609 | # Prompt for allowed network 610 | if ! prompt_allow_networks; then 611 | dialog --backtitle "Configure NTP" --title "NTP Configuration" \ 612 | --msgbox "No network was allowed. Configuration cancelled." 6 50 613 | return 1 614 | fi 615 | 616 | # Check that ADDC responds to NTP 617 | if ! check_ntp_operational_on_addc; then 618 | return 1 619 | fi 620 | 621 | update_chrony_config 622 | 623 | if ! validate_time_sync; then 624 | dialog --backtitle "Configure NTP" --title "NTP Configuration" --msgbox "NTP configuration aborted." 6 40 625 | return 1 626 | fi 627 | 628 | dialog --backtitle "Configure NTP" --title "NTP Configuration" \ 629 | --infobox "NTP configuration completed successfully." 4 60 630 | sleep 3 631 | return 0 632 | } 633 | 634 | # ========= SYSTEM UPDATE & PACKAGE INSTALL ========= 635 | update_and_install_packages() { 636 | # Simulate progress while enabling EPEL and CRB 637 | dialog --backtitle "Base Package Update" --title "Repository Setup" --gauge "Enabling EPEL and CRB repositories..." 10 60 0 < <( 638 | ( 639 | ( 640 | dnf install -y epel-release >/dev/null 2>&1 641 | dnf config-manager --set-enabled crb >/dev/null 2>&1 642 | ) & 643 | PID=$! 644 | PROGRESS=0 645 | while kill -0 "$PID" 2>/dev/null; do 646 | echo "$PROGRESS" 647 | echo "XXX" 648 | echo "Enabling EPEL and CRB..." 649 | echo "XXX" 650 | ((PROGRESS += 5)) 651 | if [[ $PROGRESS -ge 95 ]]; then 652 | PROGRESS=5 653 | fi 654 | sleep 0.5 655 | done 656 | echo "100" 657 | echo "XXX" 658 | echo "Repositories enabled." 659 | echo "XXX" 660 | ) 661 | ) 662 | 663 | dialog --backtitle "Base Package Update" --title "System Update" --infobox "Checking for updates. This may take a few moments..." 5 70 664 | sleep 2 665 | 666 | dnf check-update -y &>/dev/null 667 | 668 | TEMP_FILE=$(mktemp) 669 | dnf check-update | awk '{print $1}' | grep -vE '^$|Obsoleting|Last' | awk -F'.' '{print $1}' | sort -u > "$TEMP_FILE" 670 | 671 | PACKAGE_LIST=($(cat "$TEMP_FILE")) 672 | TOTAL_PACKAGES=${#PACKAGE_LIST[@]} 673 | 674 | if [[ "$TOTAL_PACKAGES" -eq 0 ]]; then 675 | dialog --backtitle "Base Package Update" --title "System Update" --msgbox "No updates available!" 6 50 676 | rm -f "$TEMP_FILE" 677 | else 678 | PIPE=$(mktemp -u) 679 | mkfifo "$PIPE" 680 | dialog --backtitle "Base Package Update" --title "System Update" --gauge "Installing updates..." 10 70 0 < "$PIPE" & 681 | exec 3>"$PIPE" 682 | COUNT=0 683 | for PACKAGE in "${PACKAGE_LIST[@]}"; do 684 | ((COUNT++)) 685 | PERCENT=$(( (COUNT * 100) / TOTAL_PACKAGES )) 686 | echo "$PERCENT" > "$PIPE" 687 | echo "XXX" > "$PIPE" 688 | echo "Updating: $PACKAGE" > "$PIPE" 689 | echo "XXX" > "$PIPE" 690 | dnf -y install "$PACKAGE" >/dev/null 2>&1 691 | done 692 | exec 3>&- 693 | rm -f "$PIPE" "$TEMP_FILE" 694 | fi 695 | 696 | dialog --backtitle "Required Package Install" --title "Package Installation" --infobox "Installing Required Packages..." 5 50 697 | sleep 2 698 | PACKAGE_LIST=("ntsysv" "iptraf" "nano" "expect" "rsync" "sshpass" "openldap-clients" "fail2ban" "tuned" "createrepo" "cockpit" "cockpit-storaged" "mock" "cockpit-files" "net-tools" "dmidecode" "ipcalc" "bind-utils" "iotop" "zip" "yum-utils" "nano" "curl" "wget" "git" "dnf-automatic" "dnf-plugins-core" "util-linux" "htop" "iptraf-ng" "mc") 699 | TOTAL_PACKAGES=${#PACKAGE_LIST[@]} 700 | 701 | PIPE=$(mktemp -u) 702 | mkfifo "$PIPE" 703 | dialog --backtitle "Required Package Install" --title "Installing Required Packages" --gauge "Preparing to install packages..." 10 70 0 < "$PIPE" & 704 | exec 3>"$PIPE" 705 | COUNT=0 706 | for PACKAGE in "${PACKAGE_LIST[@]}"; do 707 | ((COUNT++)) 708 | PERCENT=$(( (COUNT * 100) / TOTAL_PACKAGES )) 709 | echo "$PERCENT" > "$PIPE" 710 | echo "XXX" > "$PIPE" 711 | echo "Installing: $PACKAGE" > "$PIPE" 712 | echo "XXX" > "$PIPE" 713 | dnf -y install "$PACKAGE" >/dev/null 2>&1 714 | done 715 | exec 3>&- 716 | rm -f "$PIPE" 717 | dialog --backtitle "Required Package Install" --title "Installation Complete" --infobox "All packages installed successfully!" 6 50 718 | sleep 3 719 | } 720 | #===========DETECT VIRT and INSTALL GUEST============= 721 | # Function to show a dialog infobox 722 | vm_detection() { 723 | show_info() { 724 | dialog --backtitle "Guest VM Detection and Installation" --title "$1" --infobox "$2" 5 60 725 | sleep 2 726 | } 727 | 728 | # Function to show a progress bar during installation 729 | show_progress() { 730 | ( 731 | echo "10"; sleep 1 732 | echo "40"; sleep 1 733 | echo "70"; sleep 1 734 | echo "100" 735 | ) | dialog --backtitle "Guest VM Detection and Installation" --title "$1" --gauge "$2" 7 60 0 736 | } 737 | 738 | # Detect virtualization platform 739 | HWKVM=$(dmidecode | grep -i -e manufacturer -e product -e vendor | grep KVM | cut -c16-) 740 | HWVMWARE=$(dmidecode | grep -i -e manufacturer -e product -e vendor | grep Manufacturer | grep "VMware, Inc." | cut -c16- | cut -d , -f1) 741 | 742 | show_info "Virtualization Check" "Checking for virtualization platform..." 743 | 744 | # Install guest agent for KVM 745 | if [ "$HWKVM" = "KVM" ]; then 746 | show_info "Platform Detected" "KVM platform detected.\nInstalling qemu-guest-agent..." 747 | show_progress "Installing qemu-guest-agent" "Installing guest tools for KVM..." 748 | dnf -y install qemu-guest-agent &>/dev/null 749 | fi 750 | 751 | # Install guest agent for VMware 752 | if [ "$HWVMWARE" = "VMware" ]; then 753 | show_info "Platform Detected" "VMware platform detected.\nInstalling open-vm-tools..." 754 | show_progress "Installing open-vm-tools" "Installing guest tools for VMware..." 755 | dnf -y install open-vm-tools &>/dev/null 756 | fi 757 | } 758 | #===========OPTIONAL DHCP INSTALL============= 759 | configure_dhcp_server() { 760 | local DIALOG="${DIALOG_BIN:-dialog}" 761 | local BACKTITLE="DHCP Server Install" 762 | local CHOSEN_BACKEND="" 763 | 764 | # ────────────────────────────── UI helpers ────────────────────────────── 765 | msgbox() { $DIALOG --backtitle "$BACKTITLE" --title "$1" --msgbox "$2" "${3:-8}" "${4:-72}"; } 766 | infobox(){ $DIALOG --backtitle "$BACKTITLE" --title "$1" --infobox "$2" "${3:-6}" "${4:-60}"; } 767 | 768 | # Require root + Rocky 9+ 769 | require_root(){ [[ $EUID -eq 0 ]] || { echo "Run as root." >&2; return 1; }; } 770 | require_rocky9plus(){ 771 | . /etc/os-release 2>/dev/null || true 772 | if [[ "${ID:-}" != "rocky" ]]; then 773 | msgbox "Unsupported OS" "This installer is limited to Rocky Linux 9+."; return 1 774 | fi 775 | local maj="${VERSION_ID%%.*}" 776 | if [[ -z "$maj" || "$maj" -lt 9 ]]; then 777 | msgbox "Unsupported Version" "Detected Rocky Linux ${VERSION_ID:-unknown}. This script supports Rocky Linux 9+ only." 778 | return 1 779 | fi 780 | return 0 781 | } 782 | 783 | # ─────────────────────── detection of installed backends ───────────────────── 784 | detect_isc_dhcp(){ [[ -f /etc/dhcp/dhcpd.conf ]] || rpm -q dhcp-server >/dev/null 2>&1; } 785 | detect_kea() { [[ -f /etc/kea/kea-dhcp4.conf ]] || rpm -q kea >/dev/null 2>&1; } 786 | 787 | # ───────────────────────── repo enable (Rocky 9+) ─────────────────────────── 788 | enable_repos_with_gauge() { 789 | # Rocky 9+: enable EPEL + CRB, then refresh metadata 790 | local log="/tmp/repo-setup.$(date +%s).log" 791 | local status="/tmp/repo-setup-status.$$" 792 | local msg="/tmp/repo-setup-phase.$$" 793 | : >"$log"; : >"$msg" 794 | trap 'rm -f "$status" "$msg"' RETURN 795 | 796 | ( 797 | rc=0 798 | { 799 | echo "Installing dnf-plugins-core..." >"$msg" 800 | dnf -y install dnf-plugins-core >>"$log" 2>&1 || rc=1 801 | 802 | echo "Installing epel-release..." >"$msg" 803 | dnf -y install epel-release >>"$log" 2>&1 || rc=1 804 | 805 | echo "Enabling CRB repository..." >"$msg" 806 | dnf config-manager --set-enabled crb >>"$log" 2>&1 || rc=1 807 | 808 | echo "Refreshing repository metadata (makecache --refresh)..." >"$msg" 809 | dnf -y makecache --refresh >>"$log" 2>&1 || rc=1 810 | } || rc=1 811 | echo "$rc" >"$status" 812 | ) & 813 | 814 | local pid=$! 815 | ( 816 | local PROGRESS=0 817 | while kill -0 "$pid" 2>/dev/null; do 818 | (( PROGRESS < 95 )) && PROGRESS=$(( PROGRESS + 5 )) 819 | echo "$PROGRESS" 820 | echo "XXX" 821 | echo -e "Enabling EPEL and CRB...\n$(cat "$msg" 2>/dev/null || echo "Working...")\n\nLog: $log" 822 | echo "XXX" 823 | sleep 0.5 824 | done 825 | echo "100" 826 | echo "XXX" 827 | echo -e "Repositories enabled and metadata refreshed.\n\nLog: $log" 828 | echo "XXX" 829 | ) | $DIALOG --backtitle "$BACKTITLE" --title "Repository Setup" --gauge "Preparing..." 10 70 0 830 | 831 | local rc=1 832 | [[ -f "$status" ]] && rc="$(cat "$status" 2>/dev/null || echo 1)" 833 | if [[ "$rc" -ne 0 ]]; then 834 | msgbox "Repository Setup Failed" "There was a problem enabling repositories.\n\nYou'll see the log next." 9 70 835 | $DIALOG --backtitle "$BACKTITLE" --title "Repo Setup Log" --textbox "$log" 22 100 836 | return 1 837 | fi 838 | return 0 839 | } 840 | 841 | # ────────────────────────── generic gauge runner ──────────────────────────── 842 | run_gauge_cmd() { 843 | local title="$1"; shift 844 | local log="/tmp/$(basename "$1")-install.$(date +%s).log" 845 | local status="/tmp/$(basename "$1")-status.$$" 846 | : > "$log" 847 | ( "$@" &> "$log"; echo $? > "$status" ) & local pid=$! 848 | set +e 849 | ( 850 | local pct=0 851 | while kill -0 "$pid" 2>/dev/null; do 852 | echo "$pct" 853 | echo "XXX" 854 | echo -e "Installing... Please wait.\nLog: $log" 855 | echo "XXX" 856 | sleep 0.3 857 | pct=$(( (pct + 2) % 97 )) 858 | done 859 | echo 100; echo "XXX"; echo "Finishing up..."; echo "XXX" 860 | ) | $DIALOG --backtitle "$BACKTITLE" --title "$title" --gauge "Preparing..." 10 70 0 861 | 862 | local rc=1 863 | [[ -f "$status" ]] && { rc="$(cat "$status" 2>/dev/null || echo 1)"; rm -f "$status"; } 864 | if [[ "$rc" -ne 0 ]]; then 865 | msgbox "Error" "$title failed.\n\nSee the next screen for details.\n\nLog: $log" 10 72 866 | $DIALOG --backtitle "$BACKTITLE" --title "Install log: $title" --textbox "$log" 22 100 867 | return "$rc" 868 | else 869 | infobox "Success" "$title completed.\n\nLog: $log" 8 70 870 | sleep 1 871 | fi 872 | } 873 | 874 | # ───────────────────────────── dnf installers ──────────────────────────────── 875 | install_isc_dhcp() { 876 | enable_repos_with_gauge || return 1 877 | run_gauge_cmd "Installing ISC DHCP (dhcp-server)" dnf -y install dhcp-server 878 | } 879 | install_kea() { 880 | enable_repos_with_gauge || return 1 881 | run_gauge_cmd "Installing Kea DHCP (kea)" dnf -y install kea 882 | } 883 | 884 | # ───────────────────── shared IP/CIDR + domain helpers ────────────────────── 885 | is_valid_ip(){ 886 | [[ $1 =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1 887 | local IFS=.; local o; for o in $1; do [[ $o -ge 0 && $o -le 255 ]] || return 1; done 888 | } 889 | ip_to_int(){ local IFS=.; read -r a b c d <<<"$1"; echo $(( (a<<24)+(b<<16)+(c<<8)+d )); } 890 | int_to_ip(){ local i=$1; printf "%d.%d.%d.%d" $(( (i>>24)&255 )) $(( (i>>16)&255 )) $(( (i>>8)&255 )) $(( i&255 )); } 891 | cidr_to_netmask(){ local c=$1; local m=$(( 0xFFFFFFFF << (32-c) & 0xFFFFFFFF )); int_to_ip "$m"; } 892 | netmask_to_cidr(){ 893 | local ip=$1; is_valid_ip "$ip" || { echo -1; return; } 894 | local n=$(ip_to_int "$ip") c=0 saw_zero=0 895 | for ((i=31;i>=0;i--)); do 896 | if (( (n>>i)&1 )); then (( saw_zero )) && { echo -1; return; }; ((c++)) 897 | else saw_zero=1 898 | fi 899 | done 900 | echo "$c" 901 | } 902 | network_from_ip_cidr(){ local ip=$1 c=$2; local m=$(( 0xFFFFFFFF << (32-c) & 0xFFFFFFFF )); int_to_ip $(( $(ip_to_int "$ip") & m )); } 903 | broadcast_from_ip_cidr(){ local ip=$1 c=$2; local m=$(( 0xFFFFFFFF << (32-c) & 0xFFFFFFFF )); int_to_ip $(( $(ip_to_int "$ip") | (~m & 0xFFFFFFFF) )); } 904 | ip_in_cidr(){ 905 | local ip=$1 net=$2 c=$3 906 | local m=$(( 0xFFFFFFFF << (32-c) & 0xFFFFFFFF )) 907 | (( ( $(ip_to_int "$ip") & m ) == ( $(ip_to_int "$net") & m ) )) 908 | } 909 | is_valid_domain(){ 910 | local d="$1" 911 | [[ -n "$d" ]] || return 1 912 | [[ "$d" =~ ^([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$ ]] 913 | } 914 | 915 | # ───────────────────────────── dhcpd setup flow ───────────────────────────── 916 | dhcpd_setup() { 917 | local iface inet4_line INET4 DHCPCIDR NET_DETECTED NETMASK_DETECTED 918 | iface=$(nmcli -t -f DEVICE,STATE device status | awk -F: '$2=="connected"{print $1; exit}') 919 | [[ -z "$iface" ]] && { msgbox "DHCPD Setup" "No active interface found."; return 1; } 920 | inet4_line=$(nmcli -g IP4.ADDRESS device show "$iface" | head -n 1) 921 | [[ -z "$inet4_line" ]] && { msgbox "DHCPD Setup" "No IPv4 address found on $iface."; return 1; } 922 | 923 | INET4=${inet4_line%/*} 924 | DHCPCIDR=${inet4_line#*/} 925 | NET_DETECTED=$(network_from_ip_cidr "$INET4" "$DHCPCIDR") 926 | NETMASK_DETECTED=$(cidr_to_netmask "$DHCPCIDR") 927 | 928 | local DHCPBEGIP DHCPENDIP DHCPNETMASK DHCPDEFGW SUBNETDESC DOM_SUFFIX SEARCH_DOMAIN 929 | local DEF_SUFFIX="$(hostname -d 2>/dev/null || true)" 930 | local DEF_SEARCH="${DEF_SUFFIX}" 931 | 932 | while true; do 933 | # Range start 934 | while true; do 935 | DHCPBEGIP=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 936 | "Enter beginning IP of DHCP lease range (in $NET_DETECTED/$DHCPCIDR):" 8 78) 937 | [[ -n "$DHCPBEGIP" ]] && is_valid_ip "$DHCPBEGIP" && ip_in_cidr "$DHCPBEGIP" "$NET_DETECTED" "$DHCPCIDR" && break 938 | msgbox "Invalid Input" "Start IP must be a valid IPv4 within $NET_DETECTED/$DHCPCIDR." 939 | done 940 | # Range end 941 | while true; do 942 | DHCPENDIP=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 943 | "Enter ending IP of DHCP lease range (in $NET_DETECTED/$DHCPCIDR):" 8 78) 944 | [[ -n "$DHCPENDIP" ]] && is_valid_ip "$DHCPENDIP" && ip_in_cidr "$DHCPENDIP" "$NET_DETECTED" "$DHCPCIDR" && \ 945 | (( $(ip_to_int "$DHCPBEGIP") <= $(ip_to_int "$DHCPENDIP") )) && break 946 | msgbox "Invalid Input" "End IP must be valid, in $NET_DETECTED/$DHCPCIDR, and ≥ start IP." 947 | done 948 | # Netmask (must match detected) 949 | while true; do 950 | DHCPNETMASK=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 951 | "Enter netmask for clients (must match detected $NETMASK_DETECTED):" 8 78 "$NETMASK_DETECTED") 952 | local nm_cidr; nm_cidr=$(netmask_to_cidr "$DHCPNETMASK") 953 | [[ "$nm_cidr" -eq "$DHCPCIDR" ]] && break 954 | msgbox "Invalid Netmask" "Netmask must be contiguous and equal to $NETMASK_DETECTED." 955 | done 956 | # Default gateway 957 | while true; do 958 | DHCPDEFGW=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 959 | "Enter default gateway for clients (in $NET_DETECTED/$DHCPCIDR):" 8 78) 960 | [[ -n "$DHCPDEFGW" ]] && is_valid_ip "$DHCPDEFGW" && ip_in_cidr "$DHCPDEFGW" "$NET_DETECTED" "$DHCPCIDR" && break 961 | msgbox "Invalid Gateway" "Gateway must be a valid IPv4 within $NET_DETECTED/$DHCPCIDR." 962 | done 963 | # Domain suffix (option 15) 964 | while true; do 965 | DOM_SUFFIX=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 966 | "Enter domain suffix (for 'option domain-name'):" 8 78 "${DEF_SUFFIX}") 967 | is_valid_domain "$DOM_SUFFIX" && break 968 | msgbox "Invalid Domain" "Please enter a valid domain suffix like 'ad.example.com'." 969 | done 970 | # Search domain(s) (option 119) 971 | while true; do 972 | SEARCH_DOMAIN=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 973 | "Enter search domain(s) for clients (comma-separated if multiple):" 9 78 "${DEF_SEARCH}") 974 | local ok=1 IFS=, item 975 | for item in $SEARCH_DOMAIN; do 976 | item="${item// /}" ; is_valid_domain "$item" || { ok=0; break; } 977 | done 978 | [[ $ok -eq 1 ]] && break 979 | msgbox "Invalid Search Domain" "One or more domains are invalid. Use comma-separated FQDNs." 980 | done 981 | 982 | SUBNETDESC=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 983 | "Enter a friendly name/description for this subnet:" 8 78) 984 | 985 | $DIALOG --backtitle "$BACKTITLE" --title "DHCP Configuration Summary" --yesno \ 986 | "Interface: $iface 987 | Interface IP: $INET4/$DHCPCIDR 988 | Subnet: $NET_DETECTED 989 | Netmask: $DHCPNETMASK 990 | Range: $DHCPBEGIP → $DHCPENDIP 991 | Gateway: $DHCPDEFGW 992 | Domain: $DOM_SUFFIX 993 | Search: $SEARCH_DOMAIN 994 | Description: $SUBNETDESC 995 | 996 | Are these settings correct?" 18 72 && break 997 | done 998 | 999 | infobox "DHCPD Setup" "Creating /etc/dhcp/dhcpd.conf..." 1000 | mkdir -p /etc/dhcp 1001 | mv /etc/dhcp/dhcpd.conf /etc/dhcp/dhcpd.conf.orig 2>/dev/null || true 1002 | cat </etc/dhcp/dhcpd.conf 1003 | authoritative; 1004 | allow unknown-clients; 1005 | default-lease-time 600; 1006 | max-lease-time 7200; 1007 | 1008 | option ntp-servers ${INET4}; 1009 | option time-servers ${INET4}; 1010 | option domain-name-servers ${INET4}; 1011 | option domain-name "${DOM_SUFFIX}"; 1012 | option domain-search "${SEARCH_DOMAIN}"; 1013 | 1014 | # ${SUBNETDESC} 1015 | subnet ${NET_DETECTED} netmask ${DHCPNETMASK} { 1016 | range ${DHCPBEGIP} ${DHCPENDIP}; 1017 | option subnet-mask ${DHCPNETMASK}; 1018 | option routers ${DHCPDEFGW}; 1019 | } 1020 | EOF 1021 | } 1022 | 1023 | # ───────────────────────────── Kea setup flow ─────────────────────────────── 1024 | kea_dhcp_setup() { 1025 | local KEA_CONF="/etc/kea/kea-dhcp4.conf" 1026 | mkdir -p /etc/kea; touch "$KEA_CONF" 1027 | 1028 | local iface inet4_line INET4 CIDR NETMASK NETWORK BROADCAST 1029 | iface=$(nmcli -t -f DEVICE,STATE device status | awk -F: '$2=="connected"{print $1; exit}') 1030 | [[ -z "$iface" ]] && { msgbox "KEA DHCP Setup" "No active interface found."; return 1; } 1031 | inet4_line=$(nmcli -g IP4.ADDRESS device show "$iface" | head -n 1) 1032 | [[ -z "$inet4_line" ]] && { msgbox "KEA DHCP Setup" "No IPv4 address found on $iface."; return 1; } 1033 | 1034 | INET4=${inet4_line%/*} 1035 | CIDR=${inet4_line#*/} 1036 | NETWORK=$(network_from_ip_cidr "$INET4" "$CIDR") 1037 | NETMASK=$(cidr_to_netmask "$CIDR") 1038 | BROADCAST=$(broadcast_from_ip_cidr "$INET4" "$CIDR") 1039 | 1040 | local POOL_START POOL_END ROUTER DOM_SUFFIX SEARCH_DOMAIN DNS_SERVERS SUBNET_DESC 1041 | local DEF_SUFFIX="$(hostname -d 2>/dev/null || true)" 1042 | local DEF_SEARCH="${DEF_SUFFIX}" 1043 | 1044 | while true; do 1045 | # pool start 1046 | while true; do 1047 | POOL_START=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 1048 | "Enter beginning IP of DHCP lease range (in $NETWORK/$CIDR):" 8 78) 1049 | [[ -n "$POOL_START" ]] && is_valid_ip "$POOL_START" && ip_in_cidr "$POOL_START" "$NETWORK" "$CIDR" && break 1050 | msgbox "Invalid Input" "Start IP must be a valid IPv4 within $NETWORK/$CIDR." 1051 | done 1052 | # pool end 1053 | while true; do 1054 | POOL_END=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 1055 | "Enter ending IP of DHCP lease range:" 8 78) 1056 | [[ -n "$POOL_END" ]] && is_valid_ip "$POOL_END" && ip_in_cidr "$POOL_END" "$NETWORK" "$CIDR" && \ 1057 | (( $(ip_to_int "$POOL_START") <= $(ip_to_int "$POOL_END") )) && break 1058 | msgbox "Invalid Input" "End IP must be valid, in $NETWORK/$CIDR, and ≥ start IP." 1059 | done 1060 | # gateway 1061 | while true; do 1062 | ROUTER=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 1063 | "Enter default gateway for clients (in $NETWORK/$CIDR):" 8 78) 1064 | [[ -n "$ROUTER" ]] && is_valid_ip "$ROUTER" && ip_in_cidr "$ROUTER" "$NETWORK" "$CIDR" && break 1065 | msgbox "Invalid Gateway" "Gateway must be a valid IPv4 within $NETWORK/$CIDR." 1066 | done 1067 | # domains 1068 | while true; do 1069 | DOM_SUFFIX=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 1070 | "Enter domain suffix (for 'domain-name'):" 8 78 "${DEF_SUFFIX}") 1071 | is_valid_domain "$DOM_SUFFIX" && break 1072 | msgbox "Invalid Domain" "Please enter a valid domain suffix like 'ad.example.com'." 1073 | done 1074 | while true; do 1075 | SEARCH_DOMAIN=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 1076 | "Enter search domain(s) for clients (comma-separated if multiple):" 9 78 "${DEF_SEARCH}") 1077 | local ok=1 IFS=, item 1078 | for item in $SEARCH_DOMAIN; do 1079 | item="${item// /}" ; is_valid_domain "$item" || { ok=0; break; } 1080 | done 1081 | [[ $ok -eq 1 ]] && break 1082 | msgbox "Invalid Search Domain" "One or more domains are invalid. Use comma-separated FQDNs." 1083 | done 1084 | DNS_SERVERS=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 1085 | "Enter DNS servers (comma separated, or leave blank to use $INET4):" 8 78 "$INET4") 1086 | SUBNET_DESC=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 1087 | "Enter a friendly name/description for this subnet:" 8 78) 1088 | 1089 | $DIALOG --backtitle "$BACKTITLE" --title "KEA DHCP Settings Review" --yesno \ 1090 | "Interface: $iface 1091 | Interface IP: $INET4/$CIDR 1092 | Subnet: $NETWORK/$CIDR 1093 | Broadcast: $BROADCAST 1094 | Range: $POOL_START → $POOL_END 1095 | Gateway: $ROUTER 1096 | DNS: $DNS_SERVERS 1097 | Domain: $DOM_SUFFIX 1098 | Search: $SEARCH_DOMAIN 1099 | Description: $SUBNET_DESC 1100 | 1101 | Are these settings correct?" 20 72 && break 1102 | done 1103 | 1104 | infobox "KEA DHCP Setup" "Creating /etc/kea/kea-dhcp4.conf..." 1105 | cat < "$KEA_CONF" 1106 | { 1107 | "Dhcp4": { 1108 | "interfaces-config": { 1109 | "interfaces": [ "$iface" ] 1110 | }, 1111 | "lease-database": { 1112 | "type": "memfile", 1113 | "persist": true, 1114 | "name": "/var/lib/kea/kea-leases4.csv" 1115 | }, 1116 | "subnet4": [ 1117 | { 1118 | "id": 1, 1119 | "subnet": "$NETWORK/$CIDR", 1120 | "interface": "$iface", 1121 | "comment": "$SUBNET_DESC", 1122 | "pools": [ { "pool": "$POOL_START - $POOL_END" } ], 1123 | "option-data": [ 1124 | { "name": "routers", "data": "$ROUTER" }, 1125 | { "name": "domain-name-servers", "data": "$DNS_SERVERS" }, 1126 | { "name": "ntp-servers", "data": "$DNS_SERVERS" }, 1127 | { "name": "domain-name", "data": "$DOM_SUFFIX" }, 1128 | { "name": "domain-search", "data": "$SEARCH_DOMAIN" } 1129 | ] 1130 | } 1131 | ], 1132 | "authoritative": true 1133 | } 1134 | } 1135 | EOF 1136 | chown root:kea "$KEA_CONF" 1137 | chmod 640 "$KEA_CONF" 1138 | restorecon "$KEA_CONF" 2>/dev/null || true 1139 | } 1140 | 1141 | # ────────────────────────────── preflight checks ───────────────────────────── 1142 | require_root || return 1 1143 | require_rocky9plus || return 1 1144 | command -v "$DIALOG" >/dev/null 2>&1 || { echo "dialog not found. dnf -y install dialog" >&2; return 1; } 1145 | command -v nmcli >/dev/null 2>&1 || { echo "nmcli not found. dnf -y install NetworkManager" >&2; return 1; } 1146 | 1147 | # ────────────────────────────── user selection ─────────────────────────────── 1148 | $DIALOG --backtitle "$BACKTITLE" --title "DHCP Installation" --yesno \ 1149 | "Would you like to install a DHCP service on this system? 1150 | 1151 | You will be able to choose between ISC DHCP or Kea DHCP in the next step." 9 80 || { clear; return 0; } 1152 | 1153 | local isc_installed="not installed" kea_installed="not installed" 1154 | detect_isc_dhcp && isc_installed="installed" 1155 | detect_kea && kea_installed="installed" 1156 | 1157 | local default="kea" 1158 | detect_kea && default="kea" 1159 | { detect_isc_dhcp && ! detect_kea; } && default="isc" 1160 | 1161 | local kea_desc="Install/upgrade Kea DHCP (recommended)" 1162 | [[ $kea_installed == "installed" ]] && kea_desc+=" [installed]" 1163 | local isc_desc="Install/upgrade ISC DHCP (dhcp-server)" 1164 | [[ $isc_installed == "installed" ]] && isc_desc+=" [installed]" 1165 | 1166 | local KEA_ON="OFF" ISC_ON="OFF" 1167 | [[ $default == "kea" ]] && KEA_ON="ON" || ISC_ON="ON" 1168 | 1169 | local choice 1170 | choice=$($DIALOG --backtitle "$BACKTITLE" --stdout --title "DHCP Installer" --radiolist \ 1171 | "Select which DHCP server to install or upgrade. 1172 | 1173 | Detected: 1174 | - ISC DHCP: $isc_installed 1175 | - Kea DHCP: $kea_installed" \ 1176 | 14 76 2 \ 1177 | kea "$kea_desc" $KEA_ON \ 1178 | isc "$isc_desc" $ISC_ON) 1179 | 1180 | case "${choice:-}" in 1181 | kea) install_kea && CHOSEN_BACKEND="kea" ;; 1182 | isc) install_isc_dhcp && CHOSEN_BACKEND="isc" ;; 1183 | *) clear; return 0 ;; 1184 | esac 1185 | 1186 | # ────────────────── run setup, enable service, open firewall ──────────────── 1187 | local CONF SVC 1188 | if [[ "$CHOSEN_BACKEND" == "kea" ]]; then 1189 | kea_dhcp_setup 1190 | systemctl enable --now kea-dhcp4 >/dev/null 2>&1 || true 1191 | CONF="/etc/kea/kea-dhcp4.conf"; SVC="kea-dhcp4" 1192 | else 1193 | dhcpd_setup 1194 | systemctl enable --now dhcpd >/dev/null 2>&1 || true 1195 | CONF="/etc/dhcp/dhcpd.conf"; SVC="dhcpd" 1196 | fi 1197 | 1198 | firewall-cmd --zone=public --add-service=dhcp --permanent >/dev/null 2>&1 || true 1199 | firewall-cmd --reload >/dev/null 2>&1 || true 1200 | 1201 | # ────────────────────────────── final validation ───────────────────────────── 1202 | local ok_conf=0 ok_svc=0 1203 | [[ -s "$CONF" ]] && ok_conf=1 1204 | if systemctl is-active --quiet "$SVC"; then ok_svc=1; fi 1205 | 1206 | if [[ $ok_conf -eq 1 && $ok_svc -eq 1 ]]; then 1207 | msgbox "Success" "$SVC is running and $CONF configured successfully." 1208 | clear; return 0 1209 | fi 1210 | 1211 | # Syntax hint on failure 1212 | local syntax="" 1213 | if [[ "$SVC" == "kea-dhcp4" && -f "$CONF" ]]; then 1214 | syntax="$(kea-dhcp4 -t "$CONF" 2>&1 || true)" 1215 | elif [[ "$SVC" == "dhcpd" && -f "$CONF" ]]; then 1216 | syntax="$(dhcpd -t -cf "$CONF" 2>&1 || true)" 1217 | fi 1218 | 1219 | local err="Validation failed: 1220 | - Config file present: $( [[ $ok_conf -eq 1 ]] && echo YES || echo NO ) 1221 | - Service active: $( [[ $ok_svc -eq 1 ]] && echo YES || echo NO ) 1222 | 1223 | $( [[ -n "$syntax" ]] && echo -e "Syntax check output:\n\n$syntax" || echo "No syntax details available.")" 1224 | 1225 | msgbox "DHCP Validation" "$err" 18 90 1226 | clear 1227 | return 1 1228 | } 1229 | #===========SET SELINUX============= 1230 | configure_selinux() { 1231 | dialog --backtitle "SELinux setsbool Configuration" --title "SELinux Configuration" --infobox "Applying SELinux settings for Samba..." 5 50 1232 | sleep 2 1233 | setsebool -P samba_create_home_dirs=on \ 1234 | samba_domain_controller=on \ 1235 | samba_enable_home_dirs=on \ 1236 | samba_portmapper=on \ 1237 | use_samba_home_dirs=on 1238 | sleep2 1239 | } 1240 | #===========CONFGIURE FIREWALL============= 1241 | configure_firewall() { 1242 | dialog --backtitle "Firewall Services Configuration" --title "Firewall Configuration" --infobox "Applying firewall rules for AD services..." 5 60 1243 | firewall-cmd --permanent --add-service=samba-dc >/dev/null 1244 | firewall-cmd --permanent --add-service=ldaps >/dev/null 1245 | firewall-cmd --permanent --add-service=ntp >/dev/null 1246 | firewall-cmd --reload >/dev/null 1247 | systemctl restart firewalld 1248 | sleep 2 1249 | # Extract enabled services 1250 | FIREWALL_SERVICES=$(firewall-cmd --list-services 2>/dev/null) 1251 | 1252 | dialog --backtitle "Firewall Services Configuration" --title "Firewall Status" --infobox "These services are now open on the server:\n\n$FIREWALL_SERVICES\n\n" 12 60 1253 | sleep 4 1254 | } 1255 | #===========PROVISION SAMBA WITH MOCK============= 1256 | configure_samba_provisioning() { 1257 | OSVER=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release) 1258 | MAJOROS=$(cut -d. -f1 <<< "$OSVER") 1259 | MINOROS=$(cut -d. -f2 <<< "$OSVER") 1260 | 1261 | DOMAIN=$(hostname | sed 's/^[^.:]*[.:]//' | sed -e 's/\(.*\)/\U\1/') 1262 | ADDOMAIN=$(hostname | sed 's/^[^.:]*[.:]//' | cut -d. -f1 | sed -e 's/\(.*\)/\U\1/') 1263 | 1264 | dialog --backtitle "Samba Build --dc with Mock" --title "Samba Source Build" --infobox \ 1265 | "Downloading and compiling Samba from source using 'mock'\n\nThis may take up to 30 minutes\n\nThe Installer will Continue Shortly " 10 80 1266 | sleep 4 1267 | 1268 | dnf download samba --source 1269 | if ! ls /root/samba-*.rpm 1>/dev/null 2>&1; then 1270 | dialog --backtitle "Samba Build --dc with Mock" --msgbox "Samba source RPM failed to download. Check your network." 8 50 1271 | return 1 1272 | fi 1273 | 1274 | MOCKSMBVER=$(dnf provides samba | grep samba | sed '2,4d' | cut -d: -f1 | cut -dx -f1) 1275 | MOCKCMD="mock -r rocky-${MAJOROS}-x86_64 --enablerepo=devel --define 'dist .el${MAJOROS}_${MINOROS}.dc' --with dc ${MOCKSMBVER}src.rpm" 1276 | 1277 | TMPLOG=$(mktemp) 1278 | PIPE=$(mktemp -u) 1279 | mkfifo "$PIPE" 1280 | 1281 | # Launch mock inside a pseudo-terminal using `script` 1282 | script -q -c "$MOCKCMD" /dev/null > "$PIPE" 2>&1 & 1283 | MOCKPID=$! 1284 | 1285 | dialog --backtitle "Samba Build --dc with Mock" --title "Building Samba with Mock (Live)" --programbox 25 150 < "$PIPE" 1286 | 1287 | wait $MOCKPID 1288 | rm -f "$PIPE" 1289 | 1290 | if ! ls /var/lib/mock/rocky-${MAJOROS}-x86_64/result/*.rpm &>/dev/null; then 1291 | dialog --backtitle "Samba Build --dc with Mock" --title "Mock Build Failed" --msgbox "Build failed. Check logs manually." 8 60 1292 | return 1 1293 | fi 1294 | 1295 | mkdir -p /root/.samba 1296 | cp /var/lib/mock/rocky-${MAJOROS}-x86_64/result/*.rpm /root/.samba 1297 | createrepo /root/.samba 1298 | dnf config-manager --add-repo /root/.samba 1299 | dnf -y install --nogpgcheck samba-dc samba-client krb5-workstation samba \ 1300 | --repofrompath=samba,/root/.samba \ 1301 | --enablerepo=samba >/dev/null 1302 | 1303 | mv -f /etc/samba/smb.conf /etc/samba/smb.bak.orig 1304 | 1305 | # Attempt domain join 1306 | output=$(samba-tool domain join "$DOMAIN" DC -U "administrator%$ADMINPASS" 2>&1) 1307 | 1308 | # Bail early if join failed 1309 | if echo "$output" | grep -q "ERROR"; then 1310 | dialog --backtitle "Samba Build --DC Join" --msgbox "Domain join failed. Check output:\n\n$output" 10 70 1311 | return 1 1312 | fi 1313 | 1314 | # Confirm smb.conf exists before continuing 1315 | if [[ -f /etc/samba/smb.conf ]]; then 1316 | # Set DNS resolver 1317 | # Get the primary IP address (first non-loopback IP) 1318 | IP=$(hostname -I | awk '{print $1}') 1319 | nmcli con mod "$INTERFACE" ipv4.dns "$IP" 1320 | systemctl restart NetworkManager 1321 | 1322 | # Configure KDC 1323 | \cp -f /var/lib/samba/private/krb5.conf /etc/krb5.conf 1324 | 1325 | # Modify smb.conf 1326 | sed -i '7i \ \ #Added for FreeRADIUS Support' /etc/samba/smb.conf 1327 | sed -i '8i \ \ ntlm auth = mschapv2-and-ntlmv2-only' /etc/samba/smb.conf 1328 | sed -i '9i \ \#ldap server require strong auth = no #UNCOMMENT THIS IF YOU NEED PLAIN LDAP BIND (non-TLS)' /etc/samba/smb.conf 1329 | sed -i '10i \ \dns forwarder = 208.67.222.222' /etc/samba/smb.conf 1330 | 1331 | # Start Samba 1332 | systemctl enable samba --now 1333 | else 1334 | dialog --backtitle "Samba Build --DC Join" --msgbox "Domain join reported success, but smb.conf is missing. Aborting." 8 60 1335 | return 1 1336 | fi 1337 | 1338 | # Verify join 1339 | if ! verify_samba_join; then 1340 | dialog --backtitle "Samba Build --DC Join" --msgbox "Join completed, but verification failed." 7 60 1341 | return 1 1342 | fi 1343 | 1344 | # Success 1345 | dialog --backtitle "Samba Build --DC Join" --msgbox "Successfully joined as additional DC." 7 60 1346 | return 0 1347 | } 1348 | 1349 | 1350 | verify_samba_join() { 1351 | local ERRORS=() 1352 | local LOG="/tmp/samba_join_verify.log" 1353 | : > "$LOG" 1354 | 1355 | # --- Delay before verification --- 1356 | local seconds=10 1357 | for ((i=seconds; i>=0; i--)); do 1358 | dialog --backtitle "Samba Join Verification" --title "Waiting for Domain Join to Settle" \ 1359 | --infobox "Allowing time for replication and service initialization...\n\nStarting verification in ${i} seconds." 8 80 1360 | sleep 1 1361 | done 1362 | 1363 | echo "Verifying Samba domain join status..." | tee -a "$LOG" 1364 | 1365 | # 1. Check smb.conf exists and is valid 1366 | if [[ ! -f /etc/samba/smb.conf ]]; then 1367 | ERRORS+=("Missing /etc/samba/smb.conf") 1368 | else 1369 | if ! grep -q "server role[[:space:]]*=[[:space:]]*active directory domain controller" /etc/samba/smb.conf; then 1370 | ERRORS+=("smb.conf exists but does not show 'active directory domain controller'") 1371 | fi 1372 | fi 1373 | 1374 | # 2. Check Samba AD database files 1375 | for file in secrets.ldb sam.ldb krb5.conf; do 1376 | if [[ ! -f /var/lib/samba/private/$file ]]; then 1377 | ERRORS+=("Missing $file in /var/lib/samba/private/") 1378 | fi 1379 | done 1380 | 1381 | # 3. Check DRS replication info 1382 | DRS_OUTPUT=$(samba-tool drs showrepl 2>&1) 1383 | echo "$DRS_OUTPUT" >> "$LOG" 1384 | 1385 | HOSTNAME_UPPER=$(hostname -s | tr '[:lower:]' '[:upper:]') 1386 | 1387 | if ! echo "$DRS_OUTPUT" | grep -q "Default-First-Site-Name\\\\${HOSTNAME_UPPER}"; then 1388 | ERRORS+=("drs showrepl output missing expected hostname: $HOSTNAME_UPPER") 1389 | fi 1390 | 1391 | if ! echo "$DRS_OUTPUT" | grep -q "Last success"; then 1392 | ERRORS+=("No successful inbound replication events detected") 1393 | fi 1394 | 1395 | if echo "$DRS_OUTPUT" | grep -i "consecutive failure(s): [^0]" | grep -vq "0 consecutive failure(s)"; then 1396 | ERRORS+=("There are replication failures in the DRS output") 1397 | fi 1398 | 1399 | # 4. Check for local DC presence in computer list 1400 | DC_COMPUTER_NAME="${HOSTNAME_UPPER}\$" 1401 | if ! samba-tool computer list | grep -q "^${DC_COMPUTER_NAME}$"; then 1402 | ERRORS+=("This DC (${DC_COMPUTER_NAME}) not found in samba-tool computer list") 1403 | fi 1404 | 1405 | # 5. Check Samba service 1406 | if ! systemctl is-active --quiet samba; then 1407 | ERRORS+=("Samba service is not active") 1408 | fi 1409 | 1410 | # 6. Optional: LDAP bind check (non-fatal) 1411 | if ! LDAPTLS_REQCERT=never ldapwhoami -x -H "ldap://localhost" \ 1412 | -D "Administrator@$DOMAIN" -w "$ADMINPASS" &>/dev/null; then 1413 | echo "⚠️ Warning: LDAP bind failed. Samba may not be fully ready." >> "$LOG" 1414 | fi 1415 | 1416 | # Final decision 1417 | if (( ${#ERRORS[@]} > 0 )); then 1418 | printf "%s\n" "${ERRORS[@]}" >> "$LOG" 1419 | dialog --backtitle "Samba Join Verification" --title "Verification Failed" \ 1420 | --msgbox "Samba does not appear to be fully joined as a DC.\n\nSee log: $LOG\n\nFirst error: ${ERRORS[0]}" 12 70 1421 | return 1 1422 | else 1423 | dialog --backtitle "Samba Join Verification" --msgbox "Samba AD join verified successfully.\nThis server is an active DC." 7 60 1424 | return 0 1425 | fi 1426 | } 1427 | #===========ADD DNF-SMB-MON CRON JOB============= 1428 | add_dnf_smb_mon_cron() { 1429 | MONITOR_SCRIPT="/root/ADDCInstaller/dnf-smb-mon" 1430 | DEST_BIN="/usr/bin/dnf-smb-mon" 1431 | 1432 | if [[ ! -f "$MONITOR_SCRIPT" ]]; then 1433 | dialog --backtitle "Configuring Repository Monitoring" --title "Cron Job Error" --msgbox "$MONITOR_SCRIPT not found. Cannot configure cron job." 7 80 1434 | exit 1 1435 | fi 1436 | 1437 | dialog --backtitle "Configuring Repository Monitoring" --title "Configuring Repo Monitor" --infobox "Installing dnf-smb-mon and setting up cron job..." 5 80 1438 | sleep 2 1439 | 1440 | touch /var/log/dnf-smb-mon.log 1441 | chmod 700 "$MONITOR_SCRIPT" 1442 | \cp "$MONITOR_SCRIPT" "$DEST_BIN" 1443 | 1444 | ( 1445 | crontab -l 2>/dev/null 1446 | echo "0 */6 * * * $DEST_BIN" 1447 | ) | sort -u | crontab - 1448 | 1449 | systemctl restart crond 1450 | dialog --backtitle "Configuring Repository Monitoring" --infobox "dnf-smb-mon installed and cron job scheduled every 6 hours." 6 70 1451 | sleep 2 1452 | } 1453 | #===========COPY SAMBA-DNF-PKG-UPDATE============= 1454 | copy_samba_dnf_pkg_update() { 1455 | UPDATE_SCRIPT="/root/ADDCInstaller/samba-dnf-pkg-update" 1456 | DEST_BIN="/usr/bin/samba-dnf-pkg-update" 1457 | 1458 | if [[ ! -f "$UPDATE_SCRIPT" ]]; then 1459 | dialog --backtitle "Configuring Samba DNF Package Updater" --title "Copy Error" --msgbox "$UPDATE_SCRIPT not found. Cannot continue." 7 60 1460 | exit 1 1461 | fi 1462 | 1463 | dialog --backtitle "Configuring Samba DNF Package Updater" --title "Samba DNF Update" --infobox "Installing samba-dnf-pkg-update script..." 5 60 1464 | sleep 2 1465 | 1466 | chmod 700 "$UPDATE_SCRIPT" 1467 | \cp "$UPDATE_SCRIPT" "$DEST_BIN" 1468 | 1469 | dialog --backtitle "Configuring Samba DNF Package Updater" --infobox "samba-dnf-pkg-update successfully installed to /usr/bin." 6 60 1470 | sleep 2 1471 | } 1472 | #===========UPDATE ISSUE FILE============ 1473 | update_issue_file() { 1474 | rm -rf /etc/issue 1475 | touch /etc/issue 1476 | cat </etc/issue 1477 | \S 1478 | Kernel \r on an \m 1479 | Hostname: \n 1480 | IP Address: \4 1481 | EOF 1482 | } 1483 | #===========SAMBA LDAPS CERT SETUP============= 1484 | setup_samba_ldaps_cert() { 1485 | TLS_DIR="/var/lib/samba/private/tls" 1486 | CERT="$TLS_DIR/samba.crt" 1487 | KEY="$TLS_DIR/samba.key" 1488 | CA="$TLS_DIR/ca.crt" 1489 | SMB_CONF="/etc/samba/smb.conf" 1490 | LOG="/var/log/samba-ldap-cert-setup.log" 1491 | 1492 | FQDN=$(hostname -f) 1493 | IPADDR=$(hostname -I | awk '{print $1}') 1494 | 1495 | mkdir -p "$TLS_DIR" 1496 | 1497 | dialog --backtitle "Configuring TLS" --title "Samba TLS Setup" --infobox "Generating Samba LDAPS certificate for $FQDN with IP $IPADDR..." 6 100 1498 | sleep 2 1499 | 1500 | SAN_CONF=$(mktemp) 1501 | cat > "$SAN_CONF" <> "$LOG" 2>&1 1525 | rm -f "$SAN_CONF" 1526 | 1527 | if [[ -f "$CERT" && -f "$KEY" ]]; then 1528 | cp "$CERT" "$CA" 1529 | chmod 600 "$CERT" "$CA" "$KEY" 1530 | dialog --backtitle "Configuring TLS" --infobox "Certificate and key successfully created at $TLS_DIR" 6 60 1531 | sleep 2 1532 | else 1533 | dialog --backtitle "Configuring TLS" --title "Certificate Error" --msgbox "Certificate or key was not created. Check $LOG for errors." 7 60 1534 | return 1 1535 | fi 1536 | 1537 | if ! grep -q "tls keyfile" "$SMB_CONF"; then 1538 | dialog --backtitle "Configuring TLS" --title "Updating smb.conf" --infobox "Inserting TLS configuration into smb.conf..." 6 60 1539 | sleep 2 1540 | 1541 | awk -v keyfile="$KEY" -v certfile="$CERT" -v cafile="$CA" ' 1542 | BEGIN { inserted=0 } 1543 | /^\[global\]/ { print; in_global=1; next } 1544 | in_global && /^\[/ { 1545 | if (!inserted) { 1546 | print " # TLS configuration for LDAPS/StartTLS" 1547 | print " tls enabled = yes" 1548 | print " tls keyfile = " keyfile 1549 | print " tls certfile = " certfile 1550 | print " tls cafile = " cafile 1551 | print " ldap server require strong auth = yes" 1552 | inserted = 1 1553 | } 1554 | in_global=0 1555 | } 1556 | { print } 1557 | END { 1558 | if (!inserted) { 1559 | print "[global]" 1560 | print " tls enabled = yes" 1561 | print " tls keyfile = " keyfile 1562 | print " tls certfile = " certfile 1563 | print " tls cafile = " cafile 1564 | print " ldap server require strong auth = yes" 1565 | } 1566 | } 1567 | ' "$SMB_CONF" > "$SMB_CONF.new" && mv "$SMB_CONF.new" "$SMB_CONF" 1568 | fi 1569 | 1570 | dialog --backtitle "Configuring TLS" --title "Restarting Samba" --infobox "Restarting Samba to apply certificate configuration..." 6 60 1571 | sleep 2 1572 | systemctl restart samba 1573 | 1574 | # Validate that samba restarted successfully 1575 | if systemctl is-active --quiet samba; then 1576 | dialog --backtitle "Configuring TLS" --infobox "Samba restarted and is running." 6 50 1577 | sleep 2 1578 | else 1579 | dialog --backtitle "Configuring TLS" --title "Samba Error" --msgbox "Samba failed to restart. Please check the service status manually." 7 60 1580 | return 1 1581 | fi 1582 | } 1583 | #===========LDAP BIND AND TEST============= 1584 | test_ldap_secure_connection() { 1585 | LOG="/var/log/samba-ldap-cert-setup.log" 1586 | IPADDR=$(hostname -I | awk '{print $1}') 1587 | 1588 | LDAP_ADMIN_DN=$(samba-tool user show Administrator | awk -F': ' '/^dn: / {print $2}') 1589 | if [[ -z "$LDAP_ADMIN_DN" ]]; then 1590 | dialog --backtitle "Samba Validation" --title "LDAP Test Error" --msgbox "Failed to retrieve Administrator DN from samba-tool output." 7 60 1591 | return 1 1592 | fi 1593 | 1594 | LDAP_BASEDN=$(echo "$LDAP_ADMIN_DN" | grep -oE 'DC=[^,]+(,DC=[^,]+)*') 1595 | 1596 | dialog --backtitle "Samba Validation" --infobox "Testing StartTLS on port 389..." 5 50 1597 | sleep 2 1598 | LDAPTLS_REQCERT=never \ 1599 | ldapsearch -x -H ldap://$IPADDR -ZZ \ 1600 | -D "$LDAP_ADMIN_DN" \ 1601 | -w "$ADMINPASS" \ 1602 | -b "$LDAP_BASEDN" dn >> "$LOG" 2>&1 1603 | 1604 | if grep -q "^dn: " "$LOG"; then 1605 | dialog --backtitle "Samba Validation" --infobox "StartTLS (389) test passed." 5 50 1606 | sleep 2 1607 | else 1608 | dialog --backtitle "Samba Validation" --msgbox "StartTLS (389) test failed — see $LOG for details." 7 60 1609 | fi 1610 | 1611 | dialog --backtitle "Samba Validation" --infobox "Testing LDAPS on port 636..." 5 50 1612 | sleep 2 1613 | LDAPTLS_REQCERT=never \ 1614 | ldapsearch -x -H ldaps://$IPADDR \ 1615 | -D "$LDAP_ADMIN_DN" \ 1616 | -w "$ADMINPASS" \ 1617 | -b "$LDAP_BASEDN" dn >> "$LOG" 2>&1 1618 | 1619 | if grep -q "^dn: " "$LOG"; then 1620 | dialog --backtitle "Samba Validation" --infobox "LDAPS (636) test passed." 5 50 1621 | sleep 2 1622 | else 1623 | dialog --backtitle "Samba Validation" --msgbox "LDAPS test failed — see $LOG for details." 7 60 1624 | fi 1625 | 1626 | dialog --backtitle "Samba Validation" --title "LDAP Secure Setup Complete" --infobox "StartTLS and LDAPS tested." 7 60 1627 | sleep 3 1628 | return 0 1629 | } 1630 | #===========KERBEROS LOGIN AND TICKET CHECK============= 1631 | check_kerberos_ticket() { 1632 | dialog --backtitle "Samba Validation" --title "Kerberos Login" --infobox "Attempting Kerberos login using Administrator credentials..." 5 80 1633 | sleep 2 1634 | 1635 | # Attempt kinit with password from variable 1636 | echo "$ADMINPASS" | kinit Administrator 2>/tmp/kinit_error.log 1637 | 1638 | if [[ $? -ne 0 ]]; then 1639 | ERROR_MSG=$(< /tmp/kinit_error.log) 1640 | dialog --backtitle "Samba Validation" --title "Kerberos Login Failed" --msgbox "Kerberos login failed:\n$ERROR_MSG" 10 80 1641 | return 1 1642 | fi 1643 | 1644 | # Run klist and capture output 1645 | klist_output=$(klist 2>&1) 1646 | 1647 | if echo "$klist_output" | grep -q "Valid starting.*Service principal"; then 1648 | dialog --backtitle "Samba Validation" --title "Kerberos Login Success" --infobox "Kerberos ticket successfully acquired for Administrator.\n\nTicket Details:\n\n$klist_output" 20 80 1649 | sleep 3 1650 | else 1651 | dialog --backtitle "Samba Validation" --title "Kerberos Ticket Check Failed" --msgbox "Kerberos login succeeded, but no valid ticket found.\n\n$klist_output" 10 80 1652 | return 1 1653 | fi 1654 | 1655 | return 0 1656 | } 1657 | #===========AUTHENTICATED SAMBA LOGIN CHECK============= 1658 | check_smbclient_login() { 1659 | dialog --backtitle "Samba Validation" --title "SMB Login Test" --infobox "Attempting SMB connection to //localhost/netlogon as Administrator..." 5 80 1660 | sleep 2 1661 | 1662 | smb_output=$(echo "$ADMINPASS" | smbclient //localhost/netlogon -UAdministrator -c 'ls' 2>&1) 1663 | 1664 | if echo "$smb_output" | grep -qE '^\s*\.\s+D\s+[0-9]+' && echo "$smb_output" | grep -qE '^\s*\.\.\s+D\s+[0-9]+'; then 1665 | dialog --backtitle "Samba Validation" --title "SMB Login Success" --infobox "Successfully authenticated and listed netlogon share." 5 60 1666 | sleep 2 1667 | else 1668 | dialog --backtitle "Samba Validation" --title "SMB Login Failed" --msgbox "SMB login failed or unexpected output.\n\n$smb_output" 15 70 1669 | return 1 1670 | fi 1671 | 1672 | return 0 1673 | } 1674 | #===========DNS SRV RECORD CHECK============= 1675 | check_dns_srv_records() { 1676 | FQDN=$(hostname -f) 1677 | DOMAIN=$(echo "$FQDN" | cut -d'.' -f2-) 1678 | HOSTNAME_PART=$(echo "$FQDN" | cut -d'.' -f1) 1679 | TIMEOUT=5 1680 | 1681 | dialog --backtitle "Samba Validation" --backtitle "SRV Records Check" --title "DNS SRV Record Check" --infobox "Querying SRV records for domain $DOMAIN..." 5 60 1682 | sleep 1 1683 | 1684 | # Perform SRV lookups with timeout 1685 | ldap_srv=$(timeout $TIMEOUT host -t SRV _ldap._tcp."$DOMAIN" 2>/dev/null) 1686 | kerberos_srv=$(timeout $TIMEOUT host -t SRV _kerberos._udp."$DOMAIN" 2>/dev/null) 1687 | fqdn_check=$(timeout $TIMEOUT host -t A "$FQDN" 2>/dev/null) 1688 | 1689 | # Handle timeout or failure 1690 | if [[ -z "$ldap_srv" || -z "$kerberos_srv" || -z "$fqdn_check" ]]; then 1691 | dialog --backtitle "Samba Validation" --title "DNS Query Timeout" --msgbox "Error: One or more DNS queries timed out after ${TIMEOUT}s.\n\nLDAP SRV:\n$ldap_srv\n 1692 | \nKerberos SRV:\n$kerberos_srv\n\nFQDN A record:\n$fqdn_check" 20 75 1693 | return 1 1694 | fi 1695 | 1696 | # Extract and normalize target FQDNs from SRV responses 1697 | get_srv_hostnames() { 1698 | local srv_records="$1" 1699 | echo "$srv_records" | awk '/SRV record/ {print tolower($NF)}' | sed 's/\.$//' 1700 | } 1701 | 1702 | ldap_targets=$(get_srv_hostnames "$ldap_srv") 1703 | kerberos_targets=$(get_srv_hostnames "$kerberos_srv") 1704 | 1705 | # Combine and check if any match our full FQDN 1706 | all_targets="$ldap_targets $kerberos_targets" 1707 | match_found=0 1708 | for t in $all_targets; do 1709 | if [[ "$t" == "$FQDN" ]]; then 1710 | match_found=1 1711 | break 1712 | fi 1713 | done 1714 | 1715 | if [[ $match_found -eq 1 ]]; then 1716 | dialog --backtitle "Samba Validation" --backtitle "SRV Records Check" --title "DNS SRV Check Passed" --infobox "Success: SRV record matches found for $FQDN\n\nLDAP SRV:\n$ldap_srv\n\nKerberos SRV:\n$kerberos_srv\n\nA Record:\n$fqdn_check" 20 75 1717 | sleep 3 1718 | return 0 1719 | else 1720 | # Check Samba service status 1721 | samba_status=$(systemctl is-active samba) 1722 | dns_entry=$(nmcli dev show | grep 'IP4.DNS') 1723 | 1724 | dialog --backtitle "Samba Validation" --title "DNS SRV Record Check Failed" --msgbox "Error: No matching SRV hostnames.\n\nSamba 1725 | status: $samba_status\n\nDNS entries:\n$dns_entry" 20 75 1726 | return 1 1727 | fi 1728 | } 1729 | #===========ANONYMOUS LOGIN TEST============= 1730 | test_anonymous_login() { 1731 | dialog --backtitle "Samba Validation" --title "Anonymous SMB Login Test" --infobox "Testing anonymous login to the Samba server..." 5 60 1732 | sleep 2 1733 | 1734 | output=$(smbclient -L localhost -N 2>&1) 1735 | 1736 | if echo "$output" | grep -q "Anonymous login successful"; then 1737 | dialog --backtitle "Samba Validation" --title "Anonymous Login Success" --infobox "Success: Anonymous login successful." 6 60 1738 | sleep 2 1739 | else 1740 | dialog --backtitle "Samba Validation" --title "Anonymous Login Failed" --msgbox "Error: Anonymous logins are not available.\n\n$output" 15 70 1741 | return 1 1742 | fi 1743 | 1744 | return 0 1745 | } 1746 | #===========CLEANUP STRONG AUTH LINE IN smb.conf============= 1747 | cleanup_strong_auth_line() { 1748 | CONF_FILE="/etc/samba/smb.conf" 1749 | 1750 | if grep -q '^[[:space:]]*\\#ldap server require strong auth = no' "$CONF_FILE"; then 1751 | dialog --backtitle "Samba Validation" --title "Cleaning Samba Config" --infobox "Fixing strong auth line in smb.conf..." 5 60 1752 | sleep 2 1753 | 1754 | # Remove leading backslash before #ldap line 1755 | sed -i 's/^[[:space:]]*\\#ldap server require strong auth = no/#ldap server require strong auth = no/' "$CONF_FILE" 1756 | 1757 | dialog --backtitle "Samba Validation" --title "Fix Applied" --infobox "smb.conf corrected" 5 60 1758 | sleep 2 1759 | else 1760 | dialog --backtitle "Samba Validation" --title "No Change Needed" --infobox "No Errors in smb.conf." 5 60 1761 | sleep 2 1762 | fi 1763 | } 1764 | 1765 | #===========CONFIGURE FAIL2BAN============= 1766 | configure_fail2ban() { 1767 | LOG_FILE="/var/log/fail2ban-setup.log" 1768 | ORIGINAL_FILE="/etc/fail2ban/jail.conf" 1769 | JAIL_LOCAL_FILE="/etc/fail2ban/jail.local" 1770 | SSHD_LOCAL_FILE="/etc/fail2ban/jail.d/sshd.local" 1771 | 1772 | { 1773 | echo "10" 1774 | echo "# Copying jail.conf to jail.local..." 1775 | if cp -v "$ORIGINAL_FILE" "$JAIL_LOCAL_FILE" >> "$LOG_FILE" 2>&1; then 1776 | echo "Copied jail.conf to jail.local" >> "$LOG_FILE" 1777 | else 1778 | dialog --backtitle "Configure Fail2ban for SSH" --title "Error" --msgbox "Failed to copy $ORIGINAL_FILE to $JAIL_LOCAL_FILE" 6 60 1779 | echo "Failed to copy jail.conf" >> "$LOG_FILE" 1780 | return 1 1781 | fi 1782 | 1783 | echo "30" 1784 | echo "# Enabling SSHD in jail.local..." 1785 | if sed -i '/^\[sshd\]/,/^$/ s/#mode.*normal/&\nenabled = true/' "$JAIL_LOCAL_FILE" >> "$LOG_FILE" 2>&1; then 1786 | echo "Modified jail.local to enable SSHD" >> "$LOG_FILE" 1787 | else 1788 | dialog --backtitle "Configure Fail2ban for SSH" --title "Error" --msgbox "Failed to enable SSHD in jail.local" 6 60 1789 | return 1 1790 | fi 1791 | 1792 | echo "50" 1793 | echo "# Writing SSHD jail configuration..." 1794 | cat < "$SSHD_LOCAL_FILE" 1795 | [sshd] 1796 | enabled = true 1797 | maxretry = 5 1798 | findtime = 300 1799 | bantime = 3600 1800 | bantime.increment = true 1801 | bantime.factor = 2 1802 | EOL 1803 | echo "Created sshd.local config" >> "$LOG_FILE" 1804 | 1805 | echo "60" 1806 | echo "# Enabling and starting Fail2Ban..." 1807 | systemctl enable fail2ban >> "$LOG_FILE" 2>&1 1808 | systemctl start fail2ban >> "$LOG_FILE" 2>&1 1809 | sleep 2 1810 | 1811 | echo "75" 1812 | echo "# Checking Fail2Ban status..." 1813 | if systemctl is-active --quiet fail2ban; then 1814 | echo "Fail2Ban is running." >> "$LOG_FILE" 1815 | else 1816 | echo "Fail2Ban failed to start. Attempting SELinux recovery..." >> "$LOG_FILE" 1817 | 1818 | selinux_status=$(sestatus | grep "SELinux status" | awk '{print $3}') 1819 | if [[ "$selinux_status" == "enabled" ]]; then 1820 | restorecon -v /etc/fail2ban/jail.local >> "$LOG_FILE" 2>&1 1821 | denials=$(ausearch -m avc -ts recent | grep "fail2ban-server" | wc -l) 1822 | if (( denials > 0 )); then 1823 | ausearch -c 'fail2ban-server' --raw | audit2allow -M my-fail2banserver >> "$LOG_FILE" 2>&1 1824 | semodule -X 300 -i my-fail2banserver.pp >> "$LOG_FILE" 2>&1 1825 | echo "Custom SELinux policy applied." >> "$LOG_FILE" 1826 | fi 1827 | fi 1828 | 1829 | systemctl restart fail2ban >> "$LOG_FILE" 2>&1 1830 | if systemctl is-active --quiet fail2ban; then 1831 | echo "Fail2Ban restarted successfully after SELinux fix." >> "$LOG_FILE" 1832 | else 1833 | dialog --title "Fail2Ban Error" --msgbox "Fail2Ban failed to start even after SELinux policy fix. Please investigate manually." 8 70 1834 | echo "Fail2Ban still failed after SELinux fix." >> "$LOG_FILE" 1835 | return 1 1836 | fi 1837 | fi 1838 | 1839 | echo "90" 1840 | echo "# Verifying SSHD jail status..." 1841 | sshd_status=$(fail2ban-client status sshd 2>&1) 1842 | if echo "$sshd_status" | grep -q "ERROR NOK: ('sshd',)"; then 1843 | dialog --backtitle "Configure Fail2ban for SSH" --title "SSHD Jail Error" --msgbox "SSHD jail failed to start. Check configuration:\n\n$sshd_status" 10 70 1844 | echo "SSHD jail failed to start." >> "$LOG_FILE" 1845 | elif echo "$sshd_status" | grep -q "Banned IP list:"; then 1846 | echo "SSHD jail is active and functional." >> "$LOG_FILE" 1847 | else 1848 | dialog --backtitle "Configure Fail2ban for SSH" --title "SSHD Jail Warning" --msgbox "SSHD jail may not be functional. Check manually:\n\n$sshd_status" 10 70 1849 | echo "SSHD jail might be non-functional." >> "$LOG_FILE" 1850 | fi 1851 | 1852 | echo "100" 1853 | } | dialog --backtitle "Configure Fail2ban for SSH" --title "Fail2Ban Setup" --gauge "Installing and configuring Fail2Ban..." 10 60 0 1854 | 1855 | dialog --backtitle "Configure Fail2ban for SSH" --title "Success" --infobox "Fail2Ban has been configured and started successfully." 6 60 1856 | sleep 3 1857 | } 1858 | #===========SERVICE CHECK & ENABLE PROGRESS============= 1859 | check_and_enable_services() { 1860 | TMP_LOG=$(mktemp) 1861 | TMP_BAR=$(mktemp) 1862 | 1863 | # List the services you want to manage 1864 | SERVICES=("fail2ban" "samba" "cockpit.socket") # <-- add or remove services as needed 1865 | 1866 | total=${#SERVICES[@]} 1867 | count=0 1868 | 1869 | { 1870 | for service in "${SERVICES[@]}"; do 1871 | echo "Checking $service..." >> "$TMP_LOG" 1872 | 1873 | systemctl is-enabled --quiet "$service" 1874 | if [[ $? -ne 0 ]]; then 1875 | echo "$service is not enabled. Enabling..." >> "$TMP_LOG" 1876 | systemctl enable "$service" >> "$TMP_LOG" 2>&1 1877 | fi 1878 | 1879 | systemctl is-active --quiet "$service" 1880 | if [[ $? -ne 0 ]]; then 1881 | echo "$service is not running. Starting..." >> "$TMP_LOG" 1882 | systemctl start "$service" >> "$TMP_LOG" 2>&1 1883 | fi 1884 | 1885 | systemctl is-active --quiet "$service" 1886 | if [[ $? -eq 0 ]]; then 1887 | echo "$service is active." >> "$TMP_LOG" 1888 | else 1889 | echo "$service failed to start." >> "$TMP_LOG" 1890 | fi 1891 | 1892 | # Progress bar update 1893 | count=$((count + 1)) 1894 | percent=$(( (count * 100) / total )) 1895 | echo $percent 1896 | sleep 1 1897 | done 1898 | } | dialog --backtitle "Enabling and Starting Services" --title "Service Check & Startup" --gauge "Checking services and starting them if needed..." 10 70 0 1899 | 1900 | # Final report 1901 | if grep -q "failed to start" "$TMP_LOG"; then 1902 | dialog --backtitle "Enabling and Starting Services" --title "Service Status" --textbox "$TMP_LOG" 20 70 1903 | else 1904 | dialog --backtitle "Enabling and Starting Services" --title "All Services Running" --infobox "All services have been enabled and are running." 7 60 1905 | sleep 3 1906 | fi 1907 | 1908 | rm -f "$TMP_LOG" "$TMP_BAR" 1909 | } 1910 | #===========INSTALL SERVER MANAGEMENT SCRIPTS============= 1911 | install_server_management() { 1912 | LOG_FILE="/var/log/server-management-install.log" 1913 | INSTALL_DIR="/root/RADS-SMInstaller" 1914 | GIT_REPO="https://github.com/fumatchu/RADS-SM.git" 1915 | 1916 | dialog --backtitle "Installing Server Manager" --title "Installing Server Management" --infobox \ 1917 | "This installer will set up Server Management tools for AD, DHCP, and services.\n\nYou can launch it anytime by typing: server-manager" 8 90 1918 | echo "[INFO] Starting Server Management installation..." >> "$LOG_FILE" 1919 | sleep 5 1920 | 1921 | mkdir -p "$INSTALL_DIR" 1922 | git clone "$GIT_REPO" "$INSTALL_DIR" >> "$LOG_FILE" 2>&1 1923 | if [[ $? -ne 0 ]]; then 1924 | dialog --backtitle "Installing Server Manager" --title "Clone Failed" --msgbox "Failed to clone repository from $GIT_REPO\nCheck $LOG_FILE for details." 8 60 1925 | echo "[ERROR] Git clone failed." >> "$LOG_FILE" 1926 | return 1 1927 | fi 1928 | 1929 | rm -rf /root/.servman /usr/bin/server-manager 1930 | sed -i '/\/usr\/bin\/server-manager/d' /root/.bash_profile 1931 | cd "$INSTALL_DIR" || return 1 1932 | 1933 | mv -v ./.servman /root >> "$LOG_FILE" 2>&1 1934 | chmod 700 "$INSTALL_DIR/server-manager" 1935 | mv -v "$INSTALL_DIR/server-manager" /usr/bin/ >> "$LOG_FILE" 2>&1 1936 | chmod -R 700 /root/.servman/ 1937 | echo "/usr/bin/server-manager" >> /root/.bash_profile 1938 | 1939 | rm -rf "$INSTALL_DIR" /root/RADS-* 1940 | 1941 | dialog --backtitle "Installing Server Manager" --title "Installation Complete" --msgbox \ 1942 | "Server Management tools have been successfully installed!\n\nType 'server-manager' at any time to launch the interface." 10 80 1943 | echo "[SUCCESS] Server Manager installed." >> "$LOG_FILE" 1944 | } 1945 | #===========CLEANUP INSTALLATION FILES============= 1946 | cleanup_installer_files() { 1947 | LOG_FILE="/var/log/rads-cleanup.log" 1948 | TMP_PROGRESS=$(mktemp) 1949 | 1950 | { 1951 | echo "10"; sleep 0.5 1952 | echo "# Starting cleanup..." >> "$TMP_PROGRESS" 1953 | 1954 | # Remove DCInstall.sh launch block 1955 | sed -i '/## Run RADS installer on every interactive login ##/,/fi/d' /root/.bash_profile 1956 | echo "[INFO] Removed RADS installer launch block" >> "$LOG_FILE" 1957 | echo "30"; sleep 0.5 1958 | 1959 | # Also remove any straggling DCInstall.sh lines 1960 | sed -i '/DCInstall.sh/d' /root/.bash_profile 1961 | echo "[INFO] Removed any additional DCInstall.sh entries" >> "$LOG_FILE" 1962 | echo "50"; sleep 0.5 1963 | 1964 | # Delete installer-related files 1965 | rm -rf /root/DC-Installer.sh /root/ADDCInstaller /root/FR-Installer /root/FR-Installer.sh >> "$LOG_FILE" 2>&1 1966 | rm -f /root/samba*.src.rpm >> "$LOG_FILE" 2>&1 1967 | echo "[INFO] Removed installer files" >> "$LOG_FILE" 1968 | echo "90"; sleep 0.5 1969 | 1970 | echo "100" 1971 | } | dialog --backtitle "Installer Cleanup" --title "Cleanup Progress" --gauge "Cleaning up installer files..." 10 60 0 1972 | 1973 | rm -f "$TMP_PROGRESS" 1974 | 1975 | dialog --backtitle "Installer Cleanup" --title "Cleanup Complete" --infobox "Installer files have been successfully removed from the system." 6 80 1976 | sleep 3 1977 | } 1978 | #===========FINAL INSTALLATION COMPLETE PROMPT============= 1979 | prompt_reboot_now() { 1980 | dialog --backtitle "Installation Complete" --title "Installation Complete" \ 1981 | --yesno "Server Installation Complete!\n\nWould you like to reboot the system now?" 8 50 1982 | 1983 | if [[ $? -eq 0 ]]; then 1984 | reboot 1985 | fi 1986 | } 1987 | configure_dnf_automatic() { 1988 | local CONFIG="/etc/dnf/automatic.conf" 1989 | local BACKUP="/etc/dnf/automatic.conf.bak" 1990 | local LOG="/tmp/dnf_automatic_setup.log" 1991 | : > "$LOG" 1992 | 1993 | # 1. Inform the user 1994 | dialog --backtitle "DNF Automatic Setup" --title "Configure Security Updates" \ 1995 | --infobox "This will enable SECURITY-ONLY updates.\n\nIt will also disable major OS upgrades.\n\nUpdate time will be left to system default" 10 60 1996 | sleep 3 1997 | 1998 | # 2. Backup current config 1999 | sudo cp -f "$CONFIG" "$BACKUP" 2000 | echo "[INFO] Backed up $CONFIG to $BACKUP" >> "$LOG" 2001 | 2002 | # 3. Apply dnf-automatic settings 2003 | sudo sed -i 's/^upgrade_type.*/upgrade_type = security/' "$CONFIG" 2004 | sudo sed -i 's/^apply_updates.*/apply_updates = yes/' "$CONFIG" 2005 | 2006 | # 4. Remove any [timer] section from the config (let systemd handle it) 2007 | sudo sed -i '/^\[timer\]/,/^$/d' "$CONFIG" 2008 | 2009 | # 5. Remove any old systemd override (Cockpit workaround) 2010 | sudo rm -f /etc/systemd/system/dnf-automatic.timer.d/override.conf 2011 | 2012 | # 6. Reload systemd and restart timer 2013 | sudo systemctl daemon-reexec 2014 | sudo systemctl daemon-reload 2015 | sudo systemctl enable --now dnf-automatic.timer 2016 | 2017 | # 7. Validate setup 2018 | local STATUS_MSG="" 2019 | local VALIDATE_OUTPUT 2020 | VALIDATE_OUTPUT=$(grep -E 'upgrade_type|apply_updates' "$CONFIG") 2021 | echo "$VALIDATE_OUTPUT" >> "$LOG" 2022 | 2023 | if echo "$VALIDATE_OUTPUT" | grep -q "apply_updates = yes"; then 2024 | STATUS_MSG="Security updates enabled.\n" 2025 | else 2026 | dialog --title "Error" --msgbox "Configuration failed.\nCheck $CONFIG or $LOG." 7 50 2027 | return 1 2028 | fi 2029 | 2030 | if systemctl is-active --quiet dnf-automatic.timer; then 2031 | STATUS_MSG+="Timer is active.\n" 2032 | else 2033 | STATUS_MSG+="Timer is not running!\nCheck: journalctl -u dnf-automatic.timer\n" 2034 | fi 2035 | 2036 | NEXT_RUN=$(systemctl list-timers --all | grep dnf-automatic.timer | awk '{print $1, $2}') 2037 | STATUS_MSG+="\nNext scheduled run: $NEXT_RUN" 2038 | 2039 | dialog --backtitle "DNF Automatic Setup" --title "Setup Complete" --infobox "$STATUS_MSG" 12 60 2040 | sleep 4 2041 | } 2042 | 2043 | # ========= MAIN ========= 2044 | check_samba_running 2045 | show_welcome_screen 2046 | detect_active_interface 2047 | prompt_static_ip_if_dhcp 2048 | check_root_and_os 2049 | check_and_enable_selinux 2050 | check_internet_connectivity 2051 | validate_and_set_hostname 2052 | show_ad_server_checklist 2053 | install_requirements 2054 | validate_ad_server 2055 | validate_ad_admin_password 2056 | configure_chrony_from_addc 2057 | configure_dhcp_server 2058 | update_and_install_packages 2059 | vm_detection 2060 | configure_selinux 2061 | configure_firewall 2062 | configure_samba_provisioning 2063 | add_dnf_smb_mon_cron 2064 | copy_samba_dnf_pkg_update 2065 | update_issue_file 2066 | setup_samba_ldaps_cert 2067 | test_ldap_secure_connection 2068 | check_kerberos_ticket 2069 | check_smbclient_login 2070 | check_dns_srv_records 2071 | test_anonymous_login 2072 | cleanup_strong_auth_line 2073 | configure_fail2ban 2074 | configure_dnf_automatic 2075 | check_and_enable_services 2076 | install_server_management 2077 | cleanup_installer_files 2078 | prompt_reboot_now 2079 | -------------------------------------------------------------------------------- /DCInstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | GREEN="\033[0;32m" 3 | RED="\033[0;31m" 4 | YELLOW="\033[1;33m" 5 | TEXTRESET="\033[0m" 6 | CYAN="\e[36m" 7 | RESET="\e[0m" 8 | USER=$(whoami) 9 | MAJOROS=$(cat /etc/redhat-release | grep -Eo "[0-9]" | sed '$d') 10 | 11 | 12 | clear 13 | echo -e "[${GREEN}SUCCESS${TEXTRESET}] Rocky ${CYAN}RADS FOREST${TEXTRESET} Builder ${YELLOW}Installation${TEXTRESET}" 14 | 15 | # Checking for user permissions 16 | if [ "$USER" = "root" ]; then 17 | echo -e "[${GREEN}SUCCESS${TEXTRESET}] Running as root user" 18 | sleep 2 19 | else 20 | echo -e "[${RED}ERROR${TEXTRESET}] This program must be run as root." 21 | echo "Exiting..." 22 | exit 1 23 | fi 24 | 25 | # Checking for version information 26 | if [ "$MAJOROS" -ge 9 ]; then 27 | echo -e "[${GREEN}SUCCESS${TEXTRESET}] Detected compatible OS version: Rocky 9.x or greater" 28 | sleep 2 29 | else 30 | echo -e "[${RED}ERROR${TEXTRESET}] Sorry, but this installer only works on Rocky 9.X or greater" 31 | echo -e "Please upgrade to ${GREEN}Rocky 9.x${TEXTRESET} or later" 32 | echo "Exiting the installer..." 33 | exit 1 34 | fi 35 | # ========= CHECK FOR PRE-EXISTING SMB AND SAMBA SERVICE ========= 36 | check_samba_running() { 37 | # Check if either smb or samba service is active 38 | if systemctl is-active --quiet smb || systemctl is-active --quiet samba; then 39 | dialog --backtitle "Samba Check" --title "Samba Service Running" --msgbox "Samba (or SMB) is currently running on this system. A fresh install of the OS is required to install Samba.\n\nPlease perform a clean installation." 10 60 40 | exit 1 41 | fi 42 | } 43 | 44 | 45 | # ========= REMOVE BRACKETED PASTING ========= 46 | sed -i '8i set enable-bracketed-paste off' /etc/inputrc 47 | 48 | 49 | # ========= INSERT INSTALLER INTO .bash_profile ========= 50 | PROFILE="/root/.bash_profile" 51 | BACKUP="/root/.bash_profile.bak.$(date +%Y%m%d%H%M%S)" 52 | INSTALLER="/root/ADDCInstaller/DCInstall.sh" 53 | 54 | cat << 'EOF' >> "$PROFILE" 55 | 56 | ## Run RADS installer on every interactive login ## 57 | if [[ $- == *i* ]]; then 58 | /root/ADDCInstaller/DCInstall.sh 59 | fi 60 | EOF 61 | if [[ -f "$INSTALLER" ]]; then 62 | chmod +x "$INSTALLER" 63 | else 64 | echo "WARNING: Installer not found at $INSTALLER" 65 | fi 66 | 67 | 68 | # ========= VALIDATION HELPERS ========= 69 | validate_cidr() { [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; } 70 | validate_ip() { [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; } 71 | validate_fqdn() { [[ "$1" =~ ^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$ ]]; } 72 | 73 | is_host_ip() { 74 | local cidr="$1" 75 | local ip_part="${cidr%/*}" 76 | local mask="${cidr#*/}" 77 | 78 | IFS='.' read -r o1 o2 o3 o4 <<< "$ip_part" 79 | ip_dec=$(( (o1 << 24) + (o2 << 16) + (o3 << 8) + o4 )) 80 | 81 | netmask=$(( 0xFFFFFFFF << (32 - mask) & 0xFFFFFFFF )) 82 | network=$(( ip_dec & netmask )) 83 | broadcast=$(( network | ~netmask & 0xFFFFFFFF )) 84 | 85 | [[ "$ip_dec" -eq "$network" || "$ip_dec" -eq "$broadcast" ]] && return 1 || return 0 86 | } 87 | 88 | check_hostname_in_domain() { 89 | local fqdn="$1" 90 | local hostname="${fqdn%%.*}" 91 | local domain="${fqdn#*.}" 92 | [[ ! "$domain" =~ (^|\.)"$hostname"(\.|$) ]] 93 | } 94 | isValidIP() { 95 | [[ $1 =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1 96 | IFS=. read -r o1 o2 o3 o4 <<< "$1" 97 | (( o1 <= 255 && o2 <= 255 && o3 <= 255 && o4 <= 255 )) || return 1 98 | return 0 99 | } 100 | 101 | isValidNetmask() { 102 | local valid=( 103 | 255.255.255.0 255.255.0.0 255.0.0.0 104 | 255.255.254.0 255.255.252.0 255.255.248.0 255.255.240.0 105 | 255.255.224.0 255.255.192.0 255.255.128.0 106 | ) 107 | [[ " ${valid[*]} " =~ " $1 " ]] 108 | } 109 | 110 | isIPInRange() { 111 | local ip=$1 112 | local ipnum=$(ipToNumber "$ip") 113 | local netnum=$(ipToNumber "$NETWORK") 114 | local broadnum=$(ipToNumber "$BROADCAST") 115 | [[ $ipnum -ge $netnum && $ipnum -le $broadnum ]] 116 | } 117 | 118 | # ========= SYSTEM CHECKS ========= 119 | check_root_and_os() { 120 | if [[ "$EUID" -ne 0 ]]; then 121 | dialog --aspect 9 --title "Permission Denied" --msgbox "This script must be run as root." 7 50 122 | clear; exit 1 123 | fi 124 | 125 | if [[ -f /etc/redhat-release ]]; then 126 | MAJOROS=$(grep -oP '\d+' /etc/redhat-release | head -1) 127 | else 128 | dialog --title "OS Check Failed" --msgbox "/etc/redhat-release not found. Cannot detect OS." 7 50 129 | exit 1 130 | fi 131 | 132 | if [[ "$MAJOROS" -lt 9 ]]; then 133 | dialog --title "Unsupported OS" --msgbox "This installer requires Rocky Linux 9.x or later." 7 50 134 | exit 1 135 | fi 136 | } 137 | 138 | # ========= SELINUX CHECK ========= 139 | check_and_enable_selinux() { 140 | local current_status=$(getenforce) 141 | 142 | if [[ "$current_status" == "Enforcing" ]]; then 143 | dialog --backtitle "Checking and Enabling SELinux" --title "SELinux Status" --infobox "SELinux is already enabled and enforcing." 6 50 144 | sleep 4 145 | else 146 | dialog --backtitle "Checking and Enabling SELinux" --title "SELinux Disabled" --msgbox "SELinux is not enabled. Enabling SELinux now..." 6 50 147 | sed -i 's/SELINUX=disabled/SELINUX=enforcing/' /etc/selinux/config 148 | setenforce 1 149 | 150 | if [[ "$(getenforce)" == "Enforcing" ]]; then 151 | dialog --backtitle "Checking and Enabling SELinux" --title "SELinux Enabled" --msgbox "SELinux has been successfully enabled and is now enforcing." 6 50 152 | else 153 | dialog --backtitle "Checking and Enabling SELinux" --title "SELinux Error" --msgbox "Failed to enable SELinux. Please check the configuration manually." 6 50 154 | exit 1 155 | fi 156 | fi 157 | } 158 | 159 | # ========= NETWORK DETECTION ========= 160 | detect_active_interface() { 161 | dialog --backtitle "Network Setup" --title "Interface Check" --infobox "Checking active network interface..." 5 50 162 | sleep 3 163 | 164 | # Attempt 1: Use nmcli to find connected Ethernet 165 | INTERFACE=$(nmcli -t -f DEVICE,TYPE,STATE device | grep "ethernet:connected" | cut -d: -f1 | head -n1) 166 | 167 | # Attempt 2: Fallback to any interface with an IP if nmcli fails 168 | if [[ -z "$INTERFACE" ]]; then 169 | INTERFACE=$(ip -o -4 addr show up | grep -v ' lo ' | awk '{print $2}' | head -n1) 170 | fi 171 | 172 | # Get the matching connection profile name 173 | if [[ -n "$INTERFACE" ]]; then 174 | CONNECTION=$(nmcli -t -f NAME,DEVICE connection show | grep ":$INTERFACE" | cut -d: -f1) 175 | fi 176 | 177 | # Log to /tmp in case of failure 178 | echo "DEBUG: INTERFACE=$INTERFACE" >> /tmp/kvm_debug.log 179 | echo "DEBUG: CONNECTION=$CONNECTION" >> /tmp/kvm_debug.log 180 | 181 | if [[ -z "$INTERFACE" || -z "$CONNECTION" ]]; then 182 | dialog --clear --no-ok --backtitle "Network Setup" --title "Interface Error" --aspect 9 --msgbox "No active network interface with IP found. Check /tmp/kvm_debug.log for d 183 | etails." 5 70 184 | exit 1 185 | fi 186 | 187 | export INTERFACE CONNECTION 188 | } 189 | 190 | # ========= STATIC IP CONFIG ========= 191 | prompt_static_ip_if_dhcp() { 192 | IP_METHOD=$(nmcli -g ipv4.method connection show "$CONNECTION" | tr -d '' | xargs) 193 | 194 | if [[ "$IP_METHOD" == "manual" ]]; then 195 | dialog --title "Static IP Detected" --infobox "Interface '$INTERFACE' is already using a static IP" 6 70 196 | sleep 3 197 | return 198 | elif [[ "$IP_METHOD" == "auto" ]]; then 199 | while true; do 200 | while true; do 201 | IPADDR=$(dialog --backtitle "Interface Setup" --title "Static IP Address Required" --inputbox "***DHCP DETECTED on '$INTERFACE'***\n\nEnter static IP in CIDR format (e.g., 192.168.1.100/24):" 8 80 3>&1 1>&2 2>&3) 202 | validate_cidr "$IPADDR" && break || dialog --msgbox "Invalid CIDR format. Try again." 6 40 203 | done 204 | 205 | while true; do 206 | GW=$(dialog --backtitle "Interface Setup" --title "Gateway" --inputbox "Enter default gateway:" 8 60 3>&1 1>&2 2>&3) 207 | validate_ip "$GW" && break || dialog --msgbox "Invalid IP address. Try again." 6 40 208 | done 209 | 210 | while true; do 211 | DNSSERVER=$(dialog --backtitle "Interface Setup" --title "DNS Server" --inputbox "Enter Upstream DNS server IP:" 8 60 3>&1 1>&2 2>&3) 212 | validate_ip "$DNSSERVER" && break || dialog --msgbox "Invalid IP address. Try again." 6 40 213 | done 214 | 215 | while true; do 216 | HOSTNAME=$(dialog --backtitle "Interface Setup" --title "FQDN" --inputbox "Enter FQDN (e.g., host.domain.com):" 8 60 3>&1 1>&2 2>&3) 217 | if validate_fqdn "$HOSTNAME" && check_hostname_in_domain "$HOSTNAME"; then break 218 | else dialog --msgbox "Invalid FQDN or hostname repeated in domain. Try again." 7 60 219 | fi 220 | done 221 | 222 | while true; do 223 | DNSSEARCH=$(dialog --backtitle "Interface Setup" --title "DNS Search" --inputbox "Enter domain search suffix (e.g., localdomain):" 8 60 3>&1 1>&2 2>&3) 224 | [[ -n "$DNSSEARCH" ]] && break || dialog --msgbox "Search domain cannot be blank." 6 40 225 | done 226 | 227 | dialog --backtitle "Interface Setup" --title "Confirm Settings" --yesno "Apply these settings?\n\nInterface: $INTERFACE\nIP: $IPADDR\nGW: $GW\nFQDN: $HOSTNAME\nDNS: $DNSSERVER\nSearch: $DNSSEARCH" 12 60 228 | 229 | if [[ $? -eq 0 ]]; then 230 | nmcli con mod "$CONNECTION" ipv4.address "$IPADDR" 231 | nmcli con mod "$CONNECTION" ipv4.gateway "$GW" 232 | nmcli con mod "$CONNECTION" ipv4.method manual 233 | nmcli con mod "$CONNECTION" ipv4.dns "$DNSSERVER" 234 | nmcli con mod "$CONNECTION" ipv4.dns-search "$DNSSEARCH" 235 | hostnamectl set-hostname "$HOSTNAME" 236 | 237 | 238 | dialog --clear --no-shadow --no-ok --backtitle "REBOOT REQUIRED" --title "Reboot Required" --aspect 9 --msgbox "Network stack set. The System will reboot. Reconnect at: ${IPADDR%%/*}" 5 95 239 | reboot 240 | fi 241 | done 242 | fi 243 | } 244 | 245 | # ========= UI SCREENS ========= 246 | show_welcome_screen() { 247 | clear 248 | echo -e "${GREEN} 249 | .*((((((((((((((((* 250 | .(((((((((((((((((((((((((((/ 251 | ,((((((((((((((((((((((((((((((((((. 252 | (((((((((((((((((((((((((((((((((((((((/ 253 | (((((((((((((((((((((((((((((((((((((((((((/ 254 | .((((((((((((((((((((((((((((((((((((((((((((( 255 | ,((((((((((((((((((((((((((((((((((((((((((((((((. 256 | ((((((((((((((((((((((((((((((/ ,((((((((((((((( 257 | /((((((((((((((((((((((((((((. /((((((((((((* 258 | ((((((((((((((((((((((((((/ (((((((((( 259 | (((((((((((((((((((((((( *((((((/ 260 | /((((((((((((((((((((* (((((* 261 | (((((((((((((((((( (((* ,(( 262 | .((((((((((((((. /((((((( 263 | ((((((((((/ (((((((((((((/ 264 | *((((((. /((((((((((((((((((. 265 | *(*) ,(((((((((((((((((((((((, 266 | (((((((((((((((((((((((/ 267 | /((((((((((((((((((((((. 268 | ,((((((((((((((, 269 | ${RESET}" 270 | echo -e " ${GREEN}Rocky Linux${RESET} ${CYAN}RADS FOREST${RESET} ${YELLOW}Builder${RESET}" 271 | 272 | sleep 2 273 | } 274 | 275 | # ========= INTERNET CONNECTIVITY CHECK ========= 276 | check_internet_connectivity() { 277 | dialog --backtitle "Checking Internet Connectivity" --title "Network Test" --infobox "Checking internet connectivity..." 5 50 278 | sleep 2 279 | 280 | local dns_test="FAILED" 281 | local ip_test="FAILED" 282 | 283 | if ping -c 1 -W 2 google.com &>/dev/null; then 284 | dns_test="SUCCESS" 285 | fi 286 | 287 | if ping -c 1 -W 2 8.8.8.8 &>/dev/null; then 288 | ip_test="SUCCESS" 289 | fi 290 | 291 | dialog --backtitle "Checking Internet Connectivity" --title "Connectivity Test Results" --infobox "DNS Resolution: $dns_test 292 | Direct IP (8.8.8.8): $ip_test " 7 50 293 | sleep 4 294 | 295 | if [[ "$dns_test" == "FAILED" || "$ip_test" == "FAILED" ]]; then 296 | dialog --backtitle "Checking Internet Connectivity" --title "Network Warning" --yesno "Internet connectivity issues detected. Do you want to continue?" 7 50 297 | if [[ $? -ne 0 ]]; then 298 | exit 1 299 | fi 300 | fi 301 | } 302 | 303 | # ========= HOSTNAME VALIDATION ========= 304 | validate_and_set_hostname() { 305 | local current_hostname 306 | current_hostname=$(hostname) 307 | 308 | if [[ "$current_hostname" == "localhost.localdomain" ]]; then 309 | while true; do 310 | NEW_HOSTNAME=$(dialog --backtitle "Configure Hostname" --title "Hostname Configuration" --inputbox \ 311 | "Current hostname is '$current_hostname'. Please enter a new FQDN (e.g., server.example.com):" \ 312 | 8 60 3>&1 1>&2 2>&3) 313 | 314 | if validate_fqdn "$NEW_HOSTNAME" && check_hostname_in_domain "$NEW_HOSTNAME"; then 315 | hostnamectl set-hostname "$NEW_HOSTNAME" 316 | dialog --backtitle "Configure Hostname" --title "Hostname Set" --msgbox "Hostname updated to: $NEW_HOSTNAME" 6 50 317 | break 318 | else 319 | dialog --backtitle "Configure Hostname" --title "Invalid Hostname" --msgbox "Invalid hostname. Please try again." 6 50 320 | fi 321 | done 322 | else 323 | # Show a temporary info box with current hostname, no OK button 324 | dialog --backtitle "Configure Hostname" --title "Hostname Check" --infobox \ 325 | "Hostname set to: $current_hostname" 6 60 326 | sleep 3 327 | fi 328 | } 329 | 330 | # ========= SHOW CHECKLIST TO USER ========= 331 | 332 | show_ad_server_checklist() { 333 | dialog --backtitle "Welcome to the RADS Domain/Forest Installer" --title "First AD Server Installation Checklist" --msgbox "\ 334 | ********************************************* 335 | 336 | This will Install the FIRST AD Server and build a new Forest/Domain 337 | 338 | Checklist: 339 | Before the Installer starts, please make sure you have the following information: 340 | 341 | 1. An Administrator password that you want to use for the new DOMAIN 342 | 2. An NTP Subnet for your clients. This server will provide synchronized time 343 | 3. The beginning and ending lease range for DHCP (optional) 344 | 4. The client default gateway IP Address for the DHCP Scope (optional) 345 | 5. A Friendly name as a description to the DHCP scope created (optional) 346 | 347 | *********************************************" 20 100 348 | } 349 | 350 | 351 | # ========= ASK FOR DOMAIN PASSWORD CREATION ========= 352 | TMP_FILE=$(mktemp) 353 | 354 | show_password_requirements() { 355 | dialog --backtitle "Domain Administrator Password Setup" --title "Administrator Password Requirements" --msgbox \ 356 | "Please create the DOMAIN password for the Administrator Account 357 | 358 | Your password must meet the following criteria: 359 | 360 | - At least 8 characters 361 | - At least 1 special character (!@#\$%^&) 362 | - At least 1 uppercase letter 363 | - At least 1 lowercase letter 364 | - At least 1 number" 15 90 365 | } 366 | 367 | validate_admin_password() { 368 | local password="$1" 369 | 370 | if [ ${#password} -lt 8 ]; then 371 | echo "Password is too short (minimum 8 characters)." 372 | return 1 373 | fi 374 | if ! [[ "$password" =~ [0-9] ]]; then 375 | echo "Password must include at least one number." 376 | return 1 377 | fi 378 | if ! [[ "$password" =~ [\!\@\#\$\%\^\&\*] ]]; then 379 | echo "Password must include at least one special character (!@#\$%^&*)." 380 | return 1 381 | fi 382 | if ! [[ "$password" =~ [A-Z] && "$password" =~ [a-z] ]]; then 383 | echo "Password must contain both uppercase and lowercase letters." 384 | return 1 385 | fi 386 | 387 | return 0 388 | } 389 | 390 | prompt_admin_password() { 391 | show_password_requirements 392 | 393 | while true; do 394 | dialog --backtitle "Domain Administrator Password Setup" --insecure --passwordbox "Enter Administrator Password:" 10 60 2> "$TMP_FILE" 395 | ADMINPASS=$(<"$TMP_FILE") 396 | 397 | if [ -z "$ADMINPASS" ]; then 398 | dialog --backtitle "Domain Administrator Password Setup" --msgbox "Password cannot be blank. Please try again." 6 50 399 | continue 400 | fi 401 | 402 | error=$(validate_admin_password "$ADMINPASS" 2>&1) 403 | if ! validate_admin_password "$ADMINPASS"; then 404 | dialog --backtitle "Domain Administrator Password Setup" --msgbox "$error" 8 60 405 | continue 406 | fi 407 | 408 | dialog --backtitle "Domain Administrator Password Setup" --insecure --passwordbox "Confirm Administrator Password:" 10 60 2> "$TMP_FILE" 409 | VERIFYPASS=$(<"$TMP_FILE") 410 | 411 | if [ -z "$VERIFYPASS" ]; then 412 | dialog --backtitle "Domain Administrator Password Setup" --msgbox "Confirmation cannot be blank. Please try again." 6 50 413 | continue 414 | fi 415 | 416 | if [ "$ADMINPASS" = "$VERIFYPASS" ]; then 417 | dialog --backtitle "Domain Administrator Password Setup" --infobox "Password accepted and saved." 5 40 418 | sleep 2 419 | break 420 | else 421 | dialog --backtitle "Domain Administrator Password Setup" --msgbox "Passwords do not match. Please try again." 6 50 422 | fi 423 | done 424 | 425 | export ADMINPASS 426 | rm -f "$TMP_FILE" 427 | } 428 | 429 | # ========= CONFIGURE CHRONY ========= 430 | declare -a ADDR 431 | LOG_NTP="/tmp/chrony_ntp_configure.log" 432 | touch "$LOG_NTP" 433 | 434 | log_ntp() { 435 | echo "$(date '+%Y-%m-%d %H:%M:%S') - $*" >> "$LOG_NTP" 436 | } 437 | 438 | validate_cidr() { 439 | [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]] 440 | } 441 | 442 | prompt_ntp_servers() { 443 | while true; do 444 | NTP_SERVERS=$(dialog --title "Chrony NTP Configuration" \ 445 | --backtitle "Configure NTP" --inputbox "Enter up to 3 comma-separated NTP server IPs or FQDNs:" 8 60 \ 446 | 3>&1 1>&2 2>&3) 447 | exit_status=$? 448 | if [ $exit_status -eq 1 ] || [ $exit_status -eq 255 ]; then 449 | return 1 450 | fi 451 | 452 | if [[ -n "$NTP_SERVERS" ]]; then 453 | IFS=',' read -ra ADDR <<< "$NTP_SERVERS" 454 | if (( ${#ADDR[@]} > 3 )); then 455 | dialog --backtitle "Configure NTP" --title "Chrony NTP Configuration" --msgbox "You may only enter up to 3 servers." 6 50 456 | continue 457 | fi 458 | return 0 459 | else 460 | dialog --backtitle "Configure NTP" --title "Chrony NTP Configuration" --msgbox "The input cannot be blank. Please try again." 6 50 461 | fi 462 | done 463 | } 464 | 465 | prompt_allow_networks() { 466 | while true; do 467 | ALLOW_NET=$(dialog --title "Allow NTP Access" \ 468 | --backtitle "Configure NTP" --inputbox "Enter the CIDR range to allow NTP access (e.g., 192.168.1.0/24):" 8 80 \ 469 | 3>&1 1>&2 2>&3) 470 | exit_status=$? 471 | if [ $exit_status -ne 0 ]; then 472 | return 1 473 | fi 474 | 475 | if validate_cidr "$ALLOW_NET"; then 476 | return 0 477 | else 478 | dialog --backtitle "Configure NTP" --msgbox "Invalid CIDR format. Please try again." 6 40 479 | fi 480 | done 481 | } 482 | 483 | update_chrony_config() { 484 | cp /etc/chrony.conf /etc/chrony.conf.bak 485 | sed -i '/^\(server\|pool\|allow\)[[:space:]]/d' /etc/chrony.conf 486 | 487 | for srv in "${ADDR[@]}"; do 488 | echo "server ${srv} iburst" >> /etc/chrony.conf 489 | log_ntp "Added server ${srv} to chrony.conf" 490 | done 491 | 492 | if [[ -n "$ALLOW_NET" ]]; then 493 | echo "allow $ALLOW_NET" >> /etc/chrony.conf 494 | log_ntp "Added allow $ALLOW_NET to chrony.conf" 495 | fi 496 | 497 | systemctl restart chronyd 498 | sleep 2 499 | } 500 | 501 | validate_time_sync() { 502 | local attempt=1 503 | local success=0 504 | 505 | while (( attempt <= 3 )); do 506 | dialog --backtitle "Configure NTP" --title "Chrony NTP Configuration" --infobox "Validating time sync... Attempt $attempt/3" 4 50 507 | sleep 5 508 | 509 | TRACKING=$(chronyc tracking 2>&1) 510 | echo "$TRACKING" >> "$LOG_NTP" 511 | 512 | if echo "$TRACKING" | grep -q "Leap status[[:space:]]*:[[:space:]]*Normal"; then 513 | success=1 514 | break 515 | fi 516 | ((attempt++)) 517 | done 518 | 519 | if [[ "$success" -eq 1 ]]; then 520 | dialog --backtitle "Configure NTP" --title "Chrony NTP Configuration" --infobox "Time synchronized successfully:\n\n$TRACKING" 15 100 521 | sleep 3 522 | else 523 | dialog --backtitle "Configure NTP" --title "Chrony NTP Configuration" --yesno "Time sync failed after 3 attempts.\nDo you want to proceed anyway?" 8 100 524 | [[ $? -eq 0 ]] || return 1 525 | fi 526 | return 0 527 | } 528 | 529 | # ========= SYSTEM UPDATE & PACKAGE INSTALL ========= 530 | update_and_install_packages() { 531 | # Simulate progress while enabling EPEL and CRB 532 | dialog --backtitle "Base Package Update" --title "Repository Setup" --gauge "Enabling EPEL and CRB repositories..." 10 60 0 < <( 533 | ( 534 | ( 535 | dnf install -y epel-release >/dev/null 2>&1 536 | dnf config-manager --set-enabled crb >/dev/null 2>&1 537 | ) & 538 | PID=$! 539 | PROGRESS=0 540 | while kill -0 "$PID" 2>/dev/null; do 541 | echo "$PROGRESS" 542 | echo "XXX" 543 | echo "Enabling EPEL and CRB..." 544 | echo "XXX" 545 | ((PROGRESS += 5)) 546 | if [[ $PROGRESS -ge 95 ]]; then 547 | PROGRESS=5 548 | fi 549 | sleep 0.5 550 | done 551 | echo "100" 552 | echo "XXX" 553 | echo "Repositories enabled." 554 | echo "XXX" 555 | ) 556 | ) 557 | 558 | dialog --backtitle "Base Package Update" --title "System Update" --infobox "Checking for updates. This may take a few moments..." 5 70 559 | sleep 2 560 | 561 | dnf check-update -y &>/dev/null 562 | 563 | TEMP_FILE=$(mktemp) 564 | dnf check-update | awk '{print $1}' | grep -vE '^$|Obsoleting|Last' | awk -F'.' '{print $1}' | sort -u > "$TEMP_FILE" 565 | 566 | PACKAGE_LIST=($(cat "$TEMP_FILE")) 567 | TOTAL_PACKAGES=${#PACKAGE_LIST[@]} 568 | 569 | if [[ "$TOTAL_PACKAGES" -eq 0 ]]; then 570 | dialog --backtitle "Base Package Update" --title "System Update" --msgbox "No updates available!" 6 50 571 | rm -f "$TEMP_FILE" 572 | else 573 | PIPE=$(mktemp -u) 574 | mkfifo "$PIPE" 575 | dialog --backtitle "Base Package Update" --title "System Update" --gauge "Installing updates..." 10 70 0 < "$PIPE" & 576 | exec 3>"$PIPE" 577 | COUNT=0 578 | for PACKAGE in "${PACKAGE_LIST[@]}"; do 579 | ((COUNT++)) 580 | PERCENT=$(( (COUNT * 100) / TOTAL_PACKAGES )) 581 | echo "$PERCENT" > "$PIPE" 582 | echo "XXX" > "$PIPE" 583 | echo "Updating: $PACKAGE" > "$PIPE" 584 | echo "XXX" > "$PIPE" 585 | dnf -y install "$PACKAGE" >/dev/null 2>&1 586 | done 587 | exec 3>&- 588 | rm -f "$PIPE" "$TEMP_FILE" 589 | fi 590 | 591 | dialog --backtitle "Required Package Install" --title "Package Installation" --infobox "Installing Required Packages..." 5 50 592 | sleep 2 593 | PACKAGE_LIST=("ntsysv" "iptraf" "expect" "nano" "rsync" "sshpass" "openldap-clients" "fail2ban" "tuned" "createrepo" "cockpit" "cockpit-storaged" "mock" "cockpit-files" "net-tools" "dmidecode" "ipcalc" "bind-utils" "iotop" "zip" "yum-utils" "nano" "curl" "wget" "git" "dnf-automatic" "dnf-plugins-core" "util-linux" "htop" "iptraf-ng" "mc") 594 | TOTAL_PACKAGES=${#PACKAGE_LIST[@]} 595 | 596 | PIPE=$(mktemp -u) 597 | mkfifo "$PIPE" 598 | dialog --backtitle "Required Package Install" --title "Installing Required Packages" --gauge "Preparing to install packages..." 10 70 0 < "$PIPE" & 599 | exec 3>"$PIPE" 600 | COUNT=0 601 | for PACKAGE in "${PACKAGE_LIST[@]}"; do 602 | ((COUNT++)) 603 | PERCENT=$(( (COUNT * 100) / TOTAL_PACKAGES )) 604 | echo "$PERCENT" > "$PIPE" 605 | echo "XXX" > "$PIPE" 606 | echo "Installing: $PACKAGE" > "$PIPE" 607 | echo "XXX" > "$PIPE" 608 | dnf -y install "$PACKAGE" >/dev/null 2>&1 609 | done 610 | exec 3>&- 611 | rm -f "$PIPE" 612 | dialog --backtitle "Required Package Install" --title "Installation Complete" --infobox "All packages installed successfully!" 6 50 613 | sleep 3 614 | } 615 | #===========DETECT VIRT and INSTALL GUEST============= 616 | # Function to show a dialog infobox 617 | vm_detection() { 618 | show_info() { 619 | dialog --backtitle "Guest VM Detection and Installation" --title "$1" --infobox "$2" 5 60 620 | sleep 2 621 | } 622 | 623 | # Function to show a progress bar during installation 624 | show_progress() { 625 | ( 626 | echo "10"; sleep 1 627 | echo "40"; sleep 1 628 | echo "70"; sleep 1 629 | echo "100" 630 | ) | dialog --backtitle "Guest VM Detection and Installation" --title "$1" --gauge "$2" 7 60 0 631 | } 632 | 633 | # Detect virtualization platform 634 | HWKVM=$(dmidecode | grep -i -e manufacturer -e product -e vendor | grep KVM | cut -c16-) 635 | HWVMWARE=$(dmidecode | grep -i -e manufacturer -e product -e vendor | grep Manufacturer | grep "VMware, Inc." | cut -c16- | cut -d , -f1) 636 | 637 | show_info "Virtualization Check" "Checking for virtualization platform..." 638 | 639 | # Install guest agent for KVM 640 | if [ "$HWKVM" = "KVM" ]; then 641 | show_info "Platform Detected" "KVM platform detected.\nInstalling qemu-guest-agent..." 642 | show_progress "Installing qemu-guest-agent" "Installing guest tools for KVM..." 643 | dnf -y install qemu-guest-agent &>/dev/null 644 | fi 645 | 646 | # Install guest agent for VMware 647 | if [ "$HWVMWARE" = "VMware" ]; then 648 | show_info "Platform Detected" "VMware platform detected.\nInstalling open-vm-tools..." 649 | show_progress "Installing open-vm-tools" "Installing guest tools for VMware..." 650 | dnf -y install open-vm-tools &>/dev/null 651 | fi 652 | } 653 | #===========OPTIONAL DHCP INSTALL============= 654 | configure_dhcp_server() { 655 | local DIALOG="${DIALOG_BIN:-dialog}" 656 | local BACKTITLE="DHCP Server Install" 657 | local CHOSEN_BACKEND="" 658 | 659 | # ────────────────────────────── UI helpers ────────────────────────────── 660 | msgbox() { $DIALOG --backtitle "$BACKTITLE" --title "$1" --msgbox "$2" "${3:-8}" "${4:-72}"; } 661 | infobox(){ $DIALOG --backtitle "$BACKTITLE" --title "$1" --infobox "$2" "${3:-6}" "${4:-60}"; } 662 | 663 | # Require root + Rocky 9+ 664 | require_root(){ [[ $EUID -eq 0 ]] || { echo "Run as root." >&2; return 1; }; } 665 | require_rocky9plus(){ 666 | . /etc/os-release 2>/dev/null || true 667 | if [[ "${ID:-}" != "rocky" ]]; then 668 | msgbox "Unsupported OS" "This installer is limited to Rocky Linux 9+."; return 1 669 | fi 670 | local maj="${VERSION_ID%%.*}" 671 | if [[ -z "$maj" || "$maj" -lt 9 ]]; then 672 | msgbox "Unsupported Version" "Detected Rocky Linux ${VERSION_ID:-unknown}. This script supports Rocky Linux 9+ only." 673 | return 1 674 | fi 675 | return 0 676 | } 677 | 678 | # ─────────────────────── detection of installed backends ───────────────────── 679 | detect_isc_dhcp(){ [[ -f /etc/dhcp/dhcpd.conf ]] || rpm -q dhcp-server >/dev/null 2>&1; } 680 | detect_kea() { [[ -f /etc/kea/kea-dhcp4.conf ]] || rpm -q kea >/dev/null 2>&1; } 681 | 682 | # ───────────────────────── repo enable (Rocky 9+) ─────────────────────────── 683 | enable_repos_with_gauge() { 684 | # Rocky 9+: enable EPEL + CRB, then refresh metadata 685 | local log="/tmp/repo-setup.$(date +%s).log" 686 | local status="/tmp/repo-setup-status.$$" 687 | local msg="/tmp/repo-setup-phase.$$" 688 | : >"$log"; : >"$msg" 689 | trap 'rm -f "$status" "$msg"' RETURN 690 | 691 | ( 692 | rc=0 693 | { 694 | echo "Installing dnf-plugins-core..." >"$msg" 695 | dnf -y install dnf-plugins-core >>"$log" 2>&1 || rc=1 696 | 697 | echo "Installing epel-release..." >"$msg" 698 | dnf -y install epel-release >>"$log" 2>&1 || rc=1 699 | 700 | echo "Enabling CRB repository..." >"$msg" 701 | dnf config-manager --set-enabled crb >>"$log" 2>&1 || rc=1 702 | 703 | echo "Refreshing repository metadata (makecache --refresh)..." >"$msg" 704 | dnf -y makecache --refresh >>"$log" 2>&1 || rc=1 705 | } || rc=1 706 | echo "$rc" >"$status" 707 | ) & 708 | 709 | local pid=$! 710 | ( 711 | local PROGRESS=0 712 | while kill -0 "$pid" 2>/dev/null; do 713 | (( PROGRESS < 95 )) && PROGRESS=$(( PROGRESS + 5 )) 714 | echo "$PROGRESS" 715 | echo "XXX" 716 | echo -e "Enabling EPEL and CRB...\n$(cat "$msg" 2>/dev/null || echo "Working...")\n\nLog: $log" 717 | echo "XXX" 718 | sleep 0.5 719 | done 720 | echo "100" 721 | echo "XXX" 722 | echo -e "Repositories enabled and metadata refreshed.\n\nLog: $log" 723 | echo "XXX" 724 | ) | $DIALOG --backtitle "$BACKTITLE" --title "Repository Setup" --gauge "Preparing..." 10 70 0 725 | 726 | local rc=1 727 | [[ -f "$status" ]] && rc="$(cat "$status" 2>/dev/null || echo 1)" 728 | if [[ "$rc" -ne 0 ]]; then 729 | msgbox "Repository Setup Failed" "There was a problem enabling repositories.\n\nYou'll see the log next." 9 70 730 | $DIALOG --backtitle "$BACKTITLE" --title "Repo Setup Log" --textbox "$log" 22 100 731 | return 1 732 | fi 733 | return 0 734 | } 735 | 736 | # ────────────────────────── generic gauge runner ──────────────────────────── 737 | run_gauge_cmd() { 738 | local title="$1"; shift 739 | local log="/tmp/$(basename "$1")-install.$(date +%s).log" 740 | local status="/tmp/$(basename "$1")-status.$$" 741 | : > "$log" 742 | ( "$@" &> "$log"; echo $? > "$status" ) & local pid=$! 743 | set +e 744 | ( 745 | local pct=0 746 | while kill -0 "$pid" 2>/dev/null; do 747 | echo "$pct" 748 | echo "XXX" 749 | echo -e "Installing... Please wait.\nLog: $log" 750 | echo "XXX" 751 | sleep 0.3 752 | pct=$(( (pct + 2) % 97 )) 753 | done 754 | echo 100; echo "XXX"; echo "Finishing up..."; echo "XXX" 755 | ) | $DIALOG --backtitle "$BACKTITLE" --title "$title" --gauge "Preparing..." 10 70 0 756 | 757 | local rc=1 758 | [[ -f "$status" ]] && { rc="$(cat "$status" 2>/dev/null || echo 1)"; rm -f "$status"; } 759 | if [[ "$rc" -ne 0 ]]; then 760 | msgbox "Error" "$title failed.\n\nSee the next screen for details.\n\nLog: $log" 10 72 761 | $DIALOG --backtitle "$BACKTITLE" --title "Install log: $title" --textbox "$log" 22 100 762 | return "$rc" 763 | else 764 | infobox "Success" "$title completed.\n\nLog: $log" 8 70 765 | sleep 1 766 | fi 767 | } 768 | 769 | # ───────────────────────────── dnf installers ──────────────────────────────── 770 | install_isc_dhcp() { 771 | enable_repos_with_gauge || return 1 772 | run_gauge_cmd "Installing ISC DHCP (dhcp-server)" dnf -y install dhcp-server 773 | } 774 | install_kea() { 775 | enable_repos_with_gauge || return 1 776 | run_gauge_cmd "Installing Kea DHCP (kea)" dnf -y install kea 777 | } 778 | 779 | # ───────────────────── shared IP/CIDR + domain helpers ────────────────────── 780 | is_valid_ip(){ 781 | [[ $1 =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1 782 | local IFS=.; local o; for o in $1; do [[ $o -ge 0 && $o -le 255 ]] || return 1; done 783 | } 784 | ip_to_int(){ local IFS=.; read -r a b c d <<<"$1"; echo $(( (a<<24)+(b<<16)+(c<<8)+d )); } 785 | int_to_ip(){ local i=$1; printf "%d.%d.%d.%d" $(( (i>>24)&255 )) $(( (i>>16)&255 )) $(( (i>>8)&255 )) $(( i&255 )); } 786 | cidr_to_netmask(){ local c=$1; local m=$(( 0xFFFFFFFF << (32-c) & 0xFFFFFFFF )); int_to_ip "$m"; } 787 | netmask_to_cidr(){ 788 | local ip=$1; is_valid_ip "$ip" || { echo -1; return; } 789 | local n=$(ip_to_int "$ip") c=0 saw_zero=0 790 | for ((i=31;i>=0;i--)); do 791 | if (( (n>>i)&1 )); then (( saw_zero )) && { echo -1; return; }; ((c++)) 792 | else saw_zero=1 793 | fi 794 | done 795 | echo "$c" 796 | } 797 | network_from_ip_cidr(){ local ip=$1 c=$2; local m=$(( 0xFFFFFFFF << (32-c) & 0xFFFFFFFF )); int_to_ip $(( $(ip_to_int "$ip") & m )); } 798 | broadcast_from_ip_cidr(){ local ip=$1 c=$2; local m=$(( 0xFFFFFFFF << (32-c) & 0xFFFFFFFF )); int_to_ip $(( $(ip_to_int "$ip") | (~m & 0xFFFFFFFF) )); } 799 | ip_in_cidr(){ 800 | local ip=$1 net=$2 c=$3 801 | local m=$(( 0xFFFFFFFF << (32-c) & 0xFFFFFFFF )) 802 | (( ( $(ip_to_int "$ip") & m ) == ( $(ip_to_int "$net") & m ) )) 803 | } 804 | is_valid_domain(){ 805 | local d="$1" 806 | [[ -n "$d" ]] || return 1 807 | [[ "$d" =~ ^([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$ ]] 808 | } 809 | 810 | # ───────────────────────────── dhcpd setup flow ───────────────────────────── 811 | dhcpd_setup() { 812 | local iface inet4_line INET4 DHCPCIDR NET_DETECTED NETMASK_DETECTED 813 | iface=$(nmcli -t -f DEVICE,STATE device status | awk -F: '$2=="connected"{print $1; exit}') 814 | [[ -z "$iface" ]] && { msgbox "DHCPD Setup" "No active interface found."; return 1; } 815 | inet4_line=$(nmcli -g IP4.ADDRESS device show "$iface" | head -n 1) 816 | [[ -z "$inet4_line" ]] && { msgbox "DHCPD Setup" "No IPv4 address found on $iface."; return 1; } 817 | 818 | INET4=${inet4_line%/*} 819 | DHCPCIDR=${inet4_line#*/} 820 | NET_DETECTED=$(network_from_ip_cidr "$INET4" "$DHCPCIDR") 821 | NETMASK_DETECTED=$(cidr_to_netmask "$DHCPCIDR") 822 | 823 | local DHCPBEGIP DHCPENDIP DHCPNETMASK DHCPDEFGW SUBNETDESC DOM_SUFFIX SEARCH_DOMAIN 824 | local DEF_SUFFIX="$(hostname -d 2>/dev/null || true)" 825 | local DEF_SEARCH="${DEF_SUFFIX}" 826 | 827 | while true; do 828 | # Range start 829 | while true; do 830 | DHCPBEGIP=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 831 | "Enter beginning IP of DHCP lease range (in $NET_DETECTED/$DHCPCIDR):" 8 78) 832 | [[ -n "$DHCPBEGIP" ]] && is_valid_ip "$DHCPBEGIP" && ip_in_cidr "$DHCPBEGIP" "$NET_DETECTED" "$DHCPCIDR" && break 833 | msgbox "Invalid Input" "Start IP must be a valid IPv4 within $NET_DETECTED/$DHCPCIDR." 834 | done 835 | # Range end 836 | while true; do 837 | DHCPENDIP=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 838 | "Enter ending IP of DHCP lease range (in $NET_DETECTED/$DHCPCIDR):" 8 78) 839 | [[ -n "$DHCPENDIP" ]] && is_valid_ip "$DHCPENDIP" && ip_in_cidr "$DHCPENDIP" "$NET_DETECTED" "$DHCPCIDR" && \ 840 | (( $(ip_to_int "$DHCPBEGIP") <= $(ip_to_int "$DHCPENDIP") )) && break 841 | msgbox "Invalid Input" "End IP must be valid, in $NET_DETECTED/$DHCPCIDR, and ≥ start IP." 842 | done 843 | # Netmask (must match detected) 844 | while true; do 845 | DHCPNETMASK=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 846 | "Enter netmask for clients (must match detected $NETMASK_DETECTED):" 8 78 "$NETMASK_DETECTED") 847 | local nm_cidr; nm_cidr=$(netmask_to_cidr "$DHCPNETMASK") 848 | [[ "$nm_cidr" -eq "$DHCPCIDR" ]] && break 849 | msgbox "Invalid Netmask" "Netmask must be contiguous and equal to $NETMASK_DETECTED." 850 | done 851 | # Default gateway 852 | while true; do 853 | DHCPDEFGW=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 854 | "Enter default gateway for clients (in $NET_DETECTED/$DHCPCIDR):" 8 78) 855 | [[ -n "$DHCPDEFGW" ]] && is_valid_ip "$DHCPDEFGW" && ip_in_cidr "$DHCPDEFGW" "$NET_DETECTED" "$DHCPCIDR" && break 856 | msgbox "Invalid Gateway" "Gateway must be a valid IPv4 within $NET_DETECTED/$DHCPCIDR." 857 | done 858 | # Domain suffix (option 15) 859 | while true; do 860 | DOM_SUFFIX=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 861 | "Enter domain suffix (for 'option domain-name'):" 8 78 "${DEF_SUFFIX}") 862 | is_valid_domain "$DOM_SUFFIX" && break 863 | msgbox "Invalid Domain" "Please enter a valid domain suffix like 'ad.example.com'." 864 | done 865 | # Search domain(s) (option 119) 866 | while true; do 867 | SEARCH_DOMAIN=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 868 | "Enter search domain(s) for clients (comma-separated if multiple):" 9 78 "${DEF_SEARCH}") 869 | local ok=1 IFS=, item 870 | for item in $SEARCH_DOMAIN; do 871 | item="${item// /}" ; is_valid_domain "$item" || { ok=0; break; } 872 | done 873 | [[ $ok -eq 1 ]] && break 874 | msgbox "Invalid Search Domain" "One or more domains are invalid. Use comma-separated FQDNs." 875 | done 876 | 877 | SUBNETDESC=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 878 | "Enter a friendly name/description for this subnet:" 8 78) 879 | 880 | $DIALOG --backtitle "$BACKTITLE" --title "DHCP Configuration Summary" --yesno \ 881 | "Interface: $iface 882 | Interface IP: $INET4/$DHCPCIDR 883 | Subnet: $NET_DETECTED 884 | Netmask: $DHCPNETMASK 885 | Range: $DHCPBEGIP → $DHCPENDIP 886 | Gateway: $DHCPDEFGW 887 | Domain: $DOM_SUFFIX 888 | Search: $SEARCH_DOMAIN 889 | Description: $SUBNETDESC 890 | 891 | Are these settings correct?" 18 72 && break 892 | done 893 | 894 | infobox "DHCPD Setup" "Creating /etc/dhcp/dhcpd.conf..." 895 | mkdir -p /etc/dhcp 896 | mv /etc/dhcp/dhcpd.conf /etc/dhcp/dhcpd.conf.orig 2>/dev/null || true 897 | cat </etc/dhcp/dhcpd.conf 898 | authoritative; 899 | allow unknown-clients; 900 | default-lease-time 600; 901 | max-lease-time 7200; 902 | 903 | option ntp-servers ${INET4}; 904 | option time-servers ${INET4}; 905 | option domain-name-servers ${INET4}; 906 | option domain-name "${DOM_SUFFIX}"; 907 | option domain-search "${SEARCH_DOMAIN}"; 908 | 909 | # ${SUBNETDESC} 910 | subnet ${NET_DETECTED} netmask ${DHCPNETMASK} { 911 | range ${DHCPBEGIP} ${DHCPENDIP}; 912 | option subnet-mask ${DHCPNETMASK}; 913 | option routers ${DHCPDEFGW}; 914 | } 915 | EOF 916 | } 917 | 918 | # ───────────────────────────── Kea setup flow ─────────────────────────────── 919 | kea_dhcp_setup() { 920 | local KEA_CONF="/etc/kea/kea-dhcp4.conf" 921 | mkdir -p /etc/kea; touch "$KEA_CONF" 922 | 923 | local iface inet4_line INET4 CIDR NETMASK NETWORK BROADCAST 924 | iface=$(nmcli -t -f DEVICE,STATE device status | awk -F: '$2=="connected"{print $1; exit}') 925 | [[ -z "$iface" ]] && { msgbox "KEA DHCP Setup" "No active interface found."; return 1; } 926 | inet4_line=$(nmcli -g IP4.ADDRESS device show "$iface" | head -n 1) 927 | [[ -z "$inet4_line" ]] && { msgbox "KEA DHCP Setup" "No IPv4 address found on $iface."; return 1; } 928 | 929 | INET4=${inet4_line%/*} 930 | CIDR=${inet4_line#*/} 931 | NETWORK=$(network_from_ip_cidr "$INET4" "$CIDR") 932 | NETMASK=$(cidr_to_netmask "$CIDR") 933 | BROADCAST=$(broadcast_from_ip_cidr "$INET4" "$CIDR") 934 | 935 | local POOL_START POOL_END ROUTER DOM_SUFFIX SEARCH_DOMAIN DNS_SERVERS SUBNET_DESC 936 | local DEF_SUFFIX="$(hostname -d 2>/dev/null || true)" 937 | local DEF_SEARCH="${DEF_SUFFIX}" 938 | 939 | while true; do 940 | # pool start 941 | while true; do 942 | POOL_START=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 943 | "Enter beginning IP of DHCP lease range (in $NETWORK/$CIDR):" 8 78) 944 | [[ -n "$POOL_START" ]] && is_valid_ip "$POOL_START" && ip_in_cidr "$POOL_START" "$NETWORK" "$CIDR" && break 945 | msgbox "Invalid Input" "Start IP must be a valid IPv4 within $NETWORK/$CIDR." 946 | done 947 | # pool end 948 | while true; do 949 | POOL_END=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 950 | "Enter ending IP of DHCP lease range:" 8 78) 951 | [[ -n "$POOL_END" ]] && is_valid_ip "$POOL_END" && ip_in_cidr "$POOL_END" "$NETWORK" "$CIDR" && \ 952 | (( $(ip_to_int "$POOL_START") <= $(ip_to_int "$POOL_END") )) && break 953 | msgbox "Invalid Input" "End IP must be valid, in $NETWORK/$CIDR, and ≥ start IP." 954 | done 955 | # gateway 956 | while true; do 957 | ROUTER=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 958 | "Enter default gateway for clients (in $NETWORK/$CIDR):" 8 78) 959 | [[ -n "$ROUTER" ]] && is_valid_ip "$ROUTER" && ip_in_cidr "$ROUTER" "$NETWORK" "$CIDR" && break 960 | msgbox "Invalid Gateway" "Gateway must be a valid IPv4 within $NETWORK/$CIDR." 961 | done 962 | # domains 963 | while true; do 964 | DOM_SUFFIX=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 965 | "Enter domain suffix (for 'domain-name'):" 8 78 "${DEF_SUFFIX}") 966 | is_valid_domain "$DOM_SUFFIX" && break 967 | msgbox "Invalid Domain" "Please enter a valid domain suffix like 'ad.example.com'." 968 | done 969 | while true; do 970 | SEARCH_DOMAIN=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 971 | "Enter search domain(s) for clients (comma-separated if multiple):" 9 78 "${DEF_SEARCH}") 972 | local ok=1 IFS=, item 973 | for item in $SEARCH_DOMAIN; do 974 | item="${item// /}" ; is_valid_domain "$item" || { ok=0; break; } 975 | done 976 | [[ $ok -eq 1 ]] && break 977 | msgbox "Invalid Search Domain" "One or more domains are invalid. Use comma-separated FQDNs." 978 | done 979 | DNS_SERVERS=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 980 | "Enter DNS servers (comma separated, or leave blank to use $INET4):" 8 78 "$INET4") 981 | SUBNET_DESC=$($DIALOG --backtitle "$BACKTITLE" --stdout --inputbox \ 982 | "Enter a friendly name/description for this subnet:" 8 78) 983 | 984 | $DIALOG --backtitle "$BACKTITLE" --title "KEA DHCP Settings Review" --yesno \ 985 | "Interface: $iface 986 | Interface IP: $INET4/$CIDR 987 | Subnet: $NETWORK/$CIDR 988 | Broadcast: $BROADCAST 989 | Range: $POOL_START → $POOL_END 990 | Gateway: $ROUTER 991 | DNS: $DNS_SERVERS 992 | Domain: $DOM_SUFFIX 993 | Search: $SEARCH_DOMAIN 994 | Description: $SUBNET_DESC 995 | 996 | Are these settings correct?" 20 72 && break 997 | done 998 | 999 | infobox "KEA DHCP Setup" "Creating /etc/kea/kea-dhcp4.conf..." 1000 | cat < "$KEA_CONF" 1001 | { 1002 | "Dhcp4": { 1003 | "interfaces-config": { 1004 | "interfaces": [ "$iface" ] 1005 | }, 1006 | "lease-database": { 1007 | "type": "memfile", 1008 | "persist": true, 1009 | "name": "/var/lib/kea/kea-leases4.csv" 1010 | }, 1011 | "subnet4": [ 1012 | { 1013 | "id": 1, 1014 | "subnet": "$NETWORK/$CIDR", 1015 | "interface": "$iface", 1016 | "comment": "$SUBNET_DESC", 1017 | "pools": [ { "pool": "$POOL_START - $POOL_END" } ], 1018 | "option-data": [ 1019 | { "name": "routers", "data": "$ROUTER" }, 1020 | { "name": "domain-name-servers", "data": "$DNS_SERVERS" }, 1021 | { "name": "ntp-servers", "data": "$DNS_SERVERS" }, 1022 | { "name": "domain-name", "data": "$DOM_SUFFIX" }, 1023 | { "name": "domain-search", "data": "$SEARCH_DOMAIN" } 1024 | ] 1025 | } 1026 | ], 1027 | "authoritative": true 1028 | } 1029 | } 1030 | EOF 1031 | chown root:kea "$KEA_CONF" 1032 | chmod 640 "$KEA_CONF" 1033 | restorecon "$KEA_CONF" 2>/dev/null || true 1034 | } 1035 | 1036 | # ────────────────────────────── preflight checks ───────────────────────────── 1037 | require_root || return 1 1038 | require_rocky9plus || return 1 1039 | command -v "$DIALOG" >/dev/null 2>&1 || { echo "dialog not found. dnf -y install dialog" >&2; return 1; } 1040 | command -v nmcli >/dev/null 2>&1 || { echo "nmcli not found. dnf -y install NetworkManager" >&2; return 1; } 1041 | 1042 | # ────────────────────────────── user selection ─────────────────────────────── 1043 | $DIALOG --backtitle "$BACKTITLE" --title "DHCP Installation" --yesno \ 1044 | "Would you like to install a DHCP service on this system? 1045 | 1046 | You will be able to choose between ISC DHCP or Kea DHCP in the next step." 9 80 || { clear; return 0; } 1047 | 1048 | local isc_installed="not installed" kea_installed="not installed" 1049 | detect_isc_dhcp && isc_installed="installed" 1050 | detect_kea && kea_installed="installed" 1051 | 1052 | local default="kea" 1053 | detect_kea && default="kea" 1054 | { detect_isc_dhcp && ! detect_kea; } && default="isc" 1055 | 1056 | local kea_desc="Install/upgrade Kea DHCP (recommended)" 1057 | [[ $kea_installed == "installed" ]] && kea_desc+=" [installed]" 1058 | local isc_desc="Install/upgrade ISC DHCP (dhcp-server)" 1059 | [[ $isc_installed == "installed" ]] && isc_desc+=" [installed]" 1060 | 1061 | local KEA_ON="OFF" ISC_ON="OFF" 1062 | [[ $default == "kea" ]] && KEA_ON="ON" || ISC_ON="ON" 1063 | 1064 | local choice 1065 | choice=$($DIALOG --backtitle "$BACKTITLE" --stdout --title "DHCP Installer" --radiolist \ 1066 | "Select which DHCP server to install or upgrade. 1067 | 1068 | Detected: 1069 | - ISC DHCP: $isc_installed 1070 | - Kea DHCP: $kea_installed" \ 1071 | 14 76 2 \ 1072 | kea "$kea_desc" $KEA_ON \ 1073 | isc "$isc_desc" $ISC_ON) 1074 | 1075 | case "${choice:-}" in 1076 | kea) install_kea && CHOSEN_BACKEND="kea" ;; 1077 | isc) install_isc_dhcp && CHOSEN_BACKEND="isc" ;; 1078 | *) clear; return 0 ;; 1079 | esac 1080 | 1081 | # ────────────────── run setup, enable service, open firewall ──────────────── 1082 | local CONF SVC 1083 | if [[ "$CHOSEN_BACKEND" == "kea" ]]; then 1084 | kea_dhcp_setup 1085 | systemctl enable --now kea-dhcp4 >/dev/null 2>&1 || true 1086 | CONF="/etc/kea/kea-dhcp4.conf"; SVC="kea-dhcp4" 1087 | else 1088 | dhcpd_setup 1089 | systemctl enable --now dhcpd >/dev/null 2>&1 || true 1090 | CONF="/etc/dhcp/dhcpd.conf"; SVC="dhcpd" 1091 | fi 1092 | 1093 | firewall-cmd --zone=public --add-service=dhcp --permanent >/dev/null 2>&1 || true 1094 | firewall-cmd --reload >/dev/null 2>&1 || true 1095 | 1096 | # ────────────────────────────── final validation ───────────────────────────── 1097 | local ok_conf=0 ok_svc=0 1098 | [[ -s "$CONF" ]] && ok_conf=1 1099 | if systemctl is-active --quiet "$SVC"; then ok_svc=1; fi 1100 | 1101 | if [[ $ok_conf -eq 1 && $ok_svc -eq 1 ]]; then 1102 | msgbox "Success" "$SVC is running and $CONF configured successfully." 1103 | clear; return 0 1104 | fi 1105 | 1106 | # Syntax hint on failure 1107 | local syntax="" 1108 | if [[ "$SVC" == "kea-dhcp4" && -f "$CONF" ]]; then 1109 | syntax="$(kea-dhcp4 -t "$CONF" 2>&1 || true)" 1110 | elif [[ "$SVC" == "dhcpd" && -f "$CONF" ]]; then 1111 | syntax="$(dhcpd -t -cf "$CONF" 2>&1 || true)" 1112 | fi 1113 | 1114 | local err="Validation failed: 1115 | - Config file present: $( [[ $ok_conf -eq 1 ]] && echo YES || echo NO ) 1116 | - Service active: $( [[ $ok_svc -eq 1 ]] && echo YES || echo NO ) 1117 | 1118 | $( [[ -n "$syntax" ]] && echo -e "Syntax check output:\n\n$syntax" || echo "No syntax details available.")" 1119 | 1120 | msgbox "DHCP Validation" "$err" 18 90 1121 | clear 1122 | return 1 1123 | } 1124 | #===========SET SELINUX============= 1125 | configure_selinux() { 1126 | dialog --backtitle "SELinux setsbool Configuration" --title "SELinux Configuration" --infobox "Applying SELinux settings for Samba..." 5 50 1127 | sleep 2 1128 | setsebool -P samba_create_home_dirs=on \ 1129 | samba_domain_controller=on \ 1130 | samba_enable_home_dirs=on \ 1131 | samba_portmapper=on \ 1132 | use_samba_home_dirs=on 1133 | sleep2 1134 | } 1135 | #===========CONFGIURE FIREWALL============= 1136 | configure_firewall() { 1137 | dialog --backtitle "Firewall Services Configuration" --title "Firewall Configuration" --infobox "Applying firewall rules for AD services..." 5 60 1138 | firewall-cmd --permanent --add-service=samba-dc >/dev/null 1139 | firewall-cmd --permanent --add-service=ldaps >/dev/null 1140 | firewall-cmd --permanent --add-service=ntp >/dev/null 1141 | 1142 | firewall-cmd --reload >/dev/null 1143 | systemctl restart firewalld 1144 | sleep 2 1145 | # Extract enabled services 1146 | FIREWALL_SERVICES=$(firewall-cmd --list-services 2>/dev/null) 1147 | 1148 | dialog --backtitle "Firewall Services Configuration" --title "Firewall Status" --infobox "These services are now open on the server:\n\n$FIREWALL_SERVICES\n\n" 12 60 1149 | sleep 4 1150 | } 1151 | #===========PROVISION SAMBA WITH MOCK============= 1152 | configure_samba_provisioning() { 1153 | OSVER=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release) 1154 | MAJOROS=$(cut -d. -f1 <<< "$OSVER") 1155 | MINOROS=$(cut -d. -f2 <<< "$OSVER") 1156 | 1157 | DOMAIN=$(hostname | sed 's/^[^.:]*[.:]//' | sed -e 's/\(.*\)/\U\1/') 1158 | ADDOMAIN=$(hostname | sed 's/^[^.:]*[.:]//' | cut -d. -f1 | sed -e 's/\(.*\)/\U\1/') 1159 | 1160 | dialog --backtitle "Samba Build --dc with Mock" --title "Samba Source Build" --infobox \ 1161 | "Downloading and compiling Samba from source using 'mock'\n\nThis may take up to 30 minutes\n\nThe Installer will Continue Shortly " 10 80 1162 | sleep 4 1163 | 1164 | dnf download samba --source 1165 | if ! ls /root/samba-*.rpm 1>/dev/null 2>&1; then 1166 | dialog --backtitle "Samba Build --dc with Mock" --msgbox "Samba source RPM failed to download. Check your network." 8 50 1167 | return 1 1168 | fi 1169 | 1170 | MOCKSMBVER=$(dnf provides samba | grep samba | sed '2,4d' | cut -d: -f1 | cut -dx -f1) 1171 | MOCKCMD="mock -r rocky-${MAJOROS}-x86_64 --enablerepo=devel --define 'dist .el${MAJOROS}_${MINOROS}.dc' --with dc ${MOCKSMBVER}src.rpm" 1172 | 1173 | TMPLOG=$(mktemp) 1174 | PIPE=$(mktemp -u) 1175 | mkfifo "$PIPE" 1176 | 1177 | # Launch mock inside a pseudo-terminal using `script` 1178 | script -q -c "$MOCKCMD" /dev/null > "$PIPE" 2>&1 & 1179 | MOCKPID=$! 1180 | 1181 | dialog --backtitle "Samba Build --dc with Mock" --title "Building Samba with Mock (Live)" --programbox 25 150 < "$PIPE" 1182 | 1183 | wait $MOCKPID 1184 | rm -f "$PIPE" 1185 | 1186 | if ! ls /var/lib/mock/rocky-${MAJOROS}-x86_64/result/*.rpm &>/dev/null; then 1187 | dialog --backtitle "Samba Build --dc with Mock" --title "Mock Build Failed" --msgbox "Build failed. Check logs manually." 8 60 1188 | return 1 1189 | fi 1190 | 1191 | mkdir -p /root/.samba 1192 | cp /var/lib/mock/rocky-${MAJOROS}-x86_64/result/*.rpm /root/.samba 1193 | createrepo /root/.samba 1194 | dnf config-manager --add-repo /root/.samba 1195 | dnf -y install --nogpgcheck samba-dc samba-client krb5-workstation samba \ 1196 | --repofrompath=samba,/root/.samba \ 1197 | --enablerepo=samba >/dev/null 1198 | 1199 | mv -f /etc/samba/smb.conf /etc/samba/smb.bak.orig 1200 | 1201 | output=$(samba-tool domain provision \ 1202 | --realm="$DOMAIN" \ 1203 | --domain="$ADDOMAIN" \ 1204 | --adminpass="$ADMINPASS" 2>&1) 1205 | 1206 | echo "$output" 1207 | 1208 | if echo "$output" | grep -q "ERROR"; then 1209 | dialog --backtitle "Samba Build --dc with Mock" --msgbox "Provisioning failed. Check output." 8 60 1210 | return 1 1211 | fi 1212 | 1213 | dialog --backtitle "Samba Build --dc with Mock" --msgbox "Samba AD Domain provisioned successfully." 7 50 1214 | return 0 1215 | } 1216 | 1217 | #===========CREATE KDC============= 1218 | create_kdc_conf() { 1219 | KRB5_SRC="/var/lib/samba/private/krb5.conf" 1220 | KRB5_DEST="/etc/krb5.conf" 1221 | 1222 | if [[ ! -f "$KRB5_SRC" ]]; then 1223 | dialog --backtitle "Configure Kerberos (KDC)" --title "KDC Error" --msgbox "Kerberos configuration file not found at $KRB5_SRC.\nProvisioning may have failed. Exiting..." 8 60 1224 | exit 1 1225 | fi 1226 | 1227 | dialog --backtitle "Configure Kerberos (KDC)" --title "Creating KDC" --infobox "Copying Kerberos configuration to $KRB5_DEST..." 5 60 1228 | sleep 2 1229 | \cp -rf "$KRB5_SRC" "$KRB5_DEST" 1230 | 1231 | if [[ $? -eq 0 ]]; then 1232 | dialog --backtitle "Configure Kerberos (KDC)" --infobox "Kerberos configuration successfully copied." 6 50 1233 | sleep 2 1234 | else 1235 | dialog --backtitle "Configure Kerberos (KDC)" --msgbox "Failed to copy Kerberos configuration. Please check permissions." 6 60 1236 | sleep 2 1237 | fi 1238 | } 1239 | 1240 | #===========SET DNS to ITSELF============= 1241 | set_local_dns_resolver() { 1242 | IP=$(hostname -I | awk '{print $1}') # Get first IP address 1243 | INTERFACE=$(nmcli -t -f DEVICE,STATE dev | awk -F: '$2=="connected" {print $1}' | head -n1) 1244 | 1245 | if [[ -z "$IP" || -z "$INTERFACE" ]]; then 1246 | dialog --backtitle "Configure Local Resolver" --msgbox "Failed to detect IP or active interface. Cannot set DNS." 8 50 1247 | return 1 1248 | fi 1249 | 1250 | dialog --backtitle "Configure Local Resolver" --title "Setting DNS Resolver" --infobox \ 1251 | "Configuring ${INTERFACE} to use ${IP} as its primary DNS resolver..." 5 80 1252 | sleep 2 1253 | 1254 | nmcli con mod "$INTERFACE" ipv4.dns "$IP" 1255 | systemctl restart NetworkManager 1256 | 1257 | if [[ $? -eq 0 ]]; then 1258 | dialog --backtitle "Configure Local Resolver" --infobox "DNS resolver successfully set to ${IP} on ${INTERFACE}." 6 80 1259 | sleep 2 1260 | else 1261 | dialog --backtitle "Configure Local Resolver" --msgbox "Failed to apply DNS resolver configuration." 6 50 1262 | return 1 1263 | fi 1264 | } 1265 | 1266 | #===========ADD FREERADIUS SUPPORT============= 1267 | add_freeradius_support() { 1268 | SMB_CONF="/etc/samba/smb.conf" 1269 | 1270 | if [[ ! -f "$SMB_CONF" ]]; then 1271 | dialog --title "FreeRADIUS Error" --msgbox "$SMB_CONF not found. Please verify Samba is installed." 7 60 1272 | exit 1 1273 | fi 1274 | 1275 | dialog --backtitle "Configuring smb.conf" --title "FreeRADIUS Integration" --infobox "Adding default FreeRADIUS support to smb.conf..." 5 60 1276 | sleep 2 1277 | 1278 | sed -i '8i \ \ #Added for FreeRADIUS Support' "$SMB_CONF" 1279 | sed -i '9i \ \ ntlm auth = mschapv2-and-ntlmv2-only' "$SMB_CONF" 1280 | sed -i '10i \ \\#ldap server require strong auth = no #UNCOMMENT THIS IF YOU NEED PLAIN LDAP BIND (non-TLS)' "$SMB_CONF" 1281 | 1282 | dialog --backtitle "Configuring smb.conf" --infobox "FreeRADIUS options successfully added to smb.conf." 6 60 1283 | sleep 2 1284 | } 1285 | #===========ADD DNF-SMB-MON CRON JOB============= 1286 | add_dnf_smb_mon_cron() { 1287 | MONITOR_SCRIPT="/root/ADDCInstaller/dnf-smb-mon" 1288 | DEST_BIN="/usr/bin/dnf-smb-mon" 1289 | 1290 | if [[ ! -f "$MONITOR_SCRIPT" ]]; then 1291 | dialog --backtitle "Configuring Repository Monitoring" --title "Cron Job Error" --msgbox "$MONITOR_SCRIPT not found. Cannot configure cron job." 7 80 1292 | exit 1 1293 | fi 1294 | 1295 | dialog --backtitle "Configuring Repository Monitoring" --title "Configuring Repo Monitor" --infobox "Installing dnf-smb-mon and setting up cron job..." 5 80 1296 | sleep 2 1297 | 1298 | touch /var/log/dnf-smb-mon.log 1299 | chmod 700 "$MONITOR_SCRIPT" 1300 | \cp "$MONITOR_SCRIPT" "$DEST_BIN" 1301 | 1302 | ( 1303 | crontab -l 2>/dev/null 1304 | echo "0 */6 * * * $DEST_BIN" 1305 | ) | sort -u | crontab - 1306 | 1307 | systemctl restart crond 1308 | dialog --backtitle "Configuring Repository Monitoring" --infobox "dnf-smb-mon installed and cron job scheduled every 6 hours." 6 70 1309 | sleep 2 1310 | } 1311 | #===========COPY SAMBA-DNF-PKG-UPDATE============= 1312 | copy_samba_dnf_pkg_update() { 1313 | UPDATE_SCRIPT="/root/ADDCInstaller/samba-dnf-pkg-update" 1314 | DEST_BIN="/usr/bin/samba-dnf-pkg-update" 1315 | 1316 | if [[ ! -f "$UPDATE_SCRIPT" ]]; then 1317 | dialog --backtitle "Configuring Samba DNF Package Updater" --title "Copy Error" --msgbox "$UPDATE_SCRIPT not found. Cannot continue." 7 60 1318 | exit 1 1319 | fi 1320 | 1321 | dialog --backtitle "Configuring Samba DNF Package Updater" --title "Samba DNF Update" --infobox "Installing samba-dnf-pkg-update script..." 5 60 1322 | sleep 2 1323 | 1324 | chmod 700 "$UPDATE_SCRIPT" 1325 | \cp "$UPDATE_SCRIPT" "$DEST_BIN" 1326 | 1327 | dialog --backtitle "Configuring Samba DNF Package Updater" --infobox "samba-dnf-pkg-update successfully installed to /usr/bin." 6 60 1328 | sleep 2 1329 | } 1330 | #===========ENABLE AND CHECK SAMBA DC SERVICE============= 1331 | enable_and_check_samba_service() { 1332 | SERVICE_NAME="samba" 1333 | 1334 | # Check if the service exists 1335 | if ! systemctl list-unit-files | grep -q "^${SERVICE_NAME}.service"; then 1336 | dialog --title "Service Error" --msgbox "The service '${SERVICE_NAME}' was not found on this system.\nPlease ensure Samba is installed." 8 60 1337 | exit 1 1338 | fi 1339 | 1340 | # Enable and start the service 1341 | dialog --backtitle "Validating Samba Service" --title "Samba Service" --infobox "Enabling and starting the Samba service..." 5 60 1342 | sleep 2 1343 | systemctl enable "$SERVICE_NAME" --now 1344 | 1345 | # Check service status 1346 | samba_status=$(systemctl is-active "$SERVICE_NAME") 1347 | if [[ "$samba_status" = "active" ]]; then 1348 | dialog --backtitle "Validating Samba Service" --title "Samba Service" --infobox "Samba service is running." 5 40 1349 | else 1350 | dialog --backtitle "Validating Samba Service" --title "Samba Error" --msgbox "Samba service is NOT running.\nStatus: $samba_status" 7 50 1351 | exit 1 1352 | fi 1353 | sleep 2 1354 | } 1355 | #===========UPDATE ISSUE FILE============ 1356 | update_issue_file() { 1357 | rm -rf /etc/issue 1358 | touch /etc/issue 1359 | cat </etc/issue 1360 | \S 1361 | Kernel \r on an \m 1362 | Hostname: \n 1363 | IP Address: \4 1364 | EOF 1365 | } 1366 | #===========SAMBA LDAPS CERT SETUP============= 1367 | setup_samba_ldaps_cert() { 1368 | TLS_DIR="/var/lib/samba/private/tls" 1369 | CERT="$TLS_DIR/samba.crt" 1370 | KEY="$TLS_DIR/samba.key" 1371 | CA="$TLS_DIR/ca.crt" 1372 | SMB_CONF="/etc/samba/smb.conf" 1373 | LOG="/var/log/samba-ldap-cert-setup.log" 1374 | 1375 | FQDN=$(hostname -f) 1376 | IPADDR=$(hostname -I | awk '{print $1}') 1377 | 1378 | mkdir -p "$TLS_DIR" 1379 | 1380 | dialog --backtitle "Configuring TLS" --title "Samba TLS Setup" --infobox "Generating Samba LDAPS certificate for $FQDN with IP $IPADDR..." 6 80 1381 | sleep 2 1382 | 1383 | SAN_CONF=$(mktemp) 1384 | cat > "$SAN_CONF" <> "$LOG" 2>&1 1408 | rm -f "$SAN_CONF" 1409 | 1410 | if [[ -f "$CERT" && -f "$KEY" ]]; then 1411 | cp "$CERT" "$CA" 1412 | chmod 600 "$CERT" "$CA" "$KEY" 1413 | dialog --backtitle "Configuring TLS" --infobox "Certificate and key successfully created at $TLS_DIR" 6 60 1414 | sleep 2 1415 | else 1416 | dialog --backtitle "Configuring TLS" --title "Certificate Error" --msgbox "Certificate or key was not created. Check $LOG for errors." 7 60 1417 | return 1 1418 | fi 1419 | 1420 | if ! grep -q "tls keyfile" "$SMB_CONF"; then 1421 | dialog --backtitle "Configuring TLS" --title "Updating smb.conf" --infobox "Inserting TLS configuration into smb.conf..." 6 60 1422 | sleep 2 1423 | 1424 | awk -v keyfile="$KEY" -v certfile="$CERT" -v cafile="$CA" ' 1425 | BEGIN { inserted=0 } 1426 | /^\[global\]/ { print; in_global=1; next } 1427 | in_global && /^\[/ { 1428 | if (!inserted) { 1429 | print " # TLS configuration for LDAPS/StartTLS" 1430 | print " tls enabled = yes" 1431 | print " tls keyfile = " keyfile 1432 | print " tls certfile = " certfile 1433 | print " tls cafile = " cafile 1434 | print " ldap server require strong auth = yes" 1435 | inserted = 1 1436 | } 1437 | in_global=0 1438 | } 1439 | { print } 1440 | END { 1441 | if (!inserted) { 1442 | print "[global]" 1443 | print " tls enabled = yes" 1444 | print " tls keyfile = " keyfile 1445 | print " tls certfile = " certfile 1446 | print " tls cafile = " cafile 1447 | print " ldap server require strong auth = yes" 1448 | } 1449 | } 1450 | ' "$SMB_CONF" > "$SMB_CONF.new" && mv "$SMB_CONF.new" "$SMB_CONF" 1451 | fi 1452 | 1453 | dialog --backtitle "Configuring TLS" --title "Restarting Samba" --infobox "Restarting Samba to apply certificate configuration..." 6 60 1454 | sleep 2 1455 | systemctl restart samba 1456 | 1457 | # Validate that samba restarted successfully 1458 | if systemctl is-active --quiet samba; then 1459 | dialog --backtitle "Configuring TLS" --infobox "Samba restarted and is running." 6 50 1460 | sleep 2 1461 | else 1462 | dialog --backtitle "Configuring TLS" --title "Samba Error" --msgbox "Samba failed to restart. Please check the service status manually." 7 60 1463 | return 1 1464 | fi 1465 | } 1466 | #===========LDAP BIND AND TEST============= 1467 | test_ldap_secure_connection() { 1468 | LOG="/var/log/samba-ldap-cert-setup.log" 1469 | IPADDR=$(hostname -I | awk '{print $1}') 1470 | 1471 | LDAP_ADMIN_DN=$(samba-tool user show Administrator | awk -F': ' '/^dn: / {print $2}') 1472 | if [[ -z "$LDAP_ADMIN_DN" ]]; then 1473 | dialog --backtitle "Samba Validation" --title "LDAP Test Error" --msgbox "Failed to retrieve Administrator DN from samba-tool output." 7 60 1474 | return 1 1475 | fi 1476 | 1477 | LDAP_BASEDN=$(echo "$LDAP_ADMIN_DN" | grep -oE 'DC=[^,]+(,DC=[^,]+)*') 1478 | 1479 | dialog --backtitle "Samba Validation" --infobox "Testing StartTLS on port 389..." 5 50 1480 | sleep 2 1481 | LDAPTLS_REQCERT=never \ 1482 | ldapsearch -x -H ldap://$IPADDR -ZZ \ 1483 | -D "$LDAP_ADMIN_DN" \ 1484 | -w "$ADMINPASS" \ 1485 | -b "$LDAP_BASEDN" dn >> "$LOG" 2>&1 1486 | 1487 | if grep -q "^dn: " "$LOG"; then 1488 | dialog --backtitle "Samba Validation" --infobox "StartTLS (389) test passed." 5 50 1489 | sleep 2 1490 | else 1491 | dialog --backtitle "Samba Validation" --msgbox "StartTLS (389) test failed — see $LOG for details." 7 60 1492 | fi 1493 | 1494 | dialog --backtitle "Samba Validation" --infobox "Testing LDAPS on port 636..." 5 50 1495 | sleep 2 1496 | LDAPTLS_REQCERT=never \ 1497 | ldapsearch -x -H ldaps://$IPADDR \ 1498 | -D "$LDAP_ADMIN_DN" \ 1499 | -w "$ADMINPASS" \ 1500 | -b "$LDAP_BASEDN" dn >> "$LOG" 2>&1 1501 | 1502 | if grep -q "^dn: " "$LOG"; then 1503 | dialog --backtitle "Samba Validation" --infobox "LDAPS (636) test passed." 5 50 1504 | sleep 2 1505 | else 1506 | dialog --backtitle "Samba Validation" --msgbox "LDAPS test failed — see $LOG for details." 7 60 1507 | fi 1508 | 1509 | dialog --backtitle "Samba Validation" --title "LDAP Secure Setup Complete" --infobox "StartTLS and LDAPS tested." 7 60 1510 | sleep 3 1511 | return 0 1512 | } 1513 | #===========KERBEROS LOGIN AND TICKET CHECK============= 1514 | check_kerberos_ticket() { 1515 | dialog --backtitle "Samba Validation" --title "Kerberos Login" --infobox "Attempting Kerberos login using Administrator credentials..." 5 80 1516 | sleep 2 1517 | 1518 | # Attempt kinit with password from variable 1519 | echo "$ADMINPASS" | kinit Administrator 2>/tmp/kinit_error.log 1520 | 1521 | if [[ $? -ne 0 ]]; then 1522 | ERROR_MSG=$(< /tmp/kinit_error.log) 1523 | dialog --backtitle "Samba Validation" --title "Kerberos Login Failed" --msgbox "Kerberos login failed:\n$ERROR_MSG" 10 80 1524 | return 1 1525 | fi 1526 | 1527 | # Run klist and capture output 1528 | klist_output=$(klist 2>&1) 1529 | 1530 | if echo "$klist_output" | grep -q "Valid starting.*Service principal"; then 1531 | dialog --backtitle "Samba Validation" --title "Kerberos Login Success" --infobox "Kerberos ticket successfully acquired for Administrator.\n\nTicket Details:\n\n$klist_output" 20 80 1532 | sleep 3 1533 | else 1534 | dialog --backtitle "Samba Validation" --title "Kerberos Ticket Check Failed" --msgbox "Kerberos login succeeded, but no valid ticket found.\n\n$klist_output" 10 80 1535 | return 1 1536 | fi 1537 | 1538 | return 0 1539 | } 1540 | #===========AUTHENTICATED SAMBA LOGIN CHECK============= 1541 | check_smbclient_login() { 1542 | dialog --backtitle "Samba Validation" --title "SMB Login Test" --infobox "Attempting SMB connection to //localhost/netlogon as Administrator..." 5 80 1543 | sleep 2 1544 | 1545 | smb_output=$(echo "$ADMINPASS" | smbclient //localhost/netlogon -UAdministrator -c 'ls' 2>&1) 1546 | 1547 | if echo "$smb_output" | grep -qE '^\s*\.\s+D\s+[0-9]+' && echo "$smb_output" | grep -qE '^\s*\.\.\s+D\s+[0-9]+'; then 1548 | dialog --backtitle "Samba Validation" --title "SMB Login Success" --infobox "Successfully authenticated and listed netlogon share." 5 60 1549 | sleep 2 1550 | else 1551 | dialog --backtitle "Samba Validation" --title "SMB Login Failed" --msgbox "SMB login failed or unexpected output.\n\n$smb_output" 15 70 1552 | return 1 1553 | fi 1554 | 1555 | return 0 1556 | } 1557 | #===========DNS SRV RECORD CHECK============= 1558 | check_dns_srv_records() { 1559 | FQDN=$(hostname -f) 1560 | DOMAIN=$(echo "$FQDN" | cut -d'.' -f2-) 1561 | HOSTNAME_PART=$(echo "$FQDN" | cut -d'.' -f1) 1562 | TIMEOUT=5 1563 | 1564 | dialog --backtitle "Samba Validation" --backtitle "SRV Records Check" --title "DNS SRV Record Check" --infobox "Querying SRV records for domain $DOMAIN..." 5 60 1565 | sleep 1 1566 | 1567 | # Perform SRV lookups with timeout 1568 | ldap_srv=$(timeout $TIMEOUT host -t SRV _ldap._tcp."$DOMAIN" 2>/dev/null) 1569 | kerberos_srv=$(timeout $TIMEOUT host -t SRV _kerberos._udp."$DOMAIN" 2>/dev/null) 1570 | fqdn_check=$(timeout $TIMEOUT host -t A "$FQDN" 2>/dev/null) 1571 | 1572 | # Handle timeout or failure 1573 | if [[ -z "$ldap_srv" || -z "$kerberos_srv" || -z "$fqdn_check" ]]; then 1574 | dialog --backtitle "Samba Validation" --title "DNS Query Timeout" --msgbox "Error: One or more DNS queries timed out after ${TIMEOUT}s.\n\nLDAP SRV:\n$ldap_srv\n 1575 | \nKerberos SRV:\n$kerberos_srv\n\nFQDN A record:\n$fqdn_check" 20 75 1576 | return 1 1577 | fi 1578 | 1579 | # Extract and normalize target FQDNs from SRV responses 1580 | get_srv_hostnames() { 1581 | local srv_records="$1" 1582 | echo "$srv_records" | awk '/SRV record/ {print tolower($NF)}' | sed 's/\.$//' 1583 | } 1584 | 1585 | ldap_targets=$(get_srv_hostnames "$ldap_srv") 1586 | kerberos_targets=$(get_srv_hostnames "$kerberos_srv") 1587 | 1588 | # Combine and check if any match our full FQDN 1589 | all_targets="$ldap_targets $kerberos_targets" 1590 | match_found=0 1591 | for t in $all_targets; do 1592 | if [[ "$t" == "$FQDN" ]]; then 1593 | match_found=1 1594 | break 1595 | fi 1596 | done 1597 | 1598 | if [[ $match_found -eq 1 ]]; then 1599 | dialog --backtitle "Samba Validation" --backtitle "SRV Records Check" --title "DNS SRV Check Passed" --infobox "Success: SRV record matches found for $FQDN\n\nLDAP SRV:\n$ldap_srv\n\nKerberos SRV:\n$kerberos_srv\n\nA Record:\n$fqdn_check" 20 75 1600 | sleep 3 1601 | return 0 1602 | else 1603 | # Check Samba service status 1604 | samba_status=$(systemctl is-active samba) 1605 | dns_entry=$(nmcli dev show | grep 'IP4.DNS') 1606 | 1607 | dialog --backtitle "Samba Validation" --title "DNS SRV Record Check Failed" --msgbox "Error: No matching SRV hostnames.\n\nSamba 1608 | status: $samba_status\n\nDNS entries:\n$dns_entry" 20 75 1609 | return 1 1610 | fi 1611 | } 1612 | 1613 | #===========ANONYMOUS LOGIN TEST============= 1614 | test_anonymous_login() { 1615 | dialog --backtitle "Samba Validation" --title "Anonymous SMB Login Test" --infobox "Testing anonymous login to the Samba server..." 5 60 1616 | sleep 2 1617 | 1618 | output=$(smbclient -L localhost -N 2>&1) 1619 | 1620 | if echo "$output" | grep -q "Anonymous login successful"; then 1621 | dialog --backtitle "Samba Validation" --title "Anonymous Login Success" --infobox "Success: Anonymous login successful." 6 60 1622 | sleep 2 1623 | else 1624 | dialog --backtitle "Samba Validation" --title "Anonymous Login Failed" --msgbox "Error: Anonymous logins are not available.\n\n$output" 15 70 1625 | return 1 1626 | fi 1627 | 1628 | return 0 1629 | } 1630 | #===========CLEANUP STRONG AUTH LINE IN smb.conf============= 1631 | cleanup_strong_auth_line() { 1632 | CONF_FILE="/etc/samba/smb.conf" 1633 | 1634 | if grep -q '^[[:space:]]*\\#ldap server require strong auth = no' "$CONF_FILE"; then 1635 | dialog --backtitle "Samba Validation" --title "Cleaning Samba Config" --infobox "Fixing strong auth line in smb.conf..." 5 60 1636 | sleep 2 1637 | 1638 | # Remove leading backslash before #ldap line 1639 | sed -i 's/^[[:space:]]*\\#ldap server require strong auth = no/#ldap server require strong auth = no/' "$CONF_FILE" 1640 | 1641 | dialog --backtitle "Samba Validation" --title "Fix Applied" --infobox "smb.conf corrected" 5 60 1642 | sleep 2 1643 | else 1644 | dialog --backtitle "Samba Validation" --title "No Change Needed" --infobox "No Errors in smb.conf." 5 60 1645 | sleep 2 1646 | fi 1647 | } 1648 | #===========REVERSE DNS ZONE CREATION============= 1649 | create_reverse_dns_zone() { 1650 | LOG_FILE="/var/log/samba-reverse-zone.log" 1651 | FQDN=$(hostname -f) 1652 | IP=$(hostname -I | awk '{for(i=1;i<=NF;i++) if($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) { print $i; exit }}') 1653 | 1654 | echo "[INFO] Detected FQDN: $FQDN" >> "$LOG_FILE" 1655 | echo "[INFO] Detected IP: $IP" >> "$LOG_FILE" 1656 | 1657 | if [[ -z "$IP" ]]; then 1658 | dialog --backtitle "Create Reverse Zone" --title "IP Detection Failed" --msgbox "Could not detect a valid IPv4 address from hostname -I." 6 60 1659 | echo "[ERROR] No valid IPv4 address found." >> "$LOG_FILE" 1660 | return 1 1661 | fi 1662 | 1663 | REVERSE=$(echo "$IP" | awk -F. '{print $(NF-1)"."$(NF-2)"."$(NF-3)}') 1664 | DEFAULT_ZONE="$REVERSE.in-addr.arpa" 1665 | 1666 | TMP_ZONE_FILE=$(mktemp) 1667 | dialog --backtitle "Create Reverse Zone" --title "Reverse DNS Zone Suggestion" --inputbox \ 1668 | "A reverse DNS zone should be added to your Samba DNS.\n\nBased on your IP $IP, the recommended reverse zone is:\n\n $DEFAULT_ZONE\n\nYou may press OK to accept this default or modify it." \ 1669 | 12 70 "$DEFAULT_ZONE" 2> "$TMP_ZONE_FILE" 1670 | RESPONSE=$? 1671 | 1672 | ZONE=$(<"$TMP_ZONE_FILE") 1673 | rm -f "$TMP_ZONE_FILE" 1674 | 1675 | if [[ $RESPONSE -ne 0 || -z "$ZONE" ]]; then 1676 | dialog --backtitle "Create Reverse Zone" --title "Operation Cancelled" --msgbox "No reverse zone was specified. Operation cancelled." 6 60 1677 | echo "[WARN] Operation cancelled. No zone specified." >> "$LOG_FILE" 1678 | return 1 1679 | fi 1680 | 1681 | dialog --backtitle "Create Reverse Zone" --title "Creating Reverse DNS Zone" --infobox "Adding reverse DNS zone $ZONE to domain $FQDN..." 6 80 1682 | echo "[INFO] Creating reverse zone: $ZONE on $FQDN" >> "$LOG_FILE" 1683 | sleep 2 1684 | 1685 | samba-tool dns zonecreate "$FQDN" "$ZONE" -U "Administrator%$ADMINPASS" >> "$LOG_FILE" 2>&1 1686 | if [[ $? -eq 0 ]]; then 1687 | dialog --backtitle "Create Reverse Zone" --title "Reverse Zone Added" --infobox "Reverse DNS zone $ZONE successfully added to $FQDN." 7 80 1688 | echo "[SUCCESS] Reverse zone $ZONE added to $FQDN" >> "$LOG_FILE" 1689 | sleep 3 1690 | else 1691 | dialog --backtitle "Create Reverse Zone" --title "Zone Creation Failed" --msgbox "Failed to add reverse DNS zone.\n\nCheck $LOG_FILE for details." 10 70 1692 | echo "[ERROR] Failed to create reverse zone $ZONE" >> "$LOG_FILE" 1693 | fi 1694 | } 1695 | #===========RELAX PASSWORD SETTINGS FOR LAB============= 1696 | relax_lab_password_policy() { 1697 | TMP_SELECTION=$(mktemp) 1698 | TMP_LOG="/var/log/samba-password-policy.log" 1699 | 1700 | dialog --backtitle "Password Policy" --title "Relax Password Settings (Lab Use)" --checklist "\ 1701 | Select which password policy changes you want to apply:\n\n\ 1702 | Use SPACE to select/deselect options. Press ENTER to confirm, or Cancel to skip" 15 90 6 \ 1703 | 1 "Disable complexity requirements" off \ 1704 | 2 "Set history-length to 0" off \ 1705 | 3 "Set min password age to 0" off \ 1706 | 4 "Set max password age to 0" off 2> "$TMP_SELECTION" 1707 | 1708 | RESPONSE=$? 1709 | CHOICES=$(<"$TMP_SELECTION") 1710 | rm -f "$TMP_SELECTION" 1711 | 1712 | if [[ $RESPONSE -ne 0 || -z "$CHOICES" ]]; then 1713 | dialog --backtitle "Password Policy" --title "Cancelled" --infobox "No changes were made to password policy." 5 50 1714 | sleep 2 1715 | return 1 1716 | fi 1717 | 1718 | dialog --backtitle "Password Policy" --title "Applying Settings" --infobox "Applying selected password policy settings..." 5 50 1719 | sleep 2 1720 | 1721 | for choice in $CHOICES; do 1722 | case $choice in 1723 | 1) 1724 | samba-tool domain passwordsettings set --complexity=off >> "$TMP_LOG" 2>&1 1725 | ;; 1726 | 2) 1727 | samba-tool domain passwordsettings set --history-length=0 >> "$TMP_LOG" 2>&1 1728 | ;; 1729 | 3) 1730 | samba-tool domain passwordsettings set --min-pwd-age=0 >> "$TMP_LOG" 2>&1 1731 | ;; 1732 | 4) 1733 | samba-tool domain passwordsettings set --max-pwd-age=0 >> "$TMP_LOG" 2>&1 1734 | ;; 1735 | esac 1736 | done 1737 | 1738 | dialog --backtitle "Password Policy" --title "Password Policy Updated" --infobox "Selected settings have been applied.\nLog saved to $TMP_LOG." 6 60 1739 | sleep 2 1740 | } 1741 | #===========CONFIGURE FAIL2BAN============= 1742 | configure_fail2ban() { 1743 | LOG_FILE="/var/log/fail2ban-setup.log" 1744 | ORIGINAL_FILE="/etc/fail2ban/jail.conf" 1745 | JAIL_LOCAL_FILE="/etc/fail2ban/jail.local" 1746 | SSHD_LOCAL_FILE="/etc/fail2ban/jail.d/sshd.local" 1747 | 1748 | { 1749 | echo "10" 1750 | echo "# Copying jail.conf to jail.local..." 1751 | if cp -v "$ORIGINAL_FILE" "$JAIL_LOCAL_FILE" >> "$LOG_FILE" 2>&1; then 1752 | echo "Copied jail.conf to jail.local" >> "$LOG_FILE" 1753 | else 1754 | dialog --backtitle "Configure Fail2ban for SSH" --title "Error" --msgbox "Failed to copy $ORIGINAL_FILE to $JAIL_LOCAL_FILE" 6 60 1755 | echo "Failed to copy jail.conf" >> "$LOG_FILE" 1756 | return 1 1757 | fi 1758 | 1759 | echo "30" 1760 | echo "# Enabling SSHD in jail.local..." 1761 | if sed -i '/^\[sshd\]/,/^$/ s/#mode.*normal/&\nenabled = true/' "$JAIL_LOCAL_FILE" >> "$LOG_FILE" 2>&1; then 1762 | echo "Modified jail.local to enable SSHD" >> "$LOG_FILE" 1763 | else 1764 | dialog --backtitle "Configure Fail2ban for SSH" --title "Error" --msgbox "Failed to enable SSHD in jail.local" 6 60 1765 | return 1 1766 | fi 1767 | 1768 | echo "50" 1769 | echo "# Writing SSHD jail configuration..." 1770 | cat < "$SSHD_LOCAL_FILE" 1771 | [sshd] 1772 | enabled = true 1773 | maxretry = 5 1774 | findtime = 300 1775 | bantime = 3600 1776 | bantime.increment = true 1777 | bantime.factor = 2 1778 | EOL 1779 | echo "Created sshd.local config" >> "$LOG_FILE" 1780 | 1781 | echo "60" 1782 | echo "# Enabling and starting Fail2Ban..." 1783 | systemctl enable fail2ban >> "$LOG_FILE" 2>&1 1784 | systemctl start fail2ban >> "$LOG_FILE" 2>&1 1785 | sleep 2 1786 | 1787 | echo "75" 1788 | echo "# Checking Fail2Ban status..." 1789 | if systemctl is-active --quiet fail2ban; then 1790 | echo "Fail2Ban is running." >> "$LOG_FILE" 1791 | else 1792 | echo "Fail2Ban failed to start. Attempting SELinux recovery..." >> "$LOG_FILE" 1793 | 1794 | selinux_status=$(sestatus | grep "SELinux status" | awk '{print $3}') 1795 | if [[ "$selinux_status" == "enabled" ]]; then 1796 | restorecon -v /etc/fail2ban/jail.local >> "$LOG_FILE" 2>&1 1797 | denials=$(ausearch -m avc -ts recent | grep "fail2ban-server" | wc -l) 1798 | if (( denials > 0 )); then 1799 | ausearch -c 'fail2ban-server' --raw | audit2allow -M my-fail2banserver >> "$LOG_FILE" 2>&1 1800 | semodule -X 300 -i my-fail2banserver.pp >> "$LOG_FILE" 2>&1 1801 | echo "Custom SELinux policy applied." >> "$LOG_FILE" 1802 | fi 1803 | fi 1804 | 1805 | systemctl restart fail2ban >> "$LOG_FILE" 2>&1 1806 | if systemctl is-active --quiet fail2ban; then 1807 | echo "Fail2Ban restarted successfully after SELinux fix." >> "$LOG_FILE" 1808 | else 1809 | dialog --title "Fail2Ban Error" --msgbox "Fail2Ban failed to start even after SELinux policy fix. Please investigate manually." 8 70 1810 | echo "Fail2Ban still failed after SELinux fix." >> "$LOG_FILE" 1811 | return 1 1812 | fi 1813 | fi 1814 | 1815 | echo "90" 1816 | echo "# Verifying SSHD jail status..." 1817 | sshd_status=$(fail2ban-client status sshd 2>&1) 1818 | if echo "$sshd_status" | grep -q "ERROR NOK: ('sshd',)"; then 1819 | dialog --backtitle "Configure Fail2ban for SSH" --title "SSHD Jail Error" --msgbox "SSHD jail failed to start. Check configuration:\n\n$sshd_status" 10 70 1820 | echo "SSHD jail failed to start." >> "$LOG_FILE" 1821 | elif echo "$sshd_status" | grep -q "Banned IP list:"; then 1822 | echo "SSHD jail is active and functional." >> "$LOG_FILE" 1823 | else 1824 | dialog --backtitle "Configure Fail2ban for SSH" --title "SSHD Jail Warning" --msgbox "SSHD jail may not be functional. Check manually:\n\n$sshd_status" 10 70 1825 | echo "SSHD jail might be non-functional." >> "$LOG_FILE" 1826 | fi 1827 | 1828 | echo "100" 1829 | } | dialog --backtitle "Configure Fail2ban for SSH" --title "Fail2Ban Setup" --gauge "Installing and configuring Fail2Ban..." 10 60 0 1830 | 1831 | dialog --backtitle "Configure Fail2ban for SSH" --title "Success" --infobox "Fail2Ban has been configured and started successfully." 6 60 1832 | sleep 3 1833 | } 1834 | #===========SERVICE CHECK & ENABLE PROGRESS============= 1835 | check_and_enable_services() { 1836 | TMP_LOG=$(mktemp) 1837 | TMP_BAR=$(mktemp) 1838 | 1839 | # List the services you want to manage 1840 | SERVICES=("fail2ban" "samba" "cockpit.socket") # <-- add or remove services as needed 1841 | 1842 | total=${#SERVICES[@]} 1843 | count=0 1844 | 1845 | { 1846 | for service in "${SERVICES[@]}"; do 1847 | echo "Checking $service..." >> "$TMP_LOG" 1848 | 1849 | systemctl is-enabled --quiet "$service" 1850 | if [[ $? -ne 0 ]]; then 1851 | echo "$service is not enabled. Enabling..." >> "$TMP_LOG" 1852 | systemctl enable "$service" >> "$TMP_LOG" 2>&1 1853 | fi 1854 | 1855 | systemctl is-active --quiet "$service" 1856 | if [[ $? -ne 0 ]]; then 1857 | echo "$service is not running. Starting..." >> "$TMP_LOG" 1858 | systemctl start "$service" >> "$TMP_LOG" 2>&1 1859 | fi 1860 | 1861 | systemctl is-active --quiet "$service" 1862 | if [[ $? -eq 0 ]]; then 1863 | echo "$service is active." >> "$TMP_LOG" 1864 | else 1865 | echo "$service failed to start." >> "$TMP_LOG" 1866 | fi 1867 | 1868 | # Progress bar update 1869 | count=$((count + 1)) 1870 | percent=$(( (count * 100) / total )) 1871 | echo $percent 1872 | sleep 1 1873 | done 1874 | } | dialog --backtitle "Enabling and Starting Services" --title "Service Check & Startup" --gauge "Checking services and starting them if needed..." 10 70 0 1875 | 1876 | # Final report 1877 | if grep -q "failed to start" "$TMP_LOG"; then 1878 | dialog --backtitle "Enabling and Starting Services" --title "Service Status" --textbox "$TMP_LOG" 20 70 1879 | else 1880 | dialog --backtitle "Enabling and Starting Services" --title "All Services Running" --infobox "All services have been enabled and are running." 7 60 1881 | sleep 3 1882 | fi 1883 | 1884 | rm -f "$TMP_LOG" "$TMP_BAR" 1885 | } 1886 | #===========INSTALL SERVER MANAGEMENT SCRIPTS============= 1887 | install_server_management() { 1888 | LOG_FILE="/var/log/server-management-install.log" 1889 | INSTALL_DIR="/root/RADS-SMInstaller" 1890 | GIT_REPO="https://github.com/fumatchu/RADS-SM.git" 1891 | 1892 | dialog --backtitle "Installing Server Manager" --title "Installing Server Management" --infobox \ 1893 | "This installer will set up Server Management tools for AD, DHCP, and services.\n\nYou can launch it anytime by typing: server-manager" 8 90 1894 | echo "[INFO] Starting Server Management installation..." >> "$LOG_FILE" 1895 | sleep 5 1896 | 1897 | mkdir -p "$INSTALL_DIR" 1898 | git clone "$GIT_REPO" "$INSTALL_DIR" >> "$LOG_FILE" 2>&1 1899 | if [[ $? -ne 0 ]]; then 1900 | dialog --backtitle "Installing Server Manager" --title "Clone Failed" --msgbox "Failed to clone repository from $GIT_REPO\nCheck $LOG_FILE for details." 8 60 1901 | echo "[ERROR] Git clone failed." >> "$LOG_FILE" 1902 | return 1 1903 | fi 1904 | 1905 | rm -rf /root/.servman /usr/bin/server-manager 1906 | sed -i '/\/usr\/bin\/server-manager/d' /root/.bash_profile 1907 | cd "$INSTALL_DIR" || return 1 1908 | 1909 | mv -v ./.servman /root >> "$LOG_FILE" 2>&1 1910 | chmod 700 "$INSTALL_DIR/server-manager" 1911 | mv -v "$INSTALL_DIR/server-manager" /usr/bin/ >> "$LOG_FILE" 2>&1 1912 | chmod -R 700 /root/.servman/ 1913 | echo "/usr/bin/server-manager" >> /root/.bash_profile 1914 | 1915 | rm -rf "$INSTALL_DIR" /root/RADS-* 1916 | 1917 | dialog --backtitle "Installing Server Manager" --title "Installation Complete" --msgbox \ 1918 | "Server Management tools have been successfully installed!\n\nType 'server-manager' at any time to launch the interface." 10 80 1919 | echo "[SUCCESS] Server Manager installed." >> "$LOG_FILE" 1920 | } 1921 | #===========CLEANUP INSTALLATION FILES============= 1922 | cleanup_installer_files() { 1923 | LOG_FILE="/var/log/rads-cleanup.log" 1924 | TMP_PROGRESS=$(mktemp) 1925 | 1926 | { 1927 | echo "10"; sleep 0.5 1928 | echo "# Starting cleanup..." >> "$TMP_PROGRESS" 1929 | 1930 | # Remove DCInstall.sh launch block 1931 | sed -i '/## Run RADS installer on every interactive login ##/,/fi/d' /root/.bash_profile 1932 | echo "[INFO] Removed RADS installer launch block" >> "$LOG_FILE" 1933 | echo "30"; sleep 0.5 1934 | 1935 | # Also remove any straggling DCInstall.sh lines 1936 | sed -i '/DCInstall.sh/d' /root/.bash_profile 1937 | echo "[INFO] Removed any additional DCInstall.sh entries" >> "$LOG_FILE" 1938 | echo "50"; sleep 0.5 1939 | 1940 | # Delete installer-related files 1941 | rm -rf /root/DC-Installer.sh /root/ADDCInstaller /root/FR-Installer /root/FR-Installer.sh >> "$LOG_FILE" 2>&1 1942 | rm -f /root/samba*.src.rpm >> "$LOG_FILE" 2>&1 1943 | echo "[INFO] Removed installer files" >> "$LOG_FILE" 1944 | echo "90"; sleep 0.5 1945 | 1946 | echo "100" 1947 | } | dialog --backtitle "Installer Cleanup" --title "Cleanup Progress" --gauge "Cleaning up installer files..." 10 60 0 1948 | 1949 | rm -f "$TMP_PROGRESS" 1950 | 1951 | dialog --backtitle "Installer Cleanup" --title "Cleanup Complete" --infobox "Installer files have been successfully removed from the system." 6 80 1952 | sleep 3 1953 | } 1954 | configure_dnf_automatic() { 1955 | local CONFIG="/etc/dnf/automatic.conf" 1956 | local BACKUP="/etc/dnf/automatic.conf.bak" 1957 | local LOG="/tmp/dnf_automatic_setup.log" 1958 | : > "$LOG" 1959 | 1960 | # 1. Inform the user 1961 | dialog --backtitle "DNF Automatic Setup" --title "Configure Security Updates" \ 1962 | --infobox "This will enable SECURITY-ONLY updates.\n\nIt will also disable major OS upgrades.\n\nUpdate time will be left to system default" 10 60 1963 | sleep 4 1964 | 1965 | # 2. Backup current config 1966 | sudo cp -f "$CONFIG" "$BACKUP" 1967 | echo "[INFO] Backed up $CONFIG to $BACKUP" >> "$LOG" 1968 | 1969 | # 3. Apply dnf-automatic settings 1970 | sudo sed -i 's/^upgrade_type.*/upgrade_type = security/' "$CONFIG" 1971 | sudo sed -i 's/^apply_updates.*/apply_updates = yes/' "$CONFIG" 1972 | 1973 | # 4. Remove any [timer] section from the config (let systemd handle it) 1974 | sudo sed -i '/^\[timer\]/,/^$/d' "$CONFIG" 1975 | 1976 | # 5. Remove any old systemd override (Cockpit workaround) 1977 | sudo rm -f /etc/systemd/system/dnf-automatic.timer.d/override.conf 1978 | 1979 | # 6. Reload systemd and restart timer 1980 | sudo systemctl daemon-reexec 1981 | sudo systemctl daemon-reload 1982 | sudo systemctl enable --now dnf-automatic.timer 1983 | 1984 | # 7. Validate setup 1985 | local STATUS_MSG="" 1986 | local VALIDATE_OUTPUT 1987 | VALIDATE_OUTPUT=$(grep -E 'upgrade_type|apply_updates' "$CONFIG") 1988 | echo "$VALIDATE_OUTPUT" >> "$LOG" 1989 | 1990 | if echo "$VALIDATE_OUTPUT" | grep -q "apply_updates = yes"; then 1991 | STATUS_MSG=" Security updates enabled.\n" 1992 | else 1993 | dialog --title "Error" --msgbox "Configuration failed.\nCheck $CONFIG or $LOG." 7 50 1994 | return 1 1995 | fi 1996 | 1997 | if systemctl is-active --quiet dnf-automatic.timer; then 1998 | STATUS_MSG+="Timer is active.\n" 1999 | else 2000 | STATUS_MSG+="⚠Timer is not running!\nCheck: journalctl -u dnf-automatic.timer\n" 2001 | fi 2002 | 2003 | NEXT_RUN=$(systemctl list-timers --all | grep dnf-automatic.timer | awk '{print $1, $2}') 2004 | STATUS_MSG+="\nNext scheduled run: $NEXT_RUN" 2005 | 2006 | dialog --backtitle "DNF Automatic Setup" --title "Setup Complete" --infobox "$STATUS_MSG" 12 60 2007 | sleep 3 2008 | } 2009 | #===========FINAL INSTALLATION COMPLETE PROMPT============= 2010 | prompt_reboot_now() { 2011 | dialog --backtitle "Installation Complete" --title "Installation Complete" \ 2012 | --yesno "Server Installation Complete!\n\nWould you like to reboot the system now?" 8 50 2013 | 2014 | if [[ $? -eq 0 ]]; then 2015 | reboot 2016 | fi 2017 | } 2018 | 2019 | # ========= MAIN ========= 2020 | check_samba_running 2021 | show_welcome_screen 2022 | detect_active_interface 2023 | prompt_static_ip_if_dhcp 2024 | check_root_and_os 2025 | check_and_enable_selinux 2026 | check_internet_connectivity 2027 | validate_and_set_hostname 2028 | show_ad_server_checklist 2029 | prompt_admin_password 2030 | configure_dhcp_server 2031 | # === Set Time === 2032 | if ! prompt_ntp_servers; then 2033 | dialog --title "Chrony NTP Configuration" --msgbox "NTP configuration was cancelled." 6 40 2034 | exit 1 2035 | fi 2036 | 2037 | if ! prompt_allow_networks; then 2038 | dialog --backtitle "Configure NTP" --title "Chrony NTP Configuration" --msgbox "No network was allowed. Configuration cancelled." 6 50 2039 | exit 1 2040 | fi 2041 | 2042 | update_chrony_config 2043 | 2044 | if ! validate_time_sync; then 2045 | dialog --backtitle "Configure NTP" --title "Chrony NTP Configuration" --msgbox "Chrony configuration aborted." 6 40 2046 | exit 1 2047 | fi 2048 | 2049 | dialog --backtitle "Configure NTP" --title "Chrony NTP Configuration" --infobox "NTP configuration completed successfully." 4 60 2050 | sleep 3 2051 | #=== End Set time === 2052 | update_and_install_packages 2053 | vm_detection 2054 | configure_selinux 2055 | configure_firewall 2056 | configure_samba_provisioning 2057 | create_kdc_conf 2058 | set_local_dns_resolver 2059 | add_freeradius_support 2060 | add_dnf_smb_mon_cron 2061 | copy_samba_dnf_pkg_update 2062 | enable_and_check_samba_service 2063 | update_issue_file 2064 | setup_samba_ldaps_cert 2065 | cleanup_strong_auth_line 2066 | test_ldap_secure_connection 2067 | check_kerberos_ticket 2068 | check_dns_srv_records 2069 | check_smbclient_login 2070 | test_anonymous_login 2071 | create_reverse_dns_zone 2072 | relax_lab_password_policy 2073 | configure_fail2ban 2074 | configure_dnf_automatic 2075 | install_server_management 2076 | check_and_enable_services 2077 | cleanup_installer_files 2078 | prompt_reboot_now 2079 | --------------------------------------------------------------------------------