#!/usr/bin/env bash set -euo pipefail shopt -s expand_aliases declare -r githubUrl='https://github.com/5ouma/homelab' declare -r homelab="$HOME/homelab" TMPDIR="$(mktemp -d)" && declare -r TMPDIR export GUM_CONFIRM_SELECTED_BACKGROUND='27' export GUM_FORMAT_THEME="$TMPDIR/blue.json" export GUM_INPUT_CURSOR_FOREGROUND='33' export GUM_SPIN_SPINNER_FOREGROUND='27' export GUM_WRITE_CURSOR_FOREGROUND='33' declare executed=false declare skipped=false declare goThrough=false declare pacmanArgs=(--noconfirm) getGum() { declare -r gumVer='0.17.0' declare -r arch="$(uname -m)" declare -r gumDir="gum_${gumVer}_$(uname)_${arch/aarch64/arm64}" declare -r gumUrl="https://github.com/charmbracelet/gum/releases/download/v$gumVer/$gumDir.tar.gz" declare -r gumThemeUrl="https://raw.githubusercontent.com/5ouma/dotfiles/main/data/blue.json" printf ' 🚚💨 Now delivering...' # shellcheck disable=SC2139 curl -sL "$gumUrl" | tar --strip-components=1 -zxC "$TMPDIR" "$gumDir/gum" && alias gumCom="$TMPDIR/gum" printf '\r ' curl -s "$gumThemeUrl" -o "$GUM_FORMAT_THEME" } && getGum gum() { case $1 in confirm) gumCom confirm "$2" --affirmative="$3" --negative="$4" ;; format) gumCom format "${@:2}" ;; input) gumCom input --placeholder="$2" ;; spin) gumCom spin --title="$2" -- "${@:4}" ;; style) gumCom style --foreground="$2" "${@:3}" ;; write) gumCom write --placeholder="$2" ;; executed) echo -en '\n ' && gumCom format '✅ Done!' executed=true ;; skipped) echo -en '\n ' && gumCom format '🟡 Skipped' ;; esac } alias skip='skipped=true && return' shouldProceed() { ($goThrough || gum confirm 'Proceed?' 'Yes' 'No') } run() { gum format "$2" eval '$1' if ($skipped); then skipped=false gum skipped else gum executed fi } helpMsg() { gum format < 🥼 Self-hosted personal laboratory ## Commands: - help Print help information ## Flags: - -y, --yes Start without confirmation EOM } errMsg() { declare -r stat=$1 func="${2/"$HOME"/~}" line=$3 gum format < **$(gum style 1 'Error')** > Unknown error has occurred! > - Status: $stat > - File or Function: $func > - Line Number: $line > - Process ID: $$ EOM } && trap 'errMsg $? "$0" $LINENO' ERR if [[ ! -x $0 ]]; then gum format < $githubUrl --- EOM fi if (($# > 0)); then case "$1" in 'help') helpMsg exit ;; '-y' | '--yes') goThrough=true ;; *) gum format < **$(gum style 1 'Error')** > Unknown flag $1 EOM helpMsg exit 1 ;; esac fi if (! $goThrough); then gum confirm "Let's start setup!" 'Yes!' 'No' || exit 1 pacmanArgs=() fi if [[ ! -d "$homelab" ]]; then # shellcheck disable=SC2016 gum spin 'Cloning the repository into `~/homelab`...' -- git clone "$githubUrl" "$homelab" else gum spin 'Pulling the homelab repository...' -- git -C "$homelab" pull fi sudo -v updateSystem() { gum spin 'Synchronizing package databases...' -- yay -Sy if (! yay -Qu &>/dev/null || ! shouldProceed); then skip fi yay -Su "${pacmanArgs[@]}" } && run 'updateSystem' '## 🔧 Update system' installPackages() { declare -r packages=(docker docker-compose firewalld jq pacman-contrib reflector tailscale) if (yay -Q "${packages[@]}" &>/dev/null || ! shouldProceed); then skip fi yay -S --needed "${pacmanArgs[@]}" "${packages[@]}" } && run 'installPackages' '## 📦 Install packages' enableServices() { declare -r services=(docker firewalld paccache.timer reflector.timer tailscaled) if (systemctl is-enabled "${services[@]}" | awk '$0 == "disabled" { exit 1 }' || ! shouldProceed); then skip fi gum spin 'Enabling services...' -- sudo systemctl enable --now "${services[@]}" } && run 'enableServices' '## 🎽 Enable system services' configureReflector() { declare -r country="$(curl -s https://ipapi.co/country_name)" if (grep -q -- "--country $country" /etc/xdg/reflector/reflector.conf && grep -q -- '--sort rate' /etc/xdg/reflector/reflector.conf || ! shouldProceed); then skip fi gum spin 'Setting auto country selection...' -- sudo sed -i "s/^# --country France,Germany/--country $country/" /etc/xdg/reflector/reflector.conf gum spin 'Setting sorting by rate...' -- sudo sed -i 's/^--sort age/--sort rate/' /etc/xdg/reflector/reflector.conf } && run 'configureReflector' '## 🪞 Configure Reflector' addToDockerGroup() { if (groups "$USER" | grep -q docker || ! shouldProceed); then skip fi gum spin 'Adding user to Docker group...' -- sudo usermod -aG docker "$USER" } && run 'addToDockerGroup' '## 🐳 Add user to Docker group' setHostname() { if [[ -n "$(hostnamectl status --pretty)" ]] || ! shouldProceed; then skip fi hostname=$(gum input 'Enter hostname') gum spin 'Setting hostname...' -- sudo hostnamectl set-hostname "${hostname// /-}" gum spin 'Setting pretty hostname...' -- sudo hostnamectl set-hostname --pretty "$hostname" } && run 'setHostname' '## 🖥️ Set system hostname' configureFirewall() { if (sudo firewall-cmd -q --query-service=mdns && sudo firewall-cmd -q --query-service=samba || ! shouldProceed); then skip fi gum spin 'Adding Samba and mDNS services...' -- sudo firewall-cmd --add-service={mdns,samba} --permanent gum spin 'Reloading firewall...' -- sudo firewall-cmd --reload } && run 'configureFirewall' '## 🔥 Configure firewall' configureBackupDrive() { if (grep -q /mnt/backup /etc/fstab || ! shouldProceed); then skip fi sudo blkid backupDriveUUID="$(gum input 'Enter backup drive UUID')" echo "UUID=$backupDriveUUID /mnt/backup ext4 defaults 0 0" | sudo tee -a /etc/fstab >/dev/null } && run 'configureBackupDrive' '## 💾 Configure backup drive' configureTailscale() { if (tailscale status &>/dev/null || ! shouldProceed); then skip fi sudo tailscale up --accept-routes --advertise-exit-node --ssh gum spin 'Setting Tailscale hostname...' -- sudo tailscale set --hostname="$(hostnamectl status --pretty)" } && run 'configureTailscale' '## 🛣️ Configure Tailscale' if ($executed); then gum format '# 🎉 Homelab setup completed!' gum confirm "Reboot the system?" 'Yes' 'No' && sudo reboot else gum format '# 🧊 Nothing has changed' fi