#!/usr/bin/env bash
# Ensure bash path is found

# shellcheck source=/etc/os-release
# use shellcheck to declare which file to source

# Using set to make the script safer
set -e          # Exit on error
set -u          # Exit on undefined variable
set -x          # Print commands for debugging
set -a          # Export all variables
set -C          # Disable overwriting of files
set -o pipefail # Exit on pipe error
# set -euxaCo pipefail # All options

# Use the built-in trap command to catch error lines and signal numbers
trap 'printf "Error on line %d with signal %s" "$LINENO" "$?"' ERR # Exit on error
# Also use trap to catch interrupt signals and exit cleanly with a message to the user and a return code
trap 'printf "Interrupted on line %d with signal %s" "$LINENO" "$?"' INT SIGHUP SIGINT SIGTERM

# Get script name using parameter expansion to not spawn a new subprocess
SCRIPT_NAME="${0##*/}"

# We will be using printf instead of echo because it is more standardised.
# Also we will be using the test command's functionality as
# [[ because like this it constitutes a keyword and not a command.
# Functions will be defined as function "name" {body}" to make them
# more clear and () will not be used since using the keyword function renders them redundant

# We are taking for granted that the os-release file is in /etc as
# it has become a standard in most GNU/Linux distributions using systemd.
# We'll be using the -e flag just in case it is actually a symlink to another location.
# The get_distro function will use short if statements to check for the os-release file existence and readability.
# Then it will source it and output the distribution's name or exit in case of failure of either case.
function get_distro {
	if [[ -e /etc/os-release ]] && [[ -r /etc/os-release ]]; then # Check if file exists and is readable
		. /etc/os-release                                            # Source the file
		printf "%s" "$NAME"                                          # Output the distribution's name
	else                                                          # If the file does not exist or is not readable
		printf "%s" "File os-release not found or not readable"      # Output error message
		exit 1                                                       # Exit with error code 1
	fi
}

# The get_package_manager function will take the output of the get_distro function and determine
# which is the package manager used for the most popular server distros and exit if it is not found.
function get_package_manager {
	local distro                    # Declare distro as a local variable
	distro="$(get_distro)"          # Get the distribution name
	case "$distro" in               # Use case to check for the distribution name
		"Ubuntu" | "Debian GNU/Linux") # If the distribution is Ubuntu or Debian
			printf "%s" "apt"             # Output apt
			;;
		"CentOS Linux" | "Fedora" | "Red Hat Enterprise Linux Server") # If the distribution is CentOS, Fedora or RHEL
			printf "%s" "dnf"                                             # Output dnf
			;;

		"openSUSE Leap")      # If the distribution is OpenSUSE
			printf "%s" "zypper" # Output zypper
			;;
		*)
			# If the distribution is none of the above, output unsupported distribution
			# and exit with error code 1
			printf "%s" "Unsupported distribution"
			exit 1 # Exit with error code 1
			;;
	esac
}

# The install_packages function will take the output of the get_package_manager function and install any
# package passed as an argument to it. It will also check if the package manager is known and exit if it is not.
function install_packages {
	local package_manager                    # Declare package_manager as a local variable
	package_manager="$(get_package_manager)" # Get the package manager
	case "$package_manager" in               # Use case to check for the package manager
		"apt")                                  # If the package manager is apt
			sudo apt update                        # Update the package list
			sudo apt install -y "$@"               # Install the packages passed as arguments
			;;
		"dnf")                    # If the package manager is dnf
            sudo dnf upgrade -y      # Update the package list
			sudo dnf install -y "$@" # Install the packages passed as arguments
			;;
		"zypper")                    # If the package manager is zypper
            sudo zypper update -y       # Update the package list
			sudo zypper install -y "$@" # Install the packages passed as arguments
			;;
		*)
			# If the package manager is not one of the above, output unsupported package manager
			# and exit with error code 1
			printf "%s" "Unsupported package manager"
			exit 1 # Exit with error code 1
			;;
	esac
}

