Skip to content

Instantly share code, notes, and snippets.

@dot-mike
Last active March 25, 2025 11:46
Show Gist options
  • Save dot-mike/8a1140535e11f54323e2be126ccd8e69 to your computer and use it in GitHub Desktop.
Save dot-mike/8a1140535e11f54323e2be126ccd8e69 to your computer and use it in GitHub Desktop.
Inital setup script for Ubuntu server

Inital Ubuntu setup script

This script is meant to be run on a fresh Ubuntu LTS installation.

#!/usr/bin/env bash
# Initial setup script for Ubuntu LTS with optional feature sets and extra packages
VERSION="1.0.0"
set -e -o pipefail
function getCurrentDir() {
local current_dir="${BASH_SOURCE%/*}"
if [[ ! -d "${current_dir}" ]]; then current_dir="$PWD"; fi
echo "${current_dir}"
}
current_dir=$(getCurrentDir)
log_file="output.log"
# Define package lists
global_packages=(
curl
wget
htop
vim
screen
unzip
bash-completion
locate
jq
)
development_packages=(
git
build-essential
python3
python3-pip
build-essential
cmake
autoconf
libtool
)
server_packages=(
fail2ban
ufw
)
workstation_packages=(
)
bloat_packages=(
apport
apport-symptoms
popularity-contest
snapd
snap
telnet
)
# Helper function to get system path with mock support
function get_sys_path() {
echo "${MOCK_FS_ROOT:-}$1"
}
# Helper function to ensure directory exists
function ensure_dir() {
local dir=$(dirname "$1")
sudo mkdir -p "$dir"
}
# Helper function to run system commands only in non-mock environment
function run_if_not_mock() {
if [[ -z "${MOCK_FS_ROOT}" ]]; then
"$@"
fi
}
# Update constants to use helper
SUDOERS_BACKUP="$(get_sys_path /etc/sudoers.bak)"
SSH_CONFIG="$(get_sys_path /etc/ssh/sshd_config)"
function preChecks() {
echo "Running pre-checks..."
if [[ $(df / | tail -1 | awk '{print $4}') -lt 4000000 ]]; then
echo "Warning: Less than 4GB of free disk space available."
fi
if [[ -f "${MOCK_FS_ROOT:-}/var/run/reboot-required" ]]; then
echo "Warning: A reboot is required before proceeding."
exit 1
fi
}
# Install development packages
function setupDevelopmentEnvironment() {
echo "Setting up Development Environment..."
sudo apt-get install -y "${development_packages[@]}"
}
# Install server packages
function setupServerEnvironment() {
echo "Setting up Server Environment..."
sudo apt-get install -y "${server_packages[@]}"
}
# Install workstation workstation packages
function setupWorkstationEnvironment() {
echo "Setting up Workstation Environment..."
sudo apt-get install -y "${workstation_packages[@]}"
}
# Install extra packages
function installExtraPackages() {
local extra_packages=()
echo "The default extra packages are: ${global_packages[*]}"
echo "You can edit this list interactively."
echo "Current extra packages:"
for pkg in "${global_packages[@]}"; do
echo "$pkg"
done
read -rp "Do you want to modify the list of extra packages? [Y/N]: " modify_packages
if [[ $modify_packages =~ ^[Yy]$ ]]; then
echo "Enter the packages you want to install, separated by spaces:"
read -ra extra_packages
else
extra_packages=("${global_packages[@]}")
fi
if [[ ${#extra_packages[@]} -gt 0 ]]; then
echo "Installing extra packages: ${extra_packages[*]}"
sudo apt-get install -y "${extra_packages[@]}"
else
echo "No extra packages selected for installation."
fi
}
# Update the user account
# Arguments:
# Account Username
function updateUserAccount() {
local username=${1}
sudo passwd -d "${username}"
sudo usermod -aG sudo "${username}"
}
# Add the new user account
# Arguments:
# Account Username
# Flag to determine if user account is added silently. (With / Without GECOS prompt)
function addUserAccount() {
local username=${1}
local silent_mode=${2}
if [[ ${silent_mode} == "true" ]]; then
sudo adduser --disabled-password --gecos '' "${username}"
else
sudo adduser --disabled-password "${username}"
fi
sudo usermod -aG sudo "${username}"
sudo passwd -d "${username}"
}
# Disables the sudo password prompt for a user account by editing /etc/sudoers
# Arguments:
# Account username
function disableSudoPassword() {
local username="${1}"
sudo cp /etc/sudoers "${SUDOERS_BACKUP}"
sudo bash -c "echo '${1} ALL=(ALL) NOPASSWD: ALL' | (EDITOR='tee -a' visudo)"
}
# Execute a command as a certain user
# Arguments:
# Account Username
# Command to be executed
function execAsUser() {
local username=${1}
local exec_command=${2}
sudo -u "${username}" -H bash -c "${exec_command}"
}
# Add the local machine public SSH Key for the new user account
# Arguments:
# Account Username
# Public SSH Key
function addSSHKey() {
local username=${1}
local sshKey=${2}
execAsUser "${username}" "mkdir -p ~/.ssh; chmod 700 ~/.ssh; touch ~/.ssh/authorized_keys"
execAsUser "${username}" "echo \"${sshKey}\" | sudo tee -a ~/.ssh/authorized_keys"
execAsUser "${username}" "chmod 600 ~/.ssh/authorized_keys"
}
# Modify the sshd_config file
# Arguments:
# Setting
# Value
function changeSSHConfig() {
echo "Select SSH configuration changes to apply:"
echo "1. Disable root login"
echo "2. Disable password authentication"
read -rp "Enter your choice (1/2): " ssh_choice
case "$ssh_choice" in
1)
update_sshd_config "PermitRootLogin" "no"
;;
2)
update_sshd_config "PasswordAuthentication" "no"
;;
*)
echo "Invalid choice. Skipping SSH configuration changes."
;;
esac
}
# Setup the Uncomplicated Firewall
function setupUfw() {
sudo apt-get install ufw
sudo ufw allow OpenSSH
sudo ufw enable
}
# Gets the amount of physical memory in GB (rounded up) installed on the machine
function getPhysicalMemory() {
local phymem
phymem="$(free -g|awk '/^Mem:/{print $2}')"
if [[ ${phymem} == '0' ]]; then
echo 1
else
echo "${phymem}"
fi
}
function createSwap() {
local swapmem=$(($(getPhysicalMemory) * 2))
local swapfile="${MOCK_FS_ROOT:-}/swapfile"
# Anything over 4GB in swap is probably unnecessary as a RAM fallback
if [ ${swapmem} -gt 4 ]; then
swapmem=4
fi
sudo fallocate -l "${swapmem}G" "${swapfile}"
sudo chmod 600 "${swapfile}"
sudo mkswap "${swapfile}"
sudo swapon "${swapfile}"
}
// ...existing code...
# Mount the swapfile
function mountSwap() {
sudo cp /etc/fstab /etc/fstab.bak
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
}
# Modify the swapfile settings
# Arguments:
# new vm.swappiness value
# new vm.vfs_cache_pressure value
function tweakSwapSettings() {
local swappiness=${1}
local vfs_cache_pressure=${2}
sudo sysctl vm.swappiness="${swappiness}"
sudo sysctl vm.vfs_cache_pressure="${vfs_cache_pressure}"
}
# Save the modified swap settings
# Arguments:
# new vm.swappiness value
# new vm.vfs_cache_pressure value
function saveSwapSettings() {
local swappiness=${1}
local vfs_cache_pressure=${2}
echo "vm.swappiness=${swappiness}" | sudo tee -a /etc/sysctl.conf
echo "vm.vfs_cache_pressure=${vfs_cache_pressure}" | sudo tee -a /etc/sysctl.conf
}
# Set the machine's timezone
# Arguments:
# tz data timezone
function setTimezone() {
local timezone=${1}
local timezone_file="$(get_sys_path /etc/timezone)"
local localtime_file="$(get_sys_path /etc/localtime)"
local zoneinfo_file="$(get_sys_path /usr/share/zoneinfo/${timezone})"
ensure_dir "${timezone_file}"
ensure_dir "${localtime_file}"
ensure_dir "${zoneinfo_file}"
echo "${timezone}" | sudo tee "${timezone_file}"
sudo ln -fs "${zoneinfo_file}" "${localtime_file}"
run_if_not_mock sudo dpkg-reconfigure -f noninteractive tzdata
}
# Configure Network Time Protocol
function configureNTP() {
echo -ne "Enter the NTP server(s) to use (space separated, default is 'ntp.ubuntu.com'):\n" >&3
read -r ntp_servers
if [ -z "${ntp_servers}" ]; then
ntp_servers="ntp.ubuntu.com"
fi
local timesyncd_conf="$(get_sys_path /etc/systemd/timesyncd.conf)"
ensure_dir "${timesyncd_conf}"
cat <<EOF | sudo tee "${timesyncd_conf}"
[Time]
NTP=${ntp_servers}
EOF
run_if_not_mock sudo timedatectl set-ntp true
run_if_not_mock sudo systemctl enable systemd-timesyncd
run_if_not_mock sudo systemctl restart systemd-timesyncd
}
# Reverts the original /etc/sudoers file before this script is ran
function revertSudoers() {
sudo cp "${SUDOERS_BACKUP}" /etc/sudoers
sudo rm -rf "${SUDOERS_BACKUP}"
}
function setupSwap() {
createSwap
mountSwap
tweakSwapSettings "10" "50"
saveSwapSettings "10" "50"
}
function hasSwap() {
[[ "$(sudo swapon -s)" == *"/swapfile"* ]]
}
function cleanup() {
if [[ -f "${SUDOERS_BACKUP}" ]]; then
revertSudoers
fi
}
function disableMothNews() {
echo "Disabling motd-news..."
local motd_file="$(get_sys_path /etc/default/motd-news)"
if [ -f "${motd_file}" ]; then
sed -i 's/ENABLED=1/ENABLED=0/g' "${motd_file}"
fi
run_if_not_mock systemctl disable --now motd-news.timer
run_if_not_mock systemctl disable --now motd-news.service
}
function removeBloat() {
echo "The following bloatware packages will be removed: ${bloat_packages[*]}"
if [[ -z "${MOCK_FS_ROOT}" ]]; then
apt-get -qy purge "${bloat_packages[@]}"
systemctl daemon-reload
rm -rf /root/snap
rm -rf /var/cache/snapd/
fi
}
function update_sshd_config() {
local setting="$1"
local value="$2"
if grep -q "^${setting}" $SSH_CONFIG; then
sudo sed -i "s/^${setting}.*/${setting} ${value}/" $SSH_CONFIG
else
echo "${setting} ${value}" | sudo tee -a $SSH_CONFIG
fi
}
function logTimestamp() {
local filename=${1}
{
echo "==================="
echo "Log generated on $(date)"
echo "==================="
} >>"${filename}" 2>&1
}
function setupTimezone() {
echo -ne "Enter the timezone for the server (Default is 'Asia/Singapore'):\n" >&3
read -r timezone
if [ -z "${timezone}" ]; then
timezone="Asia/Singapore"
fi
setTimezone "${timezone}"
echo "Timezone is set to $(cat /etc/timezone)" >&3
}
function enableLegacyLogging() {
echo -ne "Enabling /var/log/messages for logging with rsyslog...\n" >&3
local rsyslog_conf="$(get_sys_path /etc/rsyslog.d/50-default.conf)"
run_if_not_mock dpkg -l | grep -q rsyslog || apt-get --yes --force-yes install rsyslog
ensure_dir "${rsyslog_conf}"
touch "${rsyslog_conf}"
cp "${rsyslog_conf}" "${rsyslog_conf}.bak"
if ! grep -Erq "^daemon\.\*" "$(get_sys_path /etc/rsyslog.*)"; then
echo "daemon.* /var/log/messages" >> "${rsyslog_conf}"
fi
}
function main() {
echo "====================================="
echo "Initial Setup Script for Ubuntu LTS"
echo "Version: ${VERSION}"
echo "====================================="
preChecks
echo "Select feature set (optional):"
echo "1. Development Environment (Packages: ${development_packages[*]})"
echo "2. Server Environment (Packages: ${server_packages[*]})"
echo "3. Client Workstation (Packages: ${workstation_packages[*]})"
echo "0. Skip feature set selection"
read -rp "Enter your choice (0/1/2/3): " feature_set_choice
case "$feature_set_choice" in
1)
feature_set="development"
;;
2)
feature_set="server"
;;
3)
feature_set="workstation"
;;
0)
feature_set="none"
;;
*)
echo "Invalid choice. Exiting..."
exit 1
;;
esac
echo "Select actions to perform (Enter numbers separated by spaces, e.g., '0 1 2'):"
echo "0. Update the system"
echo "1. Create a new user account"
echo "2. Modify SSH configuration"
echo "3. Add public SSH key for the new user"
echo "4. Setup UFW"
echo "5. Setup Swap"
echo "6. Setup Timezone"
echo "7. Configure NTP"
echo "8. Install extra packages"
echo "9. Remove bloatware"
echo "10. Enable automatic security updates"
echo "11. Enable /var/log/messages for logging"
read -rp "Enter your choice(s): " actions
# Parse user actions
IFS=' ' read -ra choices <<< "$actions"
# Confirm setup
read -rp "Do you want to continue with the selected actions? [Y/N] " continueSetup
if [[ $continueSetup != [yY] ]]; then
echo "Exiting script..."
exit 0
fi
echo "Starting setup script..."
# Log file setup (unchanged)
logTimestamp "${log_file}"
exec 3>&1
exec > >(tee -a ${log_file} )
exec 2> >(tee -a ${log_file} >&2)
# Execute selected actions
for action in "${choices[@]}"; do
case $action in
0)
echo "Updating the system... "
sudo apt-get update
sudo apt-get upgrade -y
;;
1)
read -rp "Enter the username of the new user account: " username
addUserAccount "${username}"
read -rp $'Paste in the public SSH key for the new user:\n' sshKey
addSSHKey "${username}" "${sshKey}"
echo -ne "Do you want to disable the sudo password prompt for the new user? [Y/N] " >&3
read -r disableSudo
if [[ $disableSudo == [yY] ]]; then
disableSudoPassword "${username}"
fi
;;
2)
changeSSHConfig
;;
3)
echo "Adding public SSH key..."
;;
4)
echo "Setting up UFW... " >&3
setupUfw
;;
5)
if ! hasSwap; then
echo "Setting up Swap... " >&3
setupSwap
fi
;;
6)
setupTimezone
;;
7)
configureNTP
;;
8)
installExtraPackages
;;
9)
removeBloat
;;
10)
echo "Enabling automatic security updates..."
sudo apt-get install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
;;
11)
enableLegacyLogging
;;
*)
echo "Invalid choice: $action"
;;
esac
done
sudo service ssh reload
# Feature set-specific setup
case "$feature_set" in
development)
setupDevelopmentEnvironment
;;
server)
setupServerEnvironment
;;
workstation)
setupWorkstationEnvironment
;;
none)
echo "Skipping feature set installation."
;;
esac
# Common setup
disableMothNews
sudo service ssh reload
echo "Setup Done! Log file is located at ${log_file}" >&3
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main
fi
@k1soltan
Copy link

great job !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment