nihilok@home:~$

Server setup script

Building on a previous post, here is a script that automates the setup of a server. When setting up a new server there are several things that I either do by default (on autopilot) or I forget to do. This script installs and configures various components such as nginx, certbot for Let’s Encrypt SSL certificates, fail2ban, ufw, and more. It also provides an option to disable password authentication and root login via SSH for added security. Here’s the script in full:

#!/usr/bin/env bash

set -euo pipefail

IFS=$'\n\t'

# Function to display error messages
error_exit() {
	echo "Error: $1" >&2
	exit 1
}

# Ensure the script is run as root
if [[ $EUID -ne 0 ]]; then
	error_exit "This script must be run as root. Use or switch to the root user."
fi

# Logging
LOG_FILE="/var/log/server-setup.sh.log"
exec > >(tee -a "$LOG_FILE") 2>&1

# Check which package manager is available and set the update and install commands
if [ -x "$(command -v apt-get)" ]; then
	PKG_UPDATE_CMD="apt-get update"
	PKG_INSTALL_CMD="apt-get install -y"
elif [ -x "$(command -v apk)" ]; then
	PKG_UPDATE_CMD="apk update"
	PKG_INSTALL_CMD="apk add"
elif [ -x "$(command -v dnf)" ]; then
	PKG_UPDATE_CMD="dnf check-update"
	PKG_INSTALL_CMD="dnf install -y"
elif [ -x "$(command -v yum)" ]; then
	PKG_UPDATE_CMD="yum check-update"
	PKG_INSTALL_CMD="yum install -y"
elif [ -x "$(command -v pacman)" ]; then
	PKG_UPDATE_CMD="pacman -Sy"
	PKG_INSTALL_CMD="pacman -S --noconfirm"
else
	echo "Unsupported package manager" >&2
	exit 1
fi

# Check if system uses service or systemctl
if [ -x "$(command -v systemctl)" ]; then
	function start {
		systemctl start $1
		systemctl enable $1
	}
	function restart {
		systemctl restart $1
	}
elif [ -x "$(command -v service)" ]; then
	function start {
		service $1 start
		service $1 enable
	}
	function restart {
		service $1 restart
	}
else
	echo "Unsupported service manager" >&2
	exit 1
fi

# Update and upgrade
echo "Updating package repositories..."
eval $PKG_UPDATE_CMD >/dev/null 2>&1

# Install packages
echo "Installing required packages..."
{
	eval $PKG_INSTALL_CMD git python3-dev python3-pip python3-venv python3-wheel
	eval $PKG_INSTALL_CMD curl
	eval $PKG_INSTALL_CMD nginx
	start nginx
	eval $PKG_INSTALL_CMD certbot python3-certbot-nginx
	eval $PKG_INSTALL_CMD sqlite3
	eval $PKG_INSTALL_CMD fail2ban
	start fail2ban
	eval $PKG_INSTALL_CMD ufw
	ufw allow 'Nginx Full'
	ufw allow 'OpenSSH'
} >/dev/null 2>&1

# Ask user if they would like to enable the firewall
read -p "Would you like to enable the firewall? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
	ufw enable >/dev/null 2>&1 <<<y
	echo "Firewall enabled."
else
	echo "Firewall not enabled. Please enable it manually if needed."
fi

# Create a new Nginx server block if the server name is provided and no existing configs are found
NGINX_SERVER_NAME=${1:-""}

NGINX_SERVER_CONFIG="\
server {
    listen 80;
    server_name $NGINX_SERVER_NAME;
    root /var/www/html;
    location / {
        try_files \$uri \$uri/ =404;
    }
}"

