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