# The check_dependencies function will check if the dependencies defined in a local array are not installed
# and store the ones that are indeed absent in another local array.
# Then it will install the packages that are missing by invoking the install_packages function.
function check_dependencies {
	local dependencies=(curl git sudo vim ssh docker docker-compose wget) # Declare dependencies as a local array
	#> see what to do with name differences between distros if any <#
	local missing_dependencies=()              # Declare missing_dependencies as a local array
	for dependency in "${dependencies[@]}"; do # Loop through the dependencies array
		# If the dependency is not installed, add it to the missing_dependencies array
		! command -v "$dependency" &> /dev/null && missing_dependencies+=("$dependency")
	done
	# If the missing_dependencies array is not empty, install the packages
	[[ ${#missing_dependencies[@]} -ne 0 ]] && install_packages "${missing_dependencies[@]}"
}

# Global array of the service names to be restarted
services=()

# The hardenSSH function will use sed to modify the sshd_config file to have the following settings:
# - Allow ssh access to users in the sudo group only
# - Change the port to 22100 if it is available
# - Configure idle timeout to 5 minutes
# - Limit the number of authentication attempts to 3
# - Disable root login
# - Disable empty passwords
# - Disable ssh protocol 1
# - Disable password authentication and only allow public key authentication
# - Disable X11 forwarding for security reasons (X11 forwarding is not needed for ssh)
# - Disable agent forwarding to prevent ssh-agent hijacking
# Then it will store the sshd service name in the services array.
function hardenSSH {
	# Check if the sshd_config file exists and is readable
	# If it is, then modify it using sed and restart the sshd service
	# If it is not, then output an error message and exit with error code 1
	# The -i flag is used to modify the file in place
	# We split the sed command into multiple lines for readability purposes
	# and to avoid calling it multiple times
	if [[ -e /etc/ssh/sshd_config ]] && [[ -r /etc/ssh/sshd_config ]]; then
		sed -i \
			-e 's/^#AllowGroups.*/AllowGroups sudo/' \
			-e 's/^#Port.*/Port 22100/' \
			-e 's/^#ClientAliveInterval.*/ClientAliveInterval 300/' \
			-e 's/^#ClientAliveCountMax.*/ClientAliveCountMax 3/' \
			-e 's/^#PermitRootLogin.*/PermitRootLogin no/' \
			-e 's/^#PermitEmptyPasswords.*/PermitEmptyPasswords no/' \
			-e 's/^#Protocol.*/Protocol 2/' \
			-e 's/^#PasswordAuthentication.*/PasswordAuthentication no/' \
			-e 's/^#X11Forwarding.*/X11Forwarding no/' \
			-e 's/^#AllowAgentForwarding.*/AllowAgentForwarding no/' \
			/etc/ssh/sshd_config
		services+=("sshd") # Add sshd to the services array
	else
		printf "%s" "File sshd_config not found or not readable"
		exit 1
	fi
}

# The main function will call the check_dependencies function and exit if it fails.
# It will also output a message to the user to let them know that the script has finished.
function main {
	check_dependencies || exit 1  # Check dependencies and exit if it fails
    harden_ssh || exit 1          # Harden ssh and exit if it fails
	printf "%s" "Script finished" # Output message to the user
}

# # The am_i_root function will check if the user is root and exit if they are not.
# function am_i_root {
# 	if [[ $EUID -ne 0 ]]; then        # Check if the user is root
# 		printf "%s" "Please run as root" # Output message to the user
# 		exit 1                           # Exit with error code 1
# 	fi
# }
#
# # The getArgs function will get the arguments passed to the script and store them in an array.
# # It will also check if the arguments are valid and exit if they are not.
# function getArgs {
# 	local args=()            # Declare args as a local array
# 	while [[ $# -gt 0 ]]; do # Loop through the arguments
# 		case "$*" in            # Use case to check for the arguments
# 			--help | -h)           # If the argument is --help or -h
# 				printf "%s" "Usage: $SCRIPT_NAME [OPTION]..."
# 				;;
# 			--create-user | -cu) # If the argument is --create-user or -cu
# 				args+=("$1")        # Add the argument to the args array
# 				shift               # Shift the arguments
# 				case "$*" in
# 					--username=* | -u=*) # If the argument is --username=* or -u=*
# 						args+=("$1")        # Add the argument to the args array
# 						shift               # Shift the arguments
# 						;;
# 					--password=* | -p=*) # If the argument is --password=* or -p=*
# 						args+=("$1")        # Add the argument to the args array
# 						shift               # Shift the arguments
# 						;;
# 					*)
# 						printf "%s" "Invalid argument: $1"
# 						exit 1
# 						;;
# 				esac
# 				create_user "${args[@]}" # Call the create_user function with the args array as arguments
# 				;;
# 			--harden-ssh | -hs)      # If the argument is --harden-ssh or -hs
# 				args+=("$1")            # Add the argument to the args array
# 				shift                   # Shift the arguments
# 				harden_ssh "${args[@]}" # Call the harden_ssh function with the args array as arguments
# 				;;
# 		esac
# 	done
# 	printf "%s" "${args[@]}" # Output the args array
# }
#
# # The createUser function will create a new user with the username and password passed as arguments.
# function createUser {
# 	# Declare username as a local variable and assign it the first argument passed to the function
# 	local username="$1"
# 	# Declare password as a local variable and assign it the second argument passed to the function
# 	local password="$2"
# 	# Check if the user already exists and exit if they do
# 	if id -u "$username" &> /dev/null; then
# 		printf "%s" "User already exists"
# 		exit 1
# 	fi
# 	# Create the user and add them to the sudo group
# 	useradd -m -G sudo "$username"
# 	# Set the user's password using printf to avoid the password being echoed to the terminal
# 	printf "%s" "$username:$password" | chpasswd
# }

# Call the main function
main
# am_i_root

exit 0 # The right and proper way to exit a script