if [ "$NGINX_SERVER_NAME" != "" ]; then
	# Remove default Nginx server block if it exists
	if [ -f /etc/nginx/sites-enabled/default ]; then
		echo "Removing link to default nginx server block..."
		rm /etc/nginx/sites-enabled/default >/dev/null 2>&1 || true
	fi

	# Create a new Nginx server block if no existing configs are found except the default
	if [ ! -f /etc/nginx/sites-enabled/* ]; then
		echo "Creating a new Nginx server block..."
		echo "$NGINX_SERVER_CONFIG" >/etc/nginx/sites-available/current
		ln -s /etc/nginx/sites-available/current /etc/nginx/sites-enabled/
		restart nginx
		echo "Check/update the Nginx server block configuration at /etc/nginx/sites-available/current"
	else
		echo "Existing Nginx server config found. No changes made."
	fi
fi

# Ask user if they would like to disable password authentication
read -p "Would you like to disable password authentication? (y/N): " -n 1 -r
echo
if ! [[ $REPLY =~ ^[Yy]$ ]]; then
	echo "Server setup complete!"
	exit 0
fi

# Display warning and prompt for confirmation
echo "************************************************************"
echo "WARNING: This script will now disable SSH password authentication"
echo "         and root login. Ensure you have SSH key-based access"
echo "         configured for all necessary user accounts."
echo "         Disabling these settings without proper SSH keys"
echo "         can lock you out of the server."
echo "************************************************************"
read -p "Do you want to proceed? (y/N): " -n 1 -r
echo

# Check user confirmation
if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then
	echo "Operation cancelled by the user."
	echo "Server setup complete!"
	exit 0
fi

SSHD_CONFIG="/etc/ssh/sshd_config"
BACKUP_FILE="/etc/ssh/sshd_config.backup_$(date +%F_%T)"

# Backup the current SSH configuration
cp "$SSHD_CONFIG" "$BACKUP_FILE" || error_exit "Failed to create backup of $SSHD_CONFIG."
echo "Backup of sshd_config created at $BACKUP_FILE"

# Function to update or add a configuration directive
update_config() {
	local directive="$1"
	local value="$2"

	if grep -q "^\s*#\?\s*${directive}\s\+" "$SSHD_CONFIG"; then
		# Uncomment and set the directive
		sed -i "s|^\s*#\?\s*${directive}\s\+.*|${directive} ${value}|g" "$SSHD_CONFIG"
	else
		# Append the directive at the end of the file
		echo "${directive} ${value}" >>"$SSHD_CONFIG"
	fi
}

# Disable password authentication
update_config "PasswordAuthentication" "no"

# Disable root login
update_config "PermitRootLogin" "no"

# Disable empty passwords for added security
update_config "PermitEmptyPasswords" "no"

# Disable challenge-response authentication
update_config "ChallengeResponseAuthentication" "no"

# Check SSH configuration syntax
echo "Checking SSH configuration syntax..."
if ! sshd -t; then
	echo "SSH configuration syntax is invalid. Restoring the original configuration."
	cp "$BACKUP_FILE" "$SSHD_CONFIG" || error_exit "Failed to restore the original sshd_config."
	exit 1
fi

# Function to restart SSH service
restart_sshd() {
	local services=("sshd" "ssh")
	for service in "${services[@]}"; do
		if systemctl list-units --type=service | grep -q "${service}.service"; then
			systemctl restart "$service" && return 0
		elif service --status-all 2>/dev/null | grep -q "$service"; then
			service "$service" restart && return 0
		fi
	done
	return 1
}

# Prompt to restart SSH service
read -p "Do you want to restart the SSH service now? (y/N): " -n 1 -r
echo
if [[ "$REPLY" =~ ^[Yy]$ ]]; then
	echo "Restarting SSH service..."
	if ! restart_sshd; then
		error_exit "Failed to restart SSH service. Please restart it manually."
	fi
else
	echo "Please restart the SSH service manually when ready."
fi

echo "SSH password authentication and root login have been successfully disabled."
echo "Server setup complete! Please verify that you can log in using SSH keys before closing your current session."
echo
exit 0

This script can be downloaded from here and run on a server with the following commands:

curl https://gist.githubusercontent.com/nihilok/d57ce168bc643e2ac18c0b54a7877aba/raw/server-setup.sh -o server-setup.sh
chmod +x server-setup.sh
sudo ./server-setup.sh example.com

You can omit the server name arg (example.com) if you don’t want to create an Nginx server block. The script will install and configure the other components without creating an Nginx server block.