#!/bin/sh DOTS_REPO="https://git.snaile.de/luca/dotfiles" DOTS_BRANCH="main" STOW_DIR=".local/share/stow" DOTS_PACKAGE="dots" USER_GROUPS="wheel" # Comma separated list SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" BOLD="$(tput bold)" RED="$(tput setaf 1)" GREEN="$(tput setaf 2)" YELLOW="$(tput setaf 3)" BLUE="$(tput setaf 4)" RESET="$(tput sgr0)" error() { printf "%b\n" "${RED}${BOLD}${1}${RESET}" >&2 exit 1 } prompt() { message=$1 tty=$2 printf "%b" "${BLUE}${message}${RESET}" >"$tty" # shellcheck disable=SC3037,SC2046 read -r x echo "$x" unset x } emphasize() { printf "%b\n" "${GREEN}${BOLD}${1}${RESET}" } info() { printf "%b\n" "${1}" } warn() { printf "%b\n" "${YELLOW}${BOLD}${1}${RESET}" } _loop_wrapper() { unset n unset total file=$1 message=$2 command=$3 skip_regex="^(#.*)?$" total=$(grep -cvP "$skip_regex" "$file") tput sc while read -r x; do echo "$x" | grep -qvP "$skip_regex" || continue n=$((n + 1)) eval "info \"(${n}/${total}) $message\"" eval "$command" tput rc tput el done <"$file" } check_root() { [ "$(id -u)" != "0" ] && error "This script needs root!" } setup() { tput sc info "Synchronizing XBPS index..." xbps-install -Syu >/dev/null 2>&1 || error "Failed to synchronize XBPS index!" tput rc tput el info "Installing ntp..." xbps-install -y ntp >/dev/null 2>&1 tput rc tput el info "Synchronizing time..." ntpdate "pool.ntp.org" >/dev/null 2>&1 || warn "Failed to synchronize time!" tput rc tput el info "Done!" } install_packages() { failed_packages="" #shellcheck disable=SC2016 _loop_wrapper "$1" \ 'Installing ${x}' \ 'xbps-install -y "$x" >/dev/null 2>&1 || failed_packages="${failed_packages} ${x}"' if [ -n "$failed_packages" ]; then tput rc tput el warn "Failed to install:${failed_packages}" else info "Done!" fi command -v git 1>/dev/null 2>&1 || error "git isn't installed even though it should be!" command -v stow 1>/dev/null 2>&1 || error "stow isn't installed even though it should be!" } install_files() { ( cd "${SCRIPT_DIR}/files" || exit 1 find . -type f -exec install -Dm 644 "{}" "/{}" \; ) info "Done!" } create_user() { tput sc failed=false while ! echo "$username" | grep -q "[a-z_][a-z0-9_-]*$"; do $failed && warn "Invalid username, try again!" username=$(prompt "Input Username: " "$(tty)") failed=true tput rc tput el done if id -u "$username" >/dev/null 2>&1; then warn "User \"$username\" already exists! Skipping user creation!" else info "Creating user \"$username\" with the following groups: \"$USER_GROUPS\"..." useradd -m -G "$USER_GROUPS" "$username" failed=false while [ -z "$pass1" ] || [ "$pass1" != "$pass2" ]; do $failed && warn "Passwords do not match or are empty, try again!" tput rc tput el pass1=$(prompt "Input Password: " "$(tty)") tput rc tput el pass2=$(prompt "Repeat Password: " "$(tty)") tput rc tput el failed=true done echo "$username:$pass1" | chpasswd fi user_home=$(getent passwd "$username" | cut -d ':' -f 6) [ -z "$username" ] && error "\$username variable is empty, this script is bugged!" [ -z "$user_home" ] && error "\$user_home variable is empty, this script is bugged!" sudo -u "$username" [ -w "$user_home" ] || error "$username can't write to '$user_home'!" info "Done!" } create_directories() { #shellcheck disable=SC2016 _loop_wrapper "$1" \ 'Creating directory ${x}' \ "mkdir -p $user_home/\${x}" info "Done!" } install_dotfiles() { tput sc info "Cloning dotfiles..." mkdir -p "${user_home}/${STOW_DIR}" if [ ! -d "${user_home}/${STOW_DIR}/${DOTS_PACKAGE}/.git" ]; then if ! git -C "${user_home}/${STOW_DIR}" clone -q --recurse-submodules -b "$DOTS_BRANCH" "$DOTS_REPO" "$DOTS_PACKAGE"; then tput rc tput el warn "Failed to clone dotfiles" return 1 fi fi tput rc tput el info "Symlinking dotfiles..." if ! stow -d "$user_home/$STOW_DIR" -t "$user_home" "$DOTS_PACKAGE" 1>/dev/null 2>&1; then tput rc tput el warn "Failed to symlink dotfiles" return 2 fi tput rc tput el info "Done!" } enable_services() { tput sc info "Installing user service service..." target="/etc/sv/runsvdir-${username}/run" mkdir -p "$(dirname "$target")" sed "s//$username/" "${SCRIPT_DIR}/userservice.sh" >"$target" [ ! -L "/var/service/$(basename "$target")" ] && ln -s "$target" "/var/service/" chmod 755 "$target" tput rc tput el #shellcheck disable=SC2016 _loop_wrapper "$1" \ 'Enabling ${x} service' \ '[ ! -L /var/service/${x} ] && ln -s "/etc/sv/${x}" "/var/service/"' info "Done!" } finalize() { gid=$(getent passwd "$username" | cut -d ':' -f 4) groupname=$(getent group "$gid" | cut -d ':' -f 1) chown "$username:$groupname" "$user_home" info "Done!" } ### CONTROL FLOW BEGINS HERE ### check_root emphasize "-- Preparing Installation --" setup emphasize "-- Installing Packages --" install_packages "${SCRIPT_DIR}/packages.txt" emphasize "-- Copying Files --" install_files emphasize "-- Creating User Account --" create_user emphasize "-- Creating Standard Home Directories --" create_directories "${SCRIPT_DIR}/directories.txt" emphasize "-- Installing Dotfiles --" install_dotfiles emphasize "-- Enabling Services --" enable_services "${SCRIPT_DIR}/services.txt" emphasize "-- Finalizing Installation --" finalize emphasize "-- Installation Complete --"