--- /dev/null
+#!/bin/bash
+
+# https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
+
+# Used with zero exit codes that go as planned.
+__log_info () {
+ local msg="$1"
+ local white="\033[1m"
+ local reset="\033[0m"
+ printf "${white}INFO${reset}\t%s\n" "$msg"
+}
+
+# Used with zero exit codes that do not go as planned.
+__log_warn () {
+ local msg="$1"
+ local yellow="\033[1;33m"
+ local reset="\033[0m"
+ printf "${yellow}WARN${reset}\t%s\n" "$msg"
+}
+
+# Used with non-zero exit codes.
+__log_error () {
+ local msg="$1"
+ local red="\033[1;31m"
+ local reset="\033[0m"
+ printf "${red}ERROR${reset}\t%s\n" "$msg"
+}
\ No newline at end of file
--- /dev/null
+#!/bin/bash
+
+# Function to assert that a package is installed
+assert_package_installed() {
+ local package_name="$1"
+ if ! dpkg -l "$package_name"; then
+ echo "Package $package_name is not installed."
+ return 1
+ fi
+ echo "Package $package_name is installed."
+ return 0
+}
+
+# Function to install server base packages
+install_server_base_packages() {
+ apt-get update \
+ && apt-get install -y \
+ apache2 \
+ git \
+ git-annex \
+ ufw \
+ fail2ban \
+ certbot \
+ python3-certbot-apache
+ echo "Server base packages installed."
+ return 0
+}
--- /dev/null
+#!/usr/bin/env bash
+
+# TODO: Rethink hardcode
+LIB_DIR=${LIB_DIR-/workspace/lib}
+. "$LIB_DIR"/__utils.sh
+
+# adds a system user with home directory
+add_user() {
+ useradd --create-home "$1"
+}
+
+# adds an SSH public key to the user's authorized_keys
+add_ssh() {
+ local user="$1"
+ local public_key="$2"
+ local ssh_dir="/home/$user/.ssh"
+ local key_file="$ssh_dir/authorized_keys"
+
+ # create .ssh directory if missing
+ mkdir --parents "$ssh_dir"
+ chmod 700 "$ssh_dir"
+
+ # TODO: Rewrite this to use scp to copy SSH
+ # append the public key and set permissions
+ echo "$public_key" >> "$key_file"
+ chmod 600 "$key_file"
+}
+
+# adds user to a group (preserves existing groups)
+add_user_to_group() {
+ local user="$1"
+ local group="$2"
+ usermod --append --groups "$group" "$user"
+}
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env bats
+
+######################
+# Setup and Teardown #
+######################
+setup () {
+ # Source library
+ local lib_dir
+ lib_dir="$(dirname "$(dirname "$BATS_TEST_FILENAME")")/lib"
+ source "$lib_dir"/packages.sh
+}
+
+teardown () {
+ # Technically, should uninstall packages
+ echo "Pass"
+}
+
+###############
+# Begin Tests #
+###############
+@test "assert_package_installed should fail for uninstalled packages" {
+ run assert_package_installed "nonsense"
+ [ "$status" -ne 0 ]
+}
+
+@test "install_server_base_packages installs all required packages" {
+ skip "Skipping long-running test"
+ run install_server_base_packages
+ [ "$status" -eq 0 ]
+ run assert_package_installed "apache2"
+ [ "$status" -eq 0 ]
+ run assert_package_installed "git"
+ [ "$status" -eq 0 ]
+ run assert_package_installed "git-annex"
+ [ "$status" -eq 0 ]
+ run assert_package_installed "ufw"
+ [ "$status" -eq 0 ]
+ run assert_package_installed "fail2ban"
+ [ "$status" -eq 0 ]
+ run assert_package_installed "certbot"
+ [ "$status" -eq 0 ]
+ run assert_package_installed "python3-certbot-apache"
+ [ "$status" -eq 0 ]
+}
--- /dev/null
+#!/usr/bin/env bats
+
+######################
+# Setup and Teardown #
+######################
+setup () {
+ # Source library
+ local lib_dir="$(dirname $(dirname "$BATS_TEST_FILENAME"))/lib"
+ source "$lib_dir"/users.sh
+
+ # Create user
+ add_user "balto"
+}
+
+teardown () {
+ userdel --remove --force "balto"
+}
+
+###############
+# Begin Tests #
+###############
+@test "users: add_user creates a user with home directory" {
+ # Test user existence and home directory existence
+ run id -u balto
+ [ "$status" -eq 0 ]
+ [ -d "/home/balto" ]
+}
+
+@test "users: add_ssh writes a public key to authorized_keys" {
+ skip "TODO: Use SCP to add key from one machine to the other"
+ local public_key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ== test-key"
+
+ # Add SSH key and check files
+ run add_ssh "balto" "$public_key"
+ [ "$status" -eq 0 ]
+ [ -f "/home/balto/.ssh/authorized_keys" ]
+ run grep -q "$public_key" "/home/balto/.ssh/authorized_keys"
+ [ "$status" -eq 0 ]
+}
+
+@test "users: add_user_to_group adds user to specified group" {
+ local group="sudo"
+
+ # Add to group and check
+ run add_user_to_group "balto" "$group"
+ [ "$status" -eq 0 ]
+ run groups "balto"
+ [[ "$output" == *"$group"* ]]
+}
\ No newline at end of file