diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 0000000..262a05f --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,23 @@ +name: Shell checks + +on: + push: + pull_request: + +jobs: + shellcheck: + name: Bash syntax and ShellCheck + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Install ShellCheck + run: sudo apt-get update && sudo apt-get install -y shellcheck + + - name: Check Bash syntax + run: bash -n install.sh + + - name: Run ShellCheck + run: shellcheck install.sh diff --git a/.vscode/settings.json b/.vscode/settings.json index 7f7771f..c240b0a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,19 @@ { "cSpell.words": [ + "Armbian", + "btop", "decoscript", "ethernets", "fastcgi", + "journalctl", "LEMP", "mbstring", + "netfilter", "Netplan", "networkd", + "nftables", "NOPASSWD", - "phpinfo" + "phpinfo", + "pihole" ] } diff --git a/README.md b/README.md index 67058f6..29df445 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ## Overview -This script is a modular server utility tool for Debian-based and Ubuntu-based systems. It helps automate common setup tasks without forcing a specific stack. You choose exactly -what gets installed: essential tools, Nginx, PHP, NVM, static IP profiles, and more. +This is a menu-driven server utility script for Ubuntu and other Debian-based systems. It helps automate common setup tasks without forcing one fixed stack. You choose what to install or configure: +essential tools, Nginx, PHP, MySQL, NVM, SSH hardening, Fail2ban, static IP profiles, and more. -Ubuntu is the primary target environment. Other Debian derivatives should work, but if you encounter issues, feel free to open an -[issue](https://github.com/Decaded/install-script/issues) and describe your setup. +Ubuntu is the primary target environment. Other Debian derivatives should work, but some minimal images, SBC images, and custom kernels may need extra care. If you encounter an issue, +open an [issue](https://github.com/Decaded/install-script/issues) and include your OS, kernel version, and the menu option that failed.
@@ -45,7 +45,7 @@ Ubuntu is the primary target environment. Other Debian derivatives should work, ./install.sh ``` -A menu will appear with all available options. +The script requires sudo privileges and checks for them on startup. A menu will appear with the available actions.
Script Menu Preview @@ -55,44 +55,53 @@ A menu will appear with all available options. ## Features -### Essential Tools - -Install a curated pack of common system utilities: - -1. **[htop](https://htop.dev/)** – process viewer -2. **[screen](https://www.gnu.org/software/screen/)** – terminal multiplexer -3. **[nload](https://github.com/rolandriegel/nload)** – network traffic monitor -4. **[nano](https://www.nano-editor.org/)** – simple text editor -5. **[firewalld](https://firewalld.org/)** – firewall management - - - Automatically opens SSH - - Migrates from UFW if needed +### Main Menu -6. **[fail2ban](https://github.com/fail2ban/fail2ban)** – intrusion prevention +- Install selected essential apps +- Install a web server stack +- Install Node Version Manager (NVM) +- Enable passwordless sudo +- Configure SSH key-only authentication +- Configure a static IP address +- Configure Fail2ban +- Revert static IP configuration to DHCP +- Check for script updates +- Restore SSH configuration when a backup exists - - Default configuration or custom rules +### Essential Tools -7. **[git](https://git-scm.com/)** – version control +Install any combination of common utilities from a checklist: - - Optional first-time setup +- **[htop](https://htop.dev/)** and **[btop](https://github.com/aristocratos/btop)** - process viewers +- **[screen](https://www.gnu.org/software/screen/)** and **[tmux](https://github.com/tmux/tmux/wiki)** - terminal multiplexers +- **[nload](https://github.com/rolandriegel/nload)** - network traffic monitor +- **[nano](https://www.nano-editor.org/)** and **[Neovim](https://neovim.io/)** - text editors +- **[firewalld](https://firewalld.org/)** - firewall management +- **[fail2ban](https://github.com/fail2ban/fail2ban)** - intrusion prevention +- **[git](https://git-scm.com/)** - version control +- **[unattended-upgrades](https://wiki.debian.org/UnattendedUpgrades)** - automatic security updates +- **[Pi-hole](https://pi-hole.net/)** - ad blocker and optional DHCP server -8. **[unattended-upgrades](https://wiki.debian.org/UnattendedUpgrades)** – automatic security updates +When selected, some tools offer follow-up configuration. Firewalld asks for the current SSH port, opens that port, and checks for working netfilter/nftables support before trying to start +the service. Fail2ban can use the default setup or a custom `jail.local` URL. Git can configure global name, email, and default branch. ### SSH Configuration -Switch to secure, key-only SSH authentication. The script: +Switch to key-only SSH authentication. The script: - Disables password-based logins +- Enables public key authentication +- Adds the provided public key to `~/.ssh/authorized_keys` - Creates a backup of your SSH config -- Provides a restore option +- Provides a restore option when a backup exists Backup file location: -``` bash -/etc/ssh/sshd_config_decoscript.backup +```bash +/etc/ssh/sshd_config_decoscript.backup.* ``` -Re-running the script replaces the old backup, so rename it if you want to keep multiple versions. +The script keeps the five most recent SSH config backups. ### Passwordless Sudo @@ -100,39 +109,31 @@ Enables password-free sudo access if desired. If your system already uses this c ### Web Server Setup -- Automatic cleanup of Apache2 if present -- Firewall rules for HTTP(S) when using firewalld - -Installs the full **LEMP** stack: +Choose one of three web server paths: -- **[Nginx](https://nginx.org/)** installation and configuration -- **[MySQL](https://www.mysql.com/)** installation and secure setup -- **[PHP](https://www.php.net/)** installation with commonly used modules +- Full **LEMP** stack: **[Nginx](https://nginx.org/)**, **[MySQL](https://www.mysql.com/)**, and **[PHP](https://www.php.net/)** +- **Nginx + PHP** +- **Nginx only** - - Configures **php-fpm** to work with Nginx - - Installs modules: - - **php-cli** - - **php-fpm** - - **php-mbstring** - - **php-curl** - - **php-xml** - - **php-zip** - - **php-gd** - - **php-mysql** +The web server setup can: -- **OR** install Nginx and PHP only, -- **OR** install only Nginx. +- Remove Apache2 if it is installed, to avoid port conflicts +- Enable and start Nginx +- Install common PHP/FPM packages and configure Nginx for PHP +- Create `/etc/nginx/cert` for SSL certificates +- Create a simple default page if the web root is empty +- Open HTTP and HTTPS ports when firewalld is available ### Node.js via NVM -Installs the latest **[NVM](https://github.com/nvm-sh/nvm)** version and lets you manage Node.js installations cleanly: +Installs the latest **[NVM](https://github.com/nvm-sh/nvm)** version and lets you choose a Node.js version to install: -- Install or remove Node.js versions -- Switch between versions +- Lists recent remote Node.js versions +- Installs the version you enter ### Static IP Configuration -Configure a static IP address using **Netplan** when available. +Configure a static IP address using **Netplan**. Supports: @@ -141,7 +142,22 @@ Supports: - Gateway - DNS servers -If Netplan isn’t present, the script chooses the best available method. +If Netplan is not present, the script installs `netplan.io` before writing the configuration. It also installs `net-tools` when needed to list network interfaces. + +Before changing an existing Netplan file, the script creates a backup under: + +```bash +/etc/netplan/backups_decoscript/ +``` + +A revert option is available from the main menu to switch an interface back to DHCP. + +--- + +## Notes + +- Firewalld depends on working kernel netfilter/nftables support. On some minimal Armbian or SBC images, firewalld may not be usable until the kernel is updated and the system is rebooted. +- SSH and network changes can disconnect you from a remote server if incorrect values are entered. Keep another access path available when possible. --- diff --git a/install.sh b/install.sh index bf6a462..82cb436 100644 --- a/install.sh +++ b/install.sh @@ -5,13 +5,89 @@ # It allows users to install essential apps, set up a web server, configure NVM, enable passwordless sudo, # set up SSH key-based authentication and more. -# Author: Decaded (https://github.com/Decaded) - -# Script version -SCRIPT_VERSION="2.2.0" +# Author: Decaded (https://github.com/Decaded | https://decaded.dev) + +# Sections: +# - Metadata and Constants +# - Common Helpers +# - Menu and Routing +# - Update System +# - Essential Apps +# - Firewalld +# - SSH +# - Passwordless Sudo +# - Web Server +# - NVM +# - Git +# - Fail2ban +# - Static IP / Netplan +# - Main + +# ============================================================================== +# Metadata and Constants +# ============================================================================== + +SCRIPT_VERSION="3.0.0" SCRIPT_URL="https://raw.githubusercontent.com/Decaded/install-script/refs/heads/main/install.sh" -# Function to display a menu and get user's choice +SSH_CONFIG="/etc/ssh/sshd_config" +SSH_BACKUP_PATTERN="/etc/ssh/sshd_config_decoscript.backup.*" +NETPLAN_CONFIG="/etc/netplan/01-network-manager-all.yaml" +NGINX_DEFAULT_SITE="/etc/nginx/sites-available/default" +NGINX_CERT_DIR="/etc/nginx/cert" +WEB_ROOT="/var/www/html" + +# ============================================================================== +# Common Helpers +# ============================================================================== + +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +package_installed() { + dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -q "install ok installed" +} + +apt_install() { + if [ "$#" -eq 0 ]; then + return 0 + fi + + sudo apt update && sudo apt install "$@" -y +} + +check_sudo_privileges() { + if ! sudo -n true; then + echo "You need sudo privilege to run this script." + exit 1 + fi +} + +validate_port() { + local port="$1" + if ! [[ "$port" =~ ^[0-9]+$ ]] || ((port < 1 || port > 65535)); then + echo "Error: Invalid port number. Please enter a valid numeric port between 1 and 65535." + return 1 + fi + return 0 +} + +has_ssh_config_backup() { + compgen -G "$SSH_BACKUP_PATTERN" >/dev/null +} + +latest_ssh_config_backup() { + find /etc/ssh -maxdepth 1 -name "sshd_config_decoscript.backup.*" -printf "%T@ %p\n" 2>/dev/null | + sort -rn | + head -n1 | + cut -d' ' -f2- +} + +# ============================================================================== +# Menu and Routing +# ============================================================================== + show_menu() { clear echo "╔════════════════════════════════════════════════════════╗" @@ -31,7 +107,7 @@ show_menu() { echo "u) Check for script updates" echo - if [ -f "/etc/ssh/sshd_config_decoscript.backup" ]; then + if has_ssh_config_backup; then echo "9) Restore SSH Configuration" fi @@ -49,7 +125,7 @@ show_menu() { r|R) revert_static_ip ;; u|U) check_for_updates ;; 9) - if [ -f "/etc/ssh/sshd_config_decoscript.backup" ]; then + if has_ssh_config_backup; then restore_ssh_config else echo "Invalid choice. Please select a valid option." @@ -78,34 +154,17 @@ show_menu() { esac } -# Function to check if the script has sudo privileges -check_sudo_privileges() { - sudo -n true - if [ $? -ne 0 ]; then - echo "You need sudo privilege to run this script." - exit 1 - fi -} - -# Bash-native CIDR to netmask conversion -cidr_to_netmask() { - local bits=$1 - local mask=$((0xffffffff ^ ((1 << (32 - bits)) - 1))) - printf "%d.%d.%d.%d\n" \ - $(((mask >> 24) & 0xff)) \ - $(((mask >> 16) & 0xff)) \ - $(((mask >> 8) & 0xff)) \ - $((mask & 0xff)) -} +# ============================================================================== +# Update System +# ============================================================================== -# Function to check for script updates check_for_updates() { clear echo "Checking for script updates..." echo # Check if curl is available - if ! command -v curl >/dev/null 2>&1; then + if ! command_exists curl; then echo "curl is not installed. Cannot check for updates." echo "Install curl to enable update checking: sudo apt install curl" return 0 @@ -151,7 +210,7 @@ check_for_updates() { update_script() { echo echo "Downloading latest version..." - local temp_script="/tmp/install_sh_update_$" + local temp_script="/tmp/install_sh_update_$$" if curl -fsSL --max-time 10 -o "$temp_script" "$SCRIPT_URL" 2>/dev/null; then # Verify the download @@ -159,7 +218,9 @@ update_script() { chmod +x "$temp_script" # Backup current script and remove execute permissions from backup - cp "$0" "${0}.backup" 2>/dev/null && chmod -x "${0}.backup" 2>/dev/null || true + if cp "$0" "${0}.backup" 2>/dev/null; then + chmod -x "${0}.backup" 2>/dev/null || true + fi # Replace with new version mv "$temp_script" "$0" @@ -182,136 +243,133 @@ update_script() { fi } -# Function to install essential apps using dialog +# ============================================================================== +# Essential Apps +# ============================================================================== + install_essential_apps() { clear - # Check if dialog is installed, and if not, install it - if ! [ -x "$(command -v dialog)" ]; then + if ! command_exists dialog; then echo "Dialog is not installed. Installing dialog..." - sudo apt update && sudo apt install dialog -y - - # Check if the installation was successful - if [ $? -ne 0 ]; then + if ! apt_install dialog; then echo "Error: Failed to install dialog. Exiting." return fi fi - if ! [ -x "$(command -v curl)" ]; then + if ! command_exists curl; then echo "Curl is not installed. Installing curl..." - sudo apt update && sudo apt install curl -y - - # Check if the installation was successful - if [ $? -ne 0 ]; then + if ! apt_install curl; then echo "Error: Failed to install curl. Exiting." return fi fi - # Define the dialog menu options - app_options=("1" "htop - Interactive process viewer" off - "2" "screen - Terminal multiplexer" off - "3" "nload - Network traffic monitor" off - "4" "nano - Text editor" off - "5" "firewalld - Firewall management" off - "6" "fail2ban - Intrusion prevention system" off - "7" "unattended-upgrades - Automatic updates" off - "8" "git - Version control system" off - "9" "pi-hole - Ad blocker and DHCP server" off) - - # Display the dialog menu and store the user's choices - choices=$(dialog --clear --title "Essential Apps Installer" --checklist "Choose which apps to install:" 0 0 0 "${app_options[@]}" 2>&1 >/dev/tty) - - # Check if the user canceled or made no selection - if [ $? -ne 0 ]; then + local app_options=( + "htop" "Process monitor - htop" off + "btop" "Process monitor - btop" off + "screen" "Terminal multiplexer - screen" off + "tmux" "Terminal multiplexer - tmux" off + "nload" "Network traffic monitor" off + "nano" "Text editor - nano" off + "neovim" "Text editor - Neovim" off + "firewalld" "Firewall management" off + "fail2ban" "Intrusion prevention system" off + "unattended-upgrades" "Automatic updates" off + "git" "Version control system" off + "pi-hole" "Ad blocker and DHCP server" off + ) + local selected_packages=() + local choices + local choice + local install_pihole=false + local configure_firewalld=false + local configure_fail2ban_after_install=false + local configure_unattended_upgrades=false + local configure_git_after_install=false + + if ! choices=$(dialog --clear --title "Essential Apps Installer" --checklist "Choose which apps to install:" 0 0 0 "${app_options[@]}" 2>&1 >/dev/tty); then clear echo "Canceled. Returning to the main menu." return fi - # Strip quotes from dialog output choices=$(echo "$choices" | tr -d '"') - # Process user choices and install selected apps - selected_applications="" - for choice in $choices; do case $choice in - 1) selected_applications+=" htop" ;; - 2) selected_applications+=" screen" ;; - 3) selected_applications+=" nload" ;; - 4) selected_applications+=" nano" ;; - 5) selected_applications+=" firewalld" ;; - 6) selected_applications+=" fail2ban" ;; - 7) selected_applications+=" unattended-upgrades" ;; - 8) selected_applications+=" git" ;; - 9) selected_applications+=" pi-hole" ;; + htop|btop|screen|tmux|nload|nano|neovim) + selected_packages+=("$choice") + ;; + firewalld) + selected_packages+=("firewalld") + configure_firewalld=true + ;; + fail2ban) + selected_packages+=("fail2ban") + configure_fail2ban_after_install=true + ;; + unattended-upgrades) + selected_packages+=("unattended-upgrades") + configure_unattended_upgrades=true + ;; + git) + selected_packages+=("git") + configure_git_after_install=true + ;; + pi-hole) + install_pihole=true + ;; esac done - if [ -z "$selected_applications" ]; then + if [ ${#selected_packages[@]} -eq 0 ] && ! $install_pihole; then echo "No apps selected. Returning to the main menu." return fi - # Check if Pi-hole was selected - if [[ "$selected_applications" == *"pi-hole"* ]]; then - # Pi-hole installation + if $install_pihole; then echo "Installing Pi-hole..." - curl -sSL https://install.pi-hole.net | bash - if [ $? -ne 0 ]; then + if ! curl -sSL https://install.pi-hole.net | bash; then echo "Error: Failed to install Pi-hole. Please check your internet connection and try again." return fi fi - # Remove Pi-hole from the list of selected applications - selected_applications="${selected_applications//pi-hole/}" - - # Check if there are any remaining selected applications - if [ -z "$selected_applications" ]; then - echo "No apps selected. Returning to the main menu." - return - fi - - # Install the remaining selected applications using apt - echo "Installing selected apps: $selected_applications" - sudo apt update && sudo apt install $selected_applications -y - - # Check if there was an error during installation - if [ $? -ne 0 ]; then - echo "Error: Failed to install some or all of the selected apps. Please check your internet connection and try again." - return + if [ ${#selected_packages[@]} -gt 0 ]; then + echo "Installing selected apps: ${selected_packages[*]}" + if ! apt_install "${selected_packages[@]}"; then + echo "Error: Failed to install some or all of the selected apps. Please check your internet connection and try again." + return + fi fi - # Check if firewalld was selected - if [[ "$selected_applications" == *"firewalld"* ]]; then + if $configure_firewalld; then configure_firewall fi - # Check if Fail2ban was selected - if [[ "$selected_applications" == *"fail2ban"* ]]; then + if $configure_fail2ban_after_install; then configure_fail2ban fi - # Check if unattended-upgrades was selected - if [[ "$selected_applications" == *"unattended-upgrades"* ]]; then + if $configure_unattended_upgrades; then sudo dpkg-reconfigure -plow unattended-upgrades fi - # Configure Git only if it was selected - if [[ "$selected_applications" == *"git"* ]]; then + if $configure_git_after_install; then configure_git fi echo "Installation complete." - return } -# Firewalld helpers +# ============================================================================== +# Firewalld +# ============================================================================== + ensure_firewalld_installed() { - command -v firewall-cmd &>/dev/null + command_exists firewall-cmd } is_firewalld_running() { @@ -338,7 +396,7 @@ ensure_netfilter_modules() { check_netfilter_support() { ensure_netfilter_modules - if command -v nft >/dev/null 2>&1; then + if command_exists nft; then sudo nft list ruleset >/dev/null 2>&1 return $? fi @@ -406,7 +464,7 @@ add_firewalld_tcp_port() { return $? fi - if ! command -v firewall-offline-cmd &>/dev/null; then + if ! command_exists firewall-offline-cmd; then echo "Error: firewalld is not running and firewall-offline-cmd is not available." return 1 fi @@ -420,7 +478,33 @@ add_firewalld_tcp_port() { sudo firewall-offline-cmd --zone=public --add-port="$port"/tcp } -# Function to configure the firewall with checks +configure_firewalld_ports() { + local firewalld_was_running=false + local port + + if ! ensure_firewalld_installed; then + echo "Firewalld is not installed. Skipping." + return 0 + fi + + if is_firewalld_running; then + firewalld_was_running=true + fi + + for port in "$@"; do + if ! add_firewalld_tcp_port "$port"; then + echo "Warning: Failed to open port $port [TCP]." + fi + done + + if $firewalld_was_running; then + echo "Reload configuration..." + sudo firewall-cmd --reload + else + echo "Firewalld is not running. Rules were saved and will apply when it starts." + fi +} + configure_firewall() { clear @@ -440,8 +524,7 @@ configure_firewall() { read -rp "Please provide your current SSH port (default is 22): " sshPort sshPort=${sshPort:-22} - validate_port "$sshPort" - if [ $? -ne 0 ]; then + if ! validate_port "$sshPort"; then echo "Invalid port input. Exiting." exit 1 fi @@ -451,22 +534,19 @@ configure_firewall() { firewalld_was_running=true fi - add_firewalld_tcp_port "$sshPort" - if [ $? -ne 0 ]; then + if ! add_firewalld_tcp_port "$sshPort"; then echo "Error: Failed to open firewall port." exit 1 fi if $firewalld_was_running; then echo "Reload configuration..." - sudo firewall-cmd --reload - if [ $? -ne 0 ]; then + if ! sudo firewall-cmd --reload; then echo "Error: Failed to reload firewall configuration." exit 1 fi else - start_firewalld - if [ $? -ne 0 ]; then + if ! start_firewalld; then exit 1 fi fi @@ -474,20 +554,25 @@ configure_firewall() { echo } -# Function to set up SSH key-based authentication +# ============================================================================== +# SSH +# ============================================================================== + setup_ssh_key_authentication() { clear # Check if SSH service is installed - if ! dpkg -l | grep -q "openssh-server"; then + if ! package_installed openssh-server; then echo "SSH service (openssh-server) is not installed." # Ask the user if they want to install SSH service read -rp "Do you want to install SSH service? (Y/n): " install_ssh_service if [[ "$install_ssh_service" =~ ^[Yy]$ || "$install_ssh_service" == "" ]]; then - sudo apt update - sudo apt install openssh-server -y + if ! apt_install openssh-server; then + echo "Error: Failed to install openssh-server." + return + fi else echo "SSH service will not be installed. Returning to the main menu." return @@ -498,17 +583,23 @@ setup_ssh_key_authentication() { # Backup with max 5 kept local max_backups=5 - local backup_name="/etc/ssh/sshd_config_decoscript.backup.$(date +%Y%m%d%H%M%S)" - + local backup_name + backup_name="${SSH_CONFIG}_decoscript.backup.$(date +%Y%m%d%H%M%S)" + # Create a backup of the sshd_config file - sudo cp /etc/ssh/sshd_config "$backup_name" + sudo cp "$SSH_CONFIG" "$backup_name" echo "Backup created: $backup_name" - + # Clean old backups, keep only the most recent $max_backups - local backup_count=$(ls -1t /etc/ssh/sshd_config_decoscript.backup.* 2>/dev/null | wc -l) + local backup_count + backup_count=$(find /etc/ssh -maxdepth 1 -name "sshd_config_decoscript.backup.*" 2>/dev/null | wc -l) if [ "$backup_count" -gt "$max_backups" ]; then echo "Cleaning old backups (keeping $max_backups most recent)..." - ls -1t /etc/ssh/sshd_config_decoscript.backup.* 2>/dev/null | tail -n +$((max_backups + 1)) | xargs -r sudo rm -f + find /etc/ssh -maxdepth 1 -name "sshd_config_decoscript.backup.*" -printf "%T@ %p\n" 2>/dev/null | + sort -rn | + tail -n +$((max_backups + 1)) | + cut -d' ' -f2- | + xargs -r sudo rm -f fi echo "#######################################################" @@ -528,9 +619,7 @@ setup_ssh_key_authentication() { # Check if the authorized_keys file exists and the key is not already present if [ -f "$authorized_keys_file" ] && ! grep -q "$ssh_public_key" "$authorized_keys_file"; then - # Save the public key to the authorized_keys file - echo "$ssh_public_key" >>"$authorized_keys_file" - if [ $? -ne 0 ]; then + if ! echo "$ssh_public_key" >>"$authorized_keys_file"; then echo "Error: Failed to save the public key to authorized_keys file." exit 1 fi @@ -538,8 +627,7 @@ setup_ssh_key_authentication() { echo "Public key added to authorized_keys." elif [ ! -f "$authorized_keys_file" ]; then echo "Creating authorized_keys file..." - echo "$ssh_public_key" >"$authorized_keys_file" - if [ $? -ne 0 ]; then + if ! echo "$ssh_public_key" >"$authorized_keys_file"; then echo "Error: Failed to create authorized_keys file." exit 1 fi @@ -551,15 +639,14 @@ setup_ssh_key_authentication() { fi # Enable key-based authentication and disable password-based authentication for SSH - sudo sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config - sudo sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config - sudo sed -i 's/^#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config - sudo sed -i 's/^#PubkeyAuthentication no/PubkeyAuthentication yes/' /etc/ssh/sshd_config - sudo sed -i 's/^PubkeyAuthentication no/PubkeyAuthentication yes/' /etc/ssh/sshd_config + sudo sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' "$SSH_CONFIG" + sudo sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' "$SSH_CONFIG" + sudo sed -i 's/^#PubkeyAuthentication yes/PubkeyAuthentication yes/' "$SSH_CONFIG" + sudo sed -i 's/^#PubkeyAuthentication no/PubkeyAuthentication yes/' "$SSH_CONFIG" + sudo sed -i 's/^PubkeyAuthentication no/PubkeyAuthentication yes/' "$SSH_CONFIG" # Restart the SSH service for changes to take effect - sudo service ssh restart - if [ $? -ne 0 ]; then + if ! sudo service ssh restart; then echo "Error: Failed to restart the SSH service." exit 1 fi @@ -570,7 +657,51 @@ setup_ssh_key_authentication() { echo } -# Function to enable passwordless sudo access +restore_ssh_config() { + clear + + local backup + backup=$(latest_ssh_config_backup) + + if [ -z "$backup" ]; then + echo "Error: No backup files found matching $SSH_BACKUP_PATTERN" + return + fi + + echo "Found backup: $backup" + read -rp "Do you want to restore SSH configuration from this backup? (y/N): " confirm_restore + + if [[ ! "$confirm_restore" =~ ^[Yy]$ ]]; then + echo "Restore cancelled." + return + fi + + echo "Restoring SSH configuration..." + if ! sudo cp "$backup" "$SSH_CONFIG"; then + echo "Error: Failed to restore SSH configuration." + exit 1 + fi + + if ! sudo service ssh restart; then + echo "Error: Failed to restart the SSH service." + exit 1 + fi + + echo "SSH configuration has been restored." + + read -rp "Do you want to keep the backup file? (Y/n): " keep_backup + if [[ "$keep_backup" =~ ^[Nn]$ ]]; then + sudo rm "$backup" + echo "Backup file deleted." + else + echo "Backup file kept at: $backup" + fi +} + +# ============================================================================== +# Passwordless Sudo +# ============================================================================== + enable_passwordless_sudo() { clear local username="$1" @@ -583,8 +714,7 @@ enable_passwordless_sudo() { if [[ "$enable_sudo_option" =~ ^[Yy]$ ]]; then # Append to /etc/sudoers using echo and sudo - echo "$username ALL=(ALL) NOPASSWD:ALL" | sudo EDITOR='tee -a' visudo - if [ $? -ne 0 ]; then + if ! echo "$username ALL=(ALL) NOPASSWD:ALL" | sudo EDITOR='tee -a' visudo; then echo "Error: Failed to enable passwordless sudo access." exit 1 fi @@ -597,102 +727,9 @@ enable_passwordless_sudo() { echo } -# Function to install NGINX and PHP with firewall checks -install_nginx_and_php() { - clear - - local nginx_installed=false - - # Check if NGINX is already installed - if dpkg -l | grep -q "nginx"; then - echo "NGINX is already installed. Skipping NGINX installation." - nginx_installed=true - else - # Install NGINX - sudo apt install nginx -y - nginx_installed=true - fi - - # Detect latest available PHP version - echo "Detecting latest available PHP version..." - local php_versions=(8.4 8.3 8.2 8.1 8.0) - local php_version="" - - for ver in "${php_versions[@]}"; do - if apt-cache policy "php$ver" 2>/dev/null | grep -q 'Candidate:'; then - php_version="$ver" - break - fi - done - - if [ -z "$php_version" ]; then - echo "No specific PHP version found, using default 'php' package." - if $nginx_installed; then - sudo apt install php php-fpm -y - else - sudo apt install php -y - fi - else - echo "Found PHP $php_version available." - if $nginx_installed; then - sudo apt install "php$php_version" "php$php_version-fpm" -y - else - sudo apt install "php$php_version" -y - fi - fi - - # Remove apache2 if it exists - if dpkg -l | awk '/apache2/ {print }' | grep -q .; then - echo "Apache2 is installed. Removing." - sudo service apache2 stop - sudo apt remove apache2 -y - sudo apt purge apache2 -y - sudo apt autoremove -y - sudo systemctl start nginx || true - fi - - echo "#######################################################" - echo "Firewall configuration" - echo "#######################################################" - - if ! ensure_firewalld_installed; then - echo "Firewalld is not installed. Skipping." - - else - firewalld_was_running=false - if is_firewalld_running; then - firewalld_was_running=true - fi - - if ! add_firewalld_tcp_port 80; then - echo "Warning: Failed to open port 80 [TCP]." - fi - - if ! add_firewalld_tcp_port 443; then - echo "Warning: Failed to open port 443 [TCP]." - fi - - if $firewalld_was_running; then - echo "Reload configuration..." - sudo firewall-cmd --reload - else - echo "Firewalld is not running. Rules were saved and will apply when it starts." - fi - echo - fi - - # Create a directory for SSL certs if it doesn't exist - if [ ! -d "/etc/nginx/cert" ]; then - echo "Creating directory /etc/nginx/cert" - sudo mkdir -p /etc/nginx/cert - sudo chmod 700 /etc/nginx/cert - fi - - echo - echo "Finished setting up NGINX and PHP." - echo "You can upload SSL certificates into /etc/nginx/cert" - echo -} +# ============================================================================== +# Web Server +# ============================================================================== # Function to show web server installation menu install_web_server_menu() { @@ -768,9 +805,9 @@ install_lemp_stack() { echo " - PHP with PHP-FPM" echo echo "Next steps:" - echo " 1. Place your website files in /var/www/html/" + echo " 1. Place your website files in $WEB_ROOT/" echo " 2. Configure NGINX sites in /etc/nginx/sites-available/" - echo " 3. SSL certificates go in /etc/nginx/cert/" + echo " 3. SSL certificates go in $NGINX_CERT_DIR/" echo " 4. MySQL: sudo mysql -u root -p" echo "═══════════════════════════════════════════════════════" read -rp "Press Enter to continue..." @@ -797,9 +834,9 @@ install_nginx_php() { echo " - PHP with PHP-FPM" echo echo "Next steps:" - echo " 1. Place your website files in /var/www/html/" + echo " 1. Place your website files in $WEB_ROOT/" echo " 2. Configure NGINX sites in /etc/nginx/sites-available/" - echo " 3. SSL certificates go in /etc/nginx/cert/" + echo " 3. SSL certificates go in $NGINX_CERT_DIR/" echo "═══════════════════════════════════════════════════════" read -rp "Press Enter to continue..." } @@ -822,9 +859,9 @@ install_nginx_only() { echo " - NGINX web server" echo echo "Next steps:" - echo " 1. Place your static files in /var/www/html/" + echo " 1. Place your static files in $WEB_ROOT/" echo " 2. Configure NGINX sites in /etc/nginx/sites-available/" - echo " 3. SSL certificates go in /etc/nginx/cert/" + echo " 3. SSL certificates go in $NGINX_CERT_DIR/" echo "═══════════════════════════════════════════════════════" read -rp "Press Enter to continue..." } @@ -880,31 +917,36 @@ install_php_with_extensions() { sudo systemctl start php*-fpm } +backup_nginx_default_site() { + if [ -f "$NGINX_DEFAULT_SITE" ]; then + sudo cp "$NGINX_DEFAULT_SITE" "${NGINX_DEFAULT_SITE}.backup" + fi +} + # Helper: Configure NGINX for PHP configure_nginx_php() { echo echo "Configuring NGINX for PHP..." # Backup default config - if [ -f /etc/nginx/sites-available/default ]; then - sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.backup - fi + backup_nginx_default_site # Detect PHP-FPM socket - local php_socket=$(ls /run/php/php*-fpm.sock 2>/dev/null | head -1) - + local php_socket + php_socket=$(find /run/php -maxdepth 1 -name "php*-fpm.sock" -print -quit 2>/dev/null) + if [ -z "$php_socket" ]; then echo "Warning: Could not detect PHP-FPM socket. Using default." php_socket="/run/php/php-fpm.sock" fi # Create a working default config - sudo tee /etc/nginx/sites-available/default >/dev/null </dev/null <" | sudo tee /var/www/html/info.php >/dev/null + echo "" | sudo tee "$WEB_ROOT/info.php" >/dev/null echo echo "Test PHP installation: http://your-server-ip/info.php" - echo "Remember to delete /var/www/html/info.php after testing!" + echo "Remember to delete $WEB_ROOT/info.php after testing!" } # Helper: Configure NGINX for static files only @@ -945,17 +987,15 @@ configure_nginx_static() { echo "Configuring NGINX for static content..." # Backup default config - if [ -f /etc/nginx/sites-available/default ]; then - sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.backup - fi + backup_nginx_default_site # Create a clean static config - sudo tee /etc/nginx/sites-available/default >/dev/null </dev/null </dev/null </dev/null < @@ -1015,44 +1055,29 @@ finalize_web_server_install() { EOF - sudo chown www-data:www-data /var/www/html/index.html + sudo chown www-data:www-data "$WEB_ROOT/index.html" fi # Configure firewall if available - if ensure_firewalld_installed; then - echo - echo "Configuring firewall..." - - firewalld_was_running=false - if is_firewalld_running; then - firewalld_was_running=true - fi - - if ! add_firewalld_tcp_port 80; then - echo "Warning: Failed to open port 80 (HTTP)." - fi - - if ! add_firewalld_tcp_port 443; then - echo "Warning: Failed to open port 443 (HTTPS)." - fi - - if $firewalld_was_running; then - sudo firewall-cmd --reload - else - echo "Firewalld is not running. Rules were saved and will apply when it starts." - fi - fi + echo + echo "Configuring firewall..." + configure_firewalld_ports 80 443 echo "Setup complete!" } -# Function to install Node Version Manager (NVM) +# ============================================================================== +# NVM +# ============================================================================== + install_nvm() { clear # Using master branch to always get latest NVM curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash export NVM_DIR="$HOME/.nvm" + # shellcheck source=/dev/null [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + # shellcheck source=/dev/null [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" echo "Fetching available NodeJS versions (showing latest 20)..." @@ -1070,62 +1095,10 @@ install_nvm() { echo } -# Function to restore SSH configuration -restore_ssh_config() { - clear - - # Find the most recent backup - local backup - backup=$(ls -1t /etc/ssh/sshd_config_decoscript.backup.* 2>/dev/null | head -n1) - - if [ -z "$backup" ]; then - echo "Error: No backup files found matching /etc/ssh/sshd_config_decoscript.backup.*" - return - fi - - echo "Found backup: $backup" - read -rp "Do you want to restore SSH configuration from this backup? (y/N): " confirm_restore - - if [[ ! "$confirm_restore" =~ ^[Yy]$ ]]; then - echo "Restore cancelled." - return - fi - - echo "Restoring SSH configuration..." - sudo cp "$backup" /etc/ssh/sshd_config - if [ $? -ne 0 ]; then - echo "Error: Failed to restore SSH configuration." - exit 1 - fi - - sudo service ssh restart - if [ $? -ne 0 ]; then - echo "Error: Failed to restart the SSH service." - exit 1 - fi - - echo "SSH configuration has been restored." - - read -rp "Do you want to keep the backup file? (Y/n): " keep_backup - if [[ "$keep_backup" =~ ^[Nn]$ ]]; then - sudo rm "$backup" - echo "Backup file deleted." - else - echo "Backup file kept at: $backup" - fi -} - -# Function to validate if a given input is a valid port number -validate_port() { - local port="$1" - if ! [[ "$port" =~ ^[0-9]+$ ]] || ((port < 1 || port > 65535)); then - echo "Error: Invalid port number. Please enter a valid numeric port between 1 and 65535." - return 1 # Invalid port - fi - return 0 # Valid port -} +# ============================================================================== +# Git +# ============================================================================== -# Function to configure Git configure_git() { while true; do clear @@ -1189,7 +1162,10 @@ configure_git_user() { echo "Git has been configured with name: $git_name, email: $git_email, and default branch: $git_default_branch." } -# Function to configure fail2ban +# ============================================================================== +# Fail2ban +# ============================================================================== + configure_fail2ban() { clear echo "Choose the Fail2ban configuration to use:" @@ -1199,25 +1175,22 @@ configure_fail2ban() { # Read user input read -rp "Enter your choice (1/2): " fail2ban_config_choice + echo "Installing Fail2ban..." + apt_install fail2ban + case $fail2ban_config_choice in 1) - echo "Installing Fail2ban with default configuration..." - # Install Fail2ban - sudo apt install fail2ban -y + echo "Using default Fail2ban configuration." ;; 2) read -rp "Enter the URL of the user custom configuration: " fail2ban_custom_config_url # Check if the URL is valid and accessible if wget --spider "$fail2ban_custom_config_url" 2>/dev/null; then - # Install Fail2ban if not already installed - sudo apt install fail2ban -y sudo wget -O /etc/fail2ban/jail.local "$fail2ban_custom_config_url" echo "User custom Fail2ban configuration applied." else echo "Warning: Invalid URL or unable to reach the URL. Using the default configuration." - # Install Fail2ban with the default configuration - sudo apt install fail2ban -y fi ;; *) @@ -1228,21 +1201,45 @@ configure_fail2ban() { echo "Fail2ban configuration completed." } -# Function to configure a static IP address using Netplan +# ============================================================================== +# Static IP / Netplan +# ============================================================================== + +create_netplan_backup() { + local backup_dir="/etc/netplan/backups_decoscript" + local backup_timestamp + + if [ ! -d "$backup_dir" ]; then + sudo mkdir -p "$backup_dir" + fi + + backup_timestamp=$(date +%Y%m%d%H%M%S) + sudo cp "$NETPLAN_CONFIG" "$backup_dir/01-network-manager-all.yaml.$backup_timestamp" + echo "Backup created: $backup_dir/01-network-manager-all.yaml.$backup_timestamp" +} + +detect_netplan_renderer() { + if systemctl is-active --quiet NetworkManager 2>/dev/null; then + echo "NetworkManager" + else + echo "networkd" + fi +} + configure_static_ip() { clear echo "Configuring a static IP address using Netplan." # Check if Netplan is installed, and if not, install it - if ! [ -x "$(command -v netplan)" ]; then + if ! command_exists netplan; then echo "Netplan is not installed. Installing..." - sudo apt update && sudo apt install netplan.io -y + apt_install netplan.io fi # Check if ifconfig is installed, and if not, install it - if ! [ -x "$(command -v ifconfig)" ]; then + if ! command_exists ifconfig; then echo "Ifconfig (net-tools) is not installed. Installing..." - sudo apt update && sudo apt install net-tools -y + apt_install net-tools fi # Get network device information from 'ifconfig -a' @@ -1275,28 +1272,21 @@ configure_static_ip() { fi # Backup existing netplan configs before making changes - local backup_dir="/etc/netplan/backups_decoscript" - if [ ! -d "$backup_dir" ]; then - sudo mkdir -p "$backup_dir" - fi - - local backup_timestamp=$(date +%Y%m%d%H%M%S) - if [ -f "/etc/netplan/01-network-manager-all.yaml" ]; then - sudo cp "/etc/netplan/01-network-manager-all.yaml" "$backup_dir/01-network-manager-all.yaml.$backup_timestamp" - echo "Backup created: $backup_dir/01-network-manager-all.yaml.$backup_timestamp" + if [ -f "$NETPLAN_CONFIG" ]; then + create_netplan_backup fi # Determine renderer based on what's actually being used - local renderer="networkd" - if systemctl is-active --quiet NetworkManager 2>/dev/null; then + local renderer + renderer=$(detect_netplan_renderer) + if [ "$renderer" = "NetworkManager" ]; then echo "NetworkManager is active, using NetworkManager renderer." - renderer="NetworkManager" else echo "Using networkd renderer (systemd-networkd)." fi # Create a Netplan configuration file for the static IP address - cat </dev/null + cat </dev/null network: version: 2 renderer: $renderer @@ -1311,7 +1301,7 @@ network: EOL # Set correct permissions (netplan requires 600 or 640) - sudo chmod 600 /etc/netplan/01-network-manager-all.yaml + sudo chmod 600 "$NETPLAN_CONFIG" echo "Set netplan file permissions to 600 (owner read/write only)." # Apply the Netplan configuration @@ -1327,14 +1317,14 @@ revert_static_ip() { echo "========================================" # Check if the netplan file exists - if [ ! -f "/etc/netplan/01-network-manager-all.yaml" ]; then - echo "No static IP configuration found at /etc/netplan/01-network-manager-all.yaml" + if [ ! -f "$NETPLAN_CONFIG" ]; then + echo "No static IP configuration found at $NETPLAN_CONFIG" echo "Nothing to revert." return fi echo "This will remove the static IP configuration and revert to DHCP." - echo "Current configuration file: /etc/netplan/01-network-manager-all.yaml" + echo "Current configuration file: $NETPLAN_CONFIG" echo read -rp "Do you want to proceed? (y/N): " confirm_revert @@ -1344,7 +1334,7 @@ revert_static_ip() { fi # Get network device information - if [ -x "$(command -v ifconfig)" ]; then + if command_exists ifconfig; then device_info=$(sudo ifconfig -a) echo "Available network devices:" echo "$device_info" @@ -1360,27 +1350,19 @@ revert_static_ip() { return fi - # Create backup before reverting - local backup_dir="/etc/netplan/backups_decoscript" - if [ ! -d "$backup_dir" ]; then - sudo mkdir -p "$backup_dir" - fi - - local backup_timestamp=$(date +%Y%m%d%H%M%S) - sudo cp "/etc/netplan/01-network-manager-all.yaml" "$backup_dir/01-network-manager-all.yaml.$backup_timestamp" - echo "Backup created: $backup_dir/01-network-manager-all.yaml.$backup_timestamp" + create_netplan_backup # Determine renderer - local renderer="networkd" - if systemctl is-active --quiet NetworkManager 2>/dev/null; then + local renderer + renderer=$(detect_netplan_renderer) + if [ "$renderer" = "NetworkManager" ]; then echo "NetworkManager is active, using NetworkManager renderer." - renderer="NetworkManager" else echo "Using networkd renderer (systemd-networkd)." fi # Create DHCP configuration - cat </dev/null + cat </dev/null network: version: 2 renderer: $renderer @@ -1391,24 +1373,26 @@ network: EOL # Set correct permissions - sudo chmod 600 /etc/netplan/01-network-manager-all.yaml + sudo chmod 600 "$NETPLAN_CONFIG" # Apply the configuration echo "Applying DHCP configuration..." - sudo netplan apply - - if [ $? -eq 0 ]; then + + if sudo netplan apply; then echo echo "Successfully reverted to DHCP configuration." echo "Your network device '$network_device' will now obtain IP address automatically." else echo echo "Error: Failed to apply netplan configuration." - echo "You can restore from backup: $backup_dir/01-network-manager-all.yaml.$backup_timestamp" + echo "You can restore from the backup shown above." fi } -# Main script +# ============================================================================== +# Main +# ============================================================================== + check_sudo_privileges while true; do