#!/bin/sh set -eu DOTS_REPO="https://git.snaile.de/luca/dotfiles" DOTS_BRANCH="main" STOW_DIR=".local/share/stow" DOTS_PACKAGE="dots" CONFIG_FILE="./config.yml" LOG_FILE="bootstrap.log" USER_GROUPS="wheel,floppy,audio,video,cdrom,optical,kvm,xbuilder,users" # Comma separated list SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" error() { printf "%b\n" "${RED}${BOLD}${1}${RESET}" >&2 echo "$1" >>"$LOG_FILE" exit 1 } prompt() { message=$1 printf "%b" "${BLUE}${message}${RESET}" >"$(tty)" # shellcheck disable=SC3037,SC2046 read -r x echo "$x" unset x } emphasize() { message=$1 printf "%-$(tput cols)b\n" "${GREEN}${BOLD}${message}${RESET}" echo "$1" >>"$LOG_FILE" } info() { message=$1 printf "%-$(tput cols)b\n" "${message}" echo "$1" >>"$LOG_FILE" } warn() { message=$1 printf "%-$(tput cols)b\n" "${YELLOW}${BOLD}${message}${RESET}" echo "$1" >>"$LOG_FILE" } print_buffer() { buffer_size=$1 trim=${2:-y} # 'y' | 'n' line_length=$(tput cols) for i in $(seq 1 "$((buffer_size))"); do if [ "$trim" = "y" ]; then template="%-${line_length}s\n" else template="%-${line_length}.${line_length}s\n" fi eval '[ -z "${buffer'"$i"':-}" ] || { printf "'"$template"'" "${buffer'"$i"'}" }' done } push_buffer() { line=$1 buffer_size=$2 for i in $(seq 1 $((buffer_size - 1))); do eval 'buffer'"$i"'="${buffer'$((i + 1))':-}"' done eval 'buffer'"$buffer_size"'=''''"$line"''''' } scroll() { buffer_size=$1 # int trim=${2:-y} # 'y' | 'n' tput civis while read -r line; do push_buffer "$line" "$buffer_size" echo "$line" >>"$LOG_FILE" tput sc print_buffer "$buffer_size" "$trim" tput rc done tput cnorm tput sc for i in $(seq 1 $((buffer_size))); do printf "%-$(tput cols)s" "" eval "unset buffer$i" done tput rc print_buffer "$buffer_size" "$trim" } check_root() { [ "$(id -u)" = "0" ] || error "This script needs root!" } setup() { echo "Synchronizing XBPS index..." | tee -pa "$LOG_FILE" xbps-install -Sy xbps void-repo-nonfree >/dev/null 2>&1 || echo "Failed to synchronize XBPS index! (Try manually running xbps-install -S)" | tee -pa "$LOG_FILE" if ! xbps-query ntp >/dev/null 2>&1; then echo "Installing script dependencies..." | tee -pa "$LOG_FILE" progs="ntp jq yq git stow fzf ncurses" # shellcheck disable=SC2086 xbps-install -y $progs >/dev/null 2>&1 echo "Synchronizing time..." | tee -pa "$LOG_FILE" ntpdate "pool.ntp.org" >/dev/null 2>&1 || warn "Failed to synchronize time!" fi echo "Done!" | tee -pa "$LOG_FILE" } install_packages() { IFS=' ' set +e for p in $(yq -c '.packages.[]' /dev/null)" flags=$(echo "$p" | yq -r '.command[1:].[]') packages=$(echo "$p" | yq -r '.list[]') [ -n "${packages}" ] && { $pre_command case $(echo "$p" | yq -r '.local') in true) echo "$flags" "$packages" | sudo -i -u "$username" xargs "$(echo "$p" | yq -r '.command[0]')" || touch /tmp/bootstrapper-failed ;; false) echo "$flags" "$packages" | xargs "$command" || touch /tmp/bootstrapper-failed ;; esac } done 2>&1 | scroll 7 set -e if [ -f /tmp/bootstrapper-failed ]; then warn "Failures during package installation, check logs." rm /tmp/bootstrapper-failed else info "Done!" fi } install_files() { ( cd "${SCRIPT_DIR}/files" || exit 1 find . -type d -exec mkdir -p "/{}" \; find . -type f -exec install -o root -g root "{}" "/{}" \; find . -type l -exec cp -d "{}" "/{}" \; ) info "Done!" } create_user() { failed=false while ! echo "$username" | grep "^[a-z_][a-z0-9_-]*$" | grep -qv "root"; do $failed && warn "Invalid username, try again!" username=$(prompt "Input Username: ") failed=true done if id -u "$username" >/dev/null 2>&1; then warn "User \"$username\" already exists, Skipping user creation!" usermod -G "$USER_GROUPS" "$username" 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!" pass1=$(prompt "Input Password: ") pass2=$(prompt "Repeat Password: ") 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 directories=$(yq -c '.directories.[]' <${CONFIG_FILE}) counter=1 num_dirs=$(echo "$directories" | wc -l) for entry in $directories; do path=$(echo "$entry" | jq -r '.path') mode=$(echo "$entry" | jq -r '.mode') [ "$mode" = "null" ] && mode="0755" info "Creating directory ${counter} of ${num_dirs}: ~/${path}" [ -d "${user_home}/${path}" ] || mkdir -m "${mode}" -p "${user_home}/${path}" counter=$((counter + 1)) done 2>&1 | scroll 7 info "Done!" } install_dotfiles() { 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 warn "Failed to clone dotfiles" return 1 fi fi info "Symlinking dotfiles..." if ! stow -d "$user_home/$STOW_DIR" -t "$user_home" "$DOTS_PACKAGE" 1>/dev/null 2>&1; then warn "Failed to symlink dotfiles" return 2 fi info "Done!" } select_keymap() { [ -L "${user_home}/.local/share/xkb/compiled/keymap" ] && return map="$(find "${user_home}/.local/share/xkb/compiled" -type f -printf "%f\n" | fzf --header="Select a keymap keymap:")" ln -s "$map" "${user_home}/.local/share/xkb/compiled/keymap" } # FIX: This has to work in a chroot enable_services() { services=$(yq -r '.services[]' &1 | scroll 7 info "Done!" } finalize() { gid=$(getent passwd "$username" | cut -d ':' -f 4) groupname=$(getent group "$gid" | cut -d ':' -f 1) info "Setting ownership of home directories..." chown "$username:$groupname" -R "$user_home" info "Done!" } ### CONTROL FLOW BEGINS HERE ### trap 'tput cnorm; tput sgr0; exit' INT TERM EXIT echo >"$LOG_FILE" check_root setup BOLD="$(tput bold)" RED="$(tput setaf 1)" GREEN="$(tput setaf 2)" YELLOW="$(tput setaf 3)" BLUE="$(tput setaf 4)" RESET="$(tput sgr0)" emphasize "-- Copying Files --" install_files # emphasize "-- Creating Symlinks --" # create_symlinks username="${SUDO_USER:-}" if [ -z "$username" ]; then emphasize "-- Creating User Account --" create_user else user_home=$(getent passwd "$username" | cut -d ':' -f 6) fi emphasize "-- Installing Dotfiles --" install_dotfiles select_keymap emphasize "-- Creating Standard Home Directories --" create_directories emphasize "-- Installing Packages --" install_packages emphasize "-- Enabling Services --" enable_services emphasize "-- Finalizing Installation --" finalize emphasize "-- Installation Complete --"