This script is meant to be run on a fresh Ubuntu LTS installation.
Last active
March 25, 2025 11:46
-
-
Save dot-mike/8a1140535e11f54323e2be126ccd8e69 to your computer and use it in GitHub Desktop.
Inital setup script for Ubuntu server
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
great job !