Skip to content

Instantly share code, notes, and snippets.

@lbussy
Last active April 19, 2025 13:56
Show Gist options
  • Save lbussy/23c05d8dc8c24d8d8edddf1d381f1c8b to your computer and use it in GitHub Desktop.
Save lbussy/23c05d8dc8c24d8d8edddf1d381f1c8b to your computer and use it in GitHub Desktop.
Bash Aliases & RPi Setup

Bash Aliases & RPi Setup

Install

curl -fsSL https://gist.githubusercontent.com/lbussy/23c05d8dc8c24d8d8edddf1d381f1c8b/raw/install_aliases.sh | bash

Overview

This script installs and configures various system components, including system updates, required packages, and the .bash_aliases file. More information about the aliases installed are available here.

Table of Contents


Features

  1. Verifies that the system is Debian-based (Ubuntu/Debian/Raspbian).
  2. Performs the following tasks:
    • Fixes broken apt packages.
    • Updates and upgrades the system.
    • Installs necessary packages.
    • Downloads and configures a .bash_aliases file.
    • Reloads .bash_aliases in the current session.
    • Checks if a kernel update requires a reboot.
  3. Interactive user confirmation or automatic mode for non-interactive environments.
  4. Displays progress and results with color-coded feedback.

Prerequisites

  • A Debian-based system (e.g., Ubuntu, Debian, Raspbian).
  • apt-get and sudo must be available.
  • Internet access to download the .bash_aliases file.

Customizing Variables

  • USE_SUDO: Set to false if sudo is not required.
  • BASH_ALIASES_URL: Provide a custom URL for the .bash_aliases file.

Example:

USE_SUDO=false BASH_ALIASES_URL="https://example.com/.bash_aliases" ./install_aliases.sh

Functions

error_exit

Prints an error message and exits the script.

check_commands

Verifies that required commands are available.

print_system

Displays system information from /etc/os-release.

check_debian_based

Ensures the system is Debian-based and supports apt-get.

check_sudo

Checks if sudo is available and the user has sudo privileges.

confirm_action

Prompts the user for confirmation to proceed.

terminal_code

Initializes terminal escape codes for colors and formatting.

init_colors

Sets up terminal colors and effects.

exec_command

Executes a command and reports its status.

fix_broken

Fixes broken apt packages.

refresh_apt

Updates the apt package cache.

upgrade_exist

Upgrades existing packages.

run_upgrade

Performs a full system upgrade.

install_apt

Installs required packages.

run_cleanup

Cleans up unused packages and clears the package cache.

download_aliases

Downloads the .bash_aliases file and configures it.

reload_aliases

Reloads the .bash_aliases file.

notify_personal_aliases

Informs the user about creating a .personal_aliases file.

needrestart

This package will check for updated eepprom, services, binaries, and kernel versions. It allows restarting services or will tell you if you need to reboot to pick up a new kernel.


Customization

You can modify the following variables in the script:

  • APTPACKAGES: List of additional packages to install.
  • COMMANDS: List of required commands to validate.

Notes

  • The script automatically detects if it’s running in a non-interactive terminal and skips confirmation prompts.
  • Outputs are color-coded for clarity:
    • Green: Success
    • Red: Error
    • Yellow: In Progress

#!/usr/bin/env bash
set -uo pipefail
IFS=$'\n\t'
set +o noclobber
# -----------------------------------------------------------------------------
# Script Name: Bash Alias Install (install_aliases.sh)
# -----------------------------------------------------------------------------
# @brief This script installs and configures various system components including
# system updates, required packages, and .bash_aliases file.
#
# @details The script performs the following tasks:
# 1. Verifies that the system is Debian-based (Ubuntu/Debian/Raspbian).
# 2. Confirms the user’s intention to proceed with the script.
# 3. Initializes terminal color codes and formatting.
# 4. Fixes broken apt packages and performs system updates/upgrades.
# 5. Installs necessary packages like colordiff and deborphan.
# 6. Downloads and installs the .bash_aliases file from a specified URL.
# 7. Reloads the .bash_aliases file and applies aliases to the current session.
# 8. Notifies the user if a kernel update requires a reboot.
#
# @notes
# - The script uses sudo to perform system-level tasks, so the USE_SUDO variable
# can be set to false if sudo is not required.
# - The script is designed to be interactive but can also be run in non-interactive
# environments without requiring user confirmation.
# - The script assumes that the system has `apt-get` available and is Debian-based.
# - The script’s progress and results are displayed with color-coded feedback for
# easy understanding.
#
# @usage
# 1. Run the script using:
# ./install_aliases.sh
# 2. Set the USE_SUDO variable to false if sudo is not required for the execution.
# 3. Configure the BASH_ALIASES_URL variable if a custom URL for .bash_aliases
# is needed.
# 4. The script will automatically proceed without confirmation if no terminal is
# detected or if the script is run in non-interactive mode.
#
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# Global Variables for Script Configuration
# -----------------------------------------------------------------------------
# @var DRY_RUN
# @brief Enables simulated execution of certain commands.
# @details When set to `true`, commands are not actually executed but are
# simulated to allow testing or validation without side effects.
# If set to `false`, commands execute normally.
declare DRY_RUN="${DRY_RUN:-false}"
# @var THIS_NAME
# @brief Name of the script displayed in messages and logs.
# @details This is used as the script's identifier for informational output.
# It is helpful when referencing the script in messages or documentation.
declare THIS_NAME="${THIS_NAME:-Bash Alias Install}"
# @var USE_SUDO
# @brief Flag to determine if the script should use sudo for elevated privileges.
# @details Set to true if sudo is required to execute commands needing root access.
# If the user has root privileges or sudo is not needed, set this to false.
declare USE_SUDO="${USE_SUDO:-true}"
# @var BASH_ALIASES_URL
# @brief URL for downloading the .bash_aliases file.
# @details This points to the location where the script fetches a preconfigured
# `.bash_aliases` file. The URL can be customized via environment variable
# `BASH_ALIASES_URL` if desired.
declare BASH_ALIASES_URL="${BASH_ALIASES_URL:="https://gist.githubusercontent.com/lbussy/23c05d8dc8c24d8d8edddf1d381f1c8b/raw/my_aliases.sh"}"
# @var COMMANDS
# @brief List of essential commands required by the script.
# @details These commands are validated for availability before the script executes.
# The script will exit if any of these commands are missing.
declare COMMANDS=(
"curl" # For downloading files from URLs
"apt-get" # For managing package installations on Debian-based systems
"bash" # Required for running the script itself
"grep" # Used for pattern matching and text processing
)
# @var APTPACKAGES
# @brief List of packages to be installed during the script execution.
# @details These are the system packages the script installs if they are not
# already present on the system. They include tools for debugging,
# system monitoring, and version control.
declare APTPACKAGES=(
"colordiff" # Enhanced colorized diff output
"deborphan" # Identifies orphaned libraries and unused packages
"htop" # Interactive process viewer for system monitoring
"git" # Version control system for managing code repositories
"gh" # GitHub CLI for interacting with GitHub repositories
"needrestart" # Notifies if a reboot is required after updates
)
# -----------------------------------------------------------------------------
# Terminal Formatting and Color Codes
# -----------------------------------------------------------------------------
# @var RESET
# @brief ANSI escape code to reset all text formatting.
# @details Used to clear any applied text attributes (e.g., bold, colors) and return
# to the terminal's default style.
# @var FGRED
# @brief ANSI escape code for red-colored text.
# @details Used to display error or critical messages to differentiate them visually.
# @var FGGRN
# @brief ANSI escape code for green-colored text.
# @details Used for success messages or positive feedback to the user.
# @var FGGLD
# @brief ANSI escape code for gold/yellow-colored text.
# @details Used for warnings or informational messages requiring attention. Fallbacks
# to yellow if the terminal does not support gold.
# @var BOLD
# @brief ANSI escape code to make text bold.
# @details Used to emphasize specific parts of the output, such as headers or important
# information.
declare RESET FGRED FGGRN FGGLD BOLD
# -----------------------------------------------------------------------------
# @brief Prints a stack trace with optional formatting and a message.
# @details This function generates and displays a formatted stack trace for
# debugging purposes. It includes a log level and optional details,
# with color-coded formatting and proper alignment.
#
# @param $1 [optional] Log level (DEBUG, INFO, WARN, ERROR, CRITICAL).
# Defaults to INFO.
# @param $2 [optional] Primary message for the stack trace.
# @param $@ [optional] Additional context or details for the stack trace.
#
# @global FUNCNAME Array of function names in the call stack.
# @global BASH_LINENO Array of line numbers corresponding to the call stack.
# @global THIS_SCRIPT The name of the current script, used for logging.
# @global COLUMNS Console width, used for formatting output.
#
# @throws None.
#
# @return None. Outputs the stack trace and message to standard output.
#
# @example
# stack_trace WARN "Unexpected condition detected."
# -----------------------------------------------------------------------------
stack_trace() {
# Determine log level and message
local level="${1:-INFO}"
local message=""
# Block width and character for header/footer
local char="-"
# Recalculate terminal columns
COLUMNS=$( (command -v tput >/dev/null && tput cols) || printf "80")
COLUMNS=$((COLUMNS > 0 ? COLUMNS : 80)) # Ensure COLUMNS is a positive number
local width
width=${COLUMNS:-80} # Max console width
# Check if $1 is a valid level, otherwise treat it as the message
case "$level" in
DEBUG|INFO|WARN|WARNING|ERROR|CRIT|CRITICAL)
shift
;;
*)
message="$level"
level="INFO"
shift
;;
esac
# Concatenate all remaining arguments into $message
for arg in "$@"; do
message+="$arg "
done
# Trim leading/trailing whitespace
message=$(printf "%s" "$message" | xargs)
# Define functions to skip
local skip_functions=("die" "warn" "stack_trace")
local encountered_main=0 # Track the first occurrence of main()
# Generate title case function name for the banner
local raw_function_name="${FUNCNAME[0]}"
local header_name header_level
header_name=$(printf "%s" "$raw_function_name" | sed -E 's/_/ /g; s/\b(.)/\U\1/g; s/(\b[A-Za-z])([A-Za-z]*)/\1\L\2/g')
header_level=$(printf "%s" "$level" | sed -E 's/\b(.)/\U\1/g; s/(\b[A-Za-z])([A-Za-z]*)/\1\L\2/g')
header_name="$header_level $header_name"
# Helper: Skip irrelevant functions
should_skip() {
local func="$1"
for skip in "${skip_functions[@]}"; do
if [[ "$func" == "$skip" ]]; then
return 0
fi
done
if [[ "$func" == "main" && $encountered_main -gt 0 ]]; then
return 0
fi
[[ "$func" == "main" ]] && ((encountered_main++))
return 1
}
# Build the stack trace
local displayed_stack=()
local longest_length=0
for ((i = 1; i < ${#FUNCNAME[@]}; i++)); do
local func="${FUNCNAME[i]}"
local line="${BASH_LINENO[i - 1]}"
local current_length=${#func}
if should_skip "$func"; then
continue
elif (( current_length > longest_length )); then
longest_length=$current_length
fi
displayed_stack=("$(printf "%s|%s" "$func()" "$line")" "${displayed_stack[@]}")
done
# General text attributes
local reset="\033[0m" # Reset text formatting
local bold="\033[1m" # Bold text
# Foreground colors
local fgred="\033[31m" # Red text
local fggrn="\033[32m" # Green text
local fgblu="\033[34m" # Blue text
local fgmag="\033[35m" # Magenta text
local fgcyn="\033[36m" # Cyan text
local fggld="\033[38;5;220m" # Gold text (ANSI 256 color)
# Determine color and label based on level
local color label
case "$level" in
DEBUG) color=$fgcyn; label="[DEBUG]";;
INFO) color=$fggrn; label="[INFO ]";;
WARN|WARNING) color=$fggld; label="[WARN ]";;
ERROR) color=$fgmag; label="[ERROR]";;
CRIT|CRITICAL) color=$fgred; label="[CRIT ]";;
esac
# Create header and footer
local dash_count=$(( (width - ${#header_name} - 2) / 2 ))
local header_l header_r
# Generate a string of spaces
spaces="$(printf '%*s' "$dash_count" "")"
# Replace spaces with the desired character
header_l="$(tr ' ' "$char" <<< "$spaces")"
header_r="$header_l"
[[ $(( (width - ${#header_name}) % 2 )) -eq 1 ]] && header_r="${header_r}${char}"
local header; header=$(printf "%b%s%b %b%b%s%b %b%s%b" \
"$color" "$header_l" "$reset" "$color" "$bold" "$header_name" "$reset" "$color" "$header_r" "$reset")
local footer repeated_string
# Generate a repeated string safely without triggering ShellCheck warnings
read -r repeated_string < <(printf '%*s' "$width" "" | tr ' ' "$char")
# Generate the footer
footer="$(printf '%b%s%b' "$color" "$repeated_string" "$reset")"
# Print header
printf "%s\n" "$header"
# Print the message, if provided
if [[ -n "$message" ]]; then
# Fallback mechanism for wrap_messages
local result primary overflow secondary
if command -v wrap_messages >/dev/null 2>&1; then
result=$(wrap_messages "$width" "$message" || true)
primary="${result%%"${delimiter}"*}"
result="${result#*"${delimiter}"}"
overflow="${result%%"${delimiter}"*}"
else
primary="$message"
fi
# Print the formatted message
printf "%b%s%b\n" "${color}" "${primary}" "${reset}"
printf "%b%s%b\n" "${color}" "${overflow}" "${reset}"
fi
# Print stack trace
local indent=$(( (width / 2) - ((longest_length + 28) / 2) ))
indent=$(( indent < 0 ? 0 : indent ))
if [[ -z "${displayed_stack[*]}" ]]; then
printf "%b[WARN ]%b Stack trace is empty.\n" "$fggld" "$reset" >&2
else
for ((i = ${#displayed_stack[@]} - 1, idx = 0; i >= 0; i--, idx++)); do
IFS='|' read -r func line <<< "${displayed_stack[i]}"
printf "%b%*s [%d] Function: %-*s Line: %4s%b\n" \
"$color" "$indent" ">" "$idx" "$((longest_length + 2))" "$func" "$line" "$reset"
done
fi
# Print footer
printf "%s\n\n" "$footer"
}
# -----------------------------------------------------------------------------
# @brief Wraps a message into lines with ellipses for overflow or continuation.
# @details This function splits the message into lines, appending an ellipsis
# for overflowed lines and prepending it for continuation lines. The
# primary and secondary messages are processed separately and combined
# with a delimiter.
#
# @param $1 [required] The message string to wrap.
# @param $2 [required] Maximum width of each line (numeric).
# @param $3 [optional] The secondary message string to include (defaults to
# an empty string).
#
# @global None.
#
# @throws None.
#
# @return A single string with wrapped lines and ellipses added as necessary.
# The primary and secondary messages are separated by a delimiter.
#
# @example
# wrapped=$(wrap_messages "This is a long message" 50)
# echo "$wrapped"
# -----------------------------------------------------------------------------
wrap_messages() {
local line_width=$1
local primary=$2
local secondary=${3:-}
local delimiter="␞"
# Validate input
if [[ -z "$line_width" || ! "$line_width" =~ ^[0-9]+$ || "$line_width" -le 1 ]]; then
printf "Error: Invalid line width '%s' in %s(). Must be a positive integer.\n" \
"$line_width" "${FUNCNAME[0]}" >&2
return 1
fi
# Inner function to wrap a single message
wrap_message() {
local message=$1
local width=$2
local result=()
# Address faulty width with a min of 10
local adjusted_width=$((width > 10 ? width - 1 : 10))
while IFS= read -r line; do
result+=("$line")
done <<< "$(printf "%s\n" "$message" | fold -s -w "$adjusted_width")"
for ((i = 0; i < ${#result[@]}; i++)); do
result[i]=$(printf "%s" "${result[i]}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if ((i == 0)); then
result[i]="${result[i]}…"
elif ((i == ${#result[@]} - 1)); then
result[i]="…${result[i]}"
else
result[i]="…${result[i]}…"
fi
done
printf "%s\n" "${result[@]}"
}
# Process primary message
local overflow=""
if [[ ${#primary} -gt $line_width ]]; then
local wrapped_primary
wrapped_primary=$(wrap_message "$primary" "$line_width")
overflow=$(printf "%s\n" "$wrapped_primary" | tail -n +2)
primary=$(printf "%s\n" "$wrapped_primary" | head -n 1)
fi
# Process secondary message
if [[ -n ${#secondary} && ${#secondary} -gt $line_width ]]; then
secondary=$(wrap_message "$secondary" "$line_width")
fi
# Combine results
printf "%s%b%s%b%s" \
"$primary" \
"$delimiter" \
"$overflow" \
"$delimiter" \
"$secondary"
}
# -----------------------------------------------------------------------------
# @brief Logs a warning message with optional details and stack trace.
# @details This function logs a warning message with color-coded formatting
# and optional details. It adjusts the output to fit within the
# terminal's width and supports message wrapping if the
# `wrap_messages` function is available. If `WARN_STACK_TRACE` is set
# to `true`, a stack trace is also logged.
#
# @param $1 [Optional] The primary warning message. Defaults to
# "A warning was raised on this line" if not provided.
# @param $@ [Optional] Additional details to include in the warning message.
#
# @global FALLBACK_SCRIPT_NAME The name of the script to use if the script
# name cannot be determined.
# @global FUNCNAME Bash array containing the function call stack.
# @global BASH_LINENO Bash array containing the line numbers of
# function calls in the stack.
# @global WRAP_DELIMITER The delimiter used for separating wrapped
# message parts.
# @global WARN_STACK_TRACE If set to `true`, a stack trace will be logged.
# @global COLUMNS The terminal's column width, used to format
# the output.
#
# @return None.
#
# @example
# warn "Configuration file missing." "Please check /etc/config."
# warn "Invalid syntax in the configuration file."
#
# @note This function requires `tput` for terminal width detection and ANSI
# formatting, with fallbacks for minimal environments.
# -----------------------------------------------------------------------------
warn() {
# Initialize variables
local script="${FALLBACK_SCRIPT_NAME:-unknown}" # This script's name
local func_name="${FUNCNAME[1]:-main}" # Calling function
local caller_line=${BASH_LINENO[0]:-0} # Calling line
# Get valid error code
local error_code
if [[ -n "${1:-}" && "$1" =~ ^[0-9]+$ ]]; then
error_code=$((10#$1)) # Convert to numeric
shift
else
error_code=1 # Default to 1 if not numeric
fi
# Configurable delimiter
local delimiter="${WRAP_DELIMITER:-␞}"
# Get the primary message
local message
message=$(sed -E 's/^[[:space:]]*//;s/[[:space:]]*$//' <<< "${1:-A warning was raised on this line}")
[[ $# -gt 0 ]] && shift
# Process details
local details
details=$(sed -E 's/^[[:space:]]*//;s/[[:space:]]*$//' <<< "$*")
# Recalculate terminal columns
COLUMNS=$( (command -v tput >/dev/null && tput cols) || printf "80")
COLUMNS=$((COLUMNS > 0 ? COLUMNS : 80)) # Ensure COLUMNS is a positive number
local width
width=${COLUMNS:-80} # Max console width
# Escape sequences for colors and attributes
local reset="\033[0m" # Reset text
local bold="\033[1m" # Bold text
local fggld="\033[38;5;220m" # Gold text
local fgcyn="\033[36m" # Cyan text
local fgblu="\033[34m" # Blue text
# Format prefix
format_prefix() {
local color=${1:-"\033[0m"}
local label="${2:-'[WARN ] [unknown:main:0]'}"
# Create prefix
printf "%b%b%s%b %b[%s:%s:%s]%b " \
"${bold}" \
"${color}" \
"${label}" \
"${reset}" \
"${bold}" \
"${script}" \
"${func_name}" \
"${caller_line}" \
"${reset}"
}
# Generate prefixes
local warn_prefix extd_prefix dets_prefix
warn_prefix=$(format_prefix "$fggld" "[WARN ]")
extd_prefix=$(format_prefix "$fgcyn" "[EXTND]")
dets_prefix=$(format_prefix "$fgblu" "[DETLS]")
# Strip ANSI escape sequences for length calculation
local plain_warn_prefix adjusted_width
plain_warn_prefix=$(printf "%s" "$warn_prefix" | sed -E 's/(\x1b\[[0-9;]*[a-zA-Z]|\x1b\([a-zA-Z])//g; s/^[[:space:]]*//; s/[[:space:]]*$//')
adjusted_width=$((width - ${#plain_warn_prefix} - 1))
# Fallback mechanism for `wrap_messages`
local result primary overflow secondary
if command -v wrap_messages >/dev/null 2>&1; then
result=$(wrap_messages "$adjusted_width" "$message" "$details" || true)
primary="${result%%"${delimiter}"*}"
result="${result#*"${delimiter}"}"
overflow="${result%%"${delimiter}"*}"
secondary="${result#*"${delimiter}"}"
else
primary="$message"
overflow=""
secondary="$details"
fi
# Print the primary message
printf "%s%s\n" "$warn_prefix" "$primary" >&2
# Print overflow lines
if [[ -n "$overflow" ]]; then
while IFS= read -r line; do
printf "%s%s\n" "$extd_prefix" "$line" >&2
done <<< "$overflow"
fi
# Print secondary details
if [[ -n "$secondary" ]]; then
while IFS= read -r line; do
printf "%s%s\n" "$dets_prefix" "$line" >&2
done <<< "$secondary"
fi
# Execute stack trace if WARN_STACK_TRACE is enabled
if [[ "${WARN_STACK_TRACE:-false}" == "true" ]]; then
stack_trace "WARNING" "${message}" "${secondary}"
fi
}
# -----------------------------------------------------------------------------
# @brief Terminates the script with a critical error message.
# @details This function is used to log a critical error message with optional
# details and exit the script with the specified error code. It
# supports formatting the output with ANSI color codes, dynamic
# column widths, and optional multi-line message wrapping.
#
# If the optional `wrap_messages` function is available, it will be
# used to wrap and combine messages. Otherwise, the function falls
# back to printing the primary message and details as-is.
#
# @param $1 [optional] Numeric error code. Defaults to 1.
# @param $2 [optional] Primary error message. Defaults to "Critical error".
# @param $@ [optional] Additional details to include in the error message.
#
# @global FALLBACK_SCRIPT_NAME The script name to use as a fallback.
# @global FUNCNAME Bash array containing the call stack.
# @global BASH_LINENO Bash array containing line numbers of the stack.
# @global WRAP_DELIMITER Delimiter used when combining wrapped messages.
# @global COLUMNS The terminal's column width, used to adjust
# message formatting.
#
# @return None. This function does not return.
# @exit Exits the script with the specified error code.
#
# @example
# die 127 "File not found" "Please check the file path and try again."
# die "Critical configuration error"
# -----------------------------------------------------------------------------
die() {
# Initialize variables
local script="${FALLBACK_SCRIPT_NAME:-unknown}" # This script's name
local func_name="${FUNCNAME[1]:-main}" # Calling function
local caller_line=${BASH_LINENO[0]:-0} # Calling line
# Get valid error code
if [[ -n "${1:-}" && "$1" =~ ^[0-9]+$ ]]; then
error_code=$((10#$1)) # Convert to numeric
shift
else
error_code=1 # Default to 1 if not numeric
fi
# Configurable delimiter
local delimiter="${WRAP_DELIMITER:-␞}"
# Process the primary message
local message
message=$(sed -E 's/^[[:space:]]*//;s/[[:space:]]*$//' <<< "${1:-Critical error}")
# Only shift if there are remaining arguments
[[ $# -gt 0 ]] && shift
# Process details
local details
details=$(sed -E 's/^[[:space:]]*//;s/[[:space:]]*$//' <<< "$*")
# Recalculate terminal columns
COLUMNS=$( (command -v tput >/dev/null && tput cols) || printf "80")
COLUMNS=$((COLUMNS > 0 ? COLUMNS : 80)) # Ensure COLUMNS is a positive number
local width
width=${COLUMNS:-80} # Max console width
# Escape sequences as safe(r) alternatives to global tput values
# General attributes
local reset="\033[0m"
local bold="\033[1m"
# Foreground colors
local fgred="\033[31m" # Red text
local fgcyn="\033[36m" # Cyan text
local fgblu="\033[34m" # Blue text
# Format prefix
format_prefix() {
local color=${1:-"\033[0m"}
local label="${2:-'[CRIT ] [unknown:main:0]'}"
# Create prefix
printf "%b%b%s%b %b[%s:%s:%s]%b " \
"${bold}" \
"${color}" \
"${label}" \
"${reset}" \
"${bold}" \
"${script}" \
"${func_name}" \
"${caller_line}" \
"${reset}"
}
# Generate prefixes
local crit_prefix extd_prefix dets_prefix
crit_prefix=$(format_prefix "$fgred" "[CRIT ]")
extd_prefix=$(format_prefix "$fgcyn" "[EXTND]")
dets_prefix=$(format_prefix "$fgblu" "[DETLS]")
# Strip ANSI escape sequences for length calculation
local plain_crit_prefix adjusted_width
plain_crit_prefix=$(printf "%s" "$crit_prefix" | sed -E 's/(\x1b\[[0-9;]*[a-zA-Z]|\x1b\([a-zA-Z])//g; s/^[[:space:]]*//; s/[[:space:]]*$//')
adjusted_width=$((width - ${#plain_crit_prefix} - 1))
# Fallback mechanism for `wrap_messages` since it is external
local result primary overflow secondary
if command -v wrap_messages >/dev/null 2>&1; then
result=$(wrap_messages "$adjusted_width" "$message" "$details" || true)
primary="${result%%"${delimiter}"*}"
result="${result#*"${delimiter}"}"
overflow="${result%%"${delimiter}"*}"
secondary="${result#*"${delimiter}"}"
else
primary="$message"
overflow=""
secondary="$details"
fi
# Print the primary message
printf "%s%s\n" "$crit_prefix" "$primary" >&2
# Print overflow lines
if [[ -n "$overflow" ]]; then
while IFS= read -r line; do
printf "%s%s\n" "$extd_prefix" "$line" >&2
done <<< "$overflow"
fi
# Print secondary details
if [[ -n "$secondary" ]]; then
while IFS= read -r line; do
printf "%s%s\n" "$dets_prefix" "$line" >&2
done <<< "$secondary"
fi
# Execute stack trace
stack_trace "CRITICAL" "${message}" "${secondary}"
# Exit with the specified error code
exit "$error_code"
}
# -----------------------------------------------------------------------------
# @brief Exit the script gracefully with a success message.
# @details This function prints a success message to stderr and exits with a
# status code of 0, indicating a successful execution. The message
# can be customized through the function's argument.
#
# @param $1 [optional] The success message to print. Defaults to "Unspecified exit"
# if no message is provided.
#
# @global FGGRN Used to set green-colored text for the success message.
# @global BOLD Used to apply bold formatting to the success message.
# @global RESET Used to reset text formatting to the terminal's default.
#
# @return This function exits the script with a status code of 0, indicating success.
#
# @example
# ok_exit "Operation completed successfully."
# # Output: [SUCCESS] Operation completed successfully.
# -----------------------------------------------------------------------------
ok_exit() {
# Local variable to store the success message
local message="${1:-Unspecified exit}" # Defaults to "Unspecified exit" if no message is provided
# Print the success message to stderr with formatting
printf "%b%b[SUCCESS]%b %s\n" "${FGGRN}" "${BOLD}" "${RESET}" "$message" >&2
# Exit with a success status code
exit 0
}
# -----------------------------------------------------------------------------
# @brief Verify the availability of required commands.
# @details Iterates through the list of required commands defined in the global
# `COMMANDS` array. If any command is missing, the script logs an error
# and exits with status code 1 using the `die` function.
#
# @global COMMANDS Array containing the list of commands to validate.
#
# @throws Exits the script with an error message if a required command is missing.
#
# @example
# COMMANDS=("curl" "bash")
# check_commands
# # If "curl" or "bash" is not installed, the script will exit with an error.
# -----------------------------------------------------------------------------
check_commands() {
# Iterate through each command in the COMMANDS array
for cmd in "${COMMANDS[@]}"; do
# Check if the command is available on the system
if ! command -v "$cmd" >/dev/null 2>&1; then
# Exit the script with an error if the command is not found
die 1 "Required command '$cmd' is not available on this system."
fi
done
}
# -----------------------------------------------------------------------------
# @brief Verify if the system is Debian-based and supports apt-get.
# @details This function checks `/etc/os-release` for identifiers such as
# "debian", "ubuntu", or "raspbian" to confirm the system is Debian-based.
# It also ensures that the `apt-get` package manager is available.
# If either condition fails, the script exits with an error message.
#
# @throws Exits the script with an error message if the system is not Debian-based
# or does not support `apt-get`.
#
# @example
# check_debian_based
# # If the system is Debian-based and supports apt-get, the function returns 0.
# # Otherwise, the script exits with an error.
# -----------------------------------------------------------------------------
check_debian_based() {
# Check if the system is Debian-based by looking for known identifiers in /etc/os-release
if grep -qiE "debian|ubuntu|raspbian" /etc/os-release 2>/dev/null; then
# Verify that apt-get is available on the system
if command -v apt-get >/dev/null 2>&1; then
# System is Debian-based and supports apt-get
return 0
else
# System is Debian-based but apt-get is not available
die 1 "This system is Debian-based but does not support apt-get."
fi
else
# System is not Debian-based
die 1 "Error: This system is not Debian-based."
fi
}
# -----------------------------------------------------------------------------
# @brief Verify the availability of sudo and the user's privileges.
# @details This function checks if the `sudo` command is available and whether
# the user has sufficient privileges to execute sudo commands. If sudo
# is required but unavailable or the user lacks privileges, the script
# exits with an error.
#
# @global USE_SUDO A boolean flag indicating whether sudo is required for the script.
#
# @throws Exits the script with an error message if `sudo` is required but not
# installed or if the user lacks sudo privileges.
#
# @example
# USE_SUDO=true
# check_sudo
# # If sudo is unavailable or privileges are insufficient, the script exits with an error.
# -----------------------------------------------------------------------------
check_sudo() {
# Check if sudo usage is required
if [ "${USE_SUDO}" = true ]; then
# Verify that the 'sudo' command is available
if ! command -v sudo >/dev/null 2>&1; then
die 1 "This script requires 'sudo', but it is not installed."
fi
# Verify that the user has sudo privileges
if ! sudo -v >/dev/null 2>&1; then
die 1 "This script requires 'sudo' privileges, but you do not have them."
fi
fi
}
# -----------------------------------------------------------------------------
# @brief Prompt the user for confirmation before proceeding.
# @details This function checks if the script is running in an interactive terminal.
# If it is, it prompts the user to confirm whether to proceed. If no terminal
# is detected, the script automatically proceeds without user confirmation.
#
# @throws Exits the script with a success message if the user cancels the operation.
#
# @example
# confirm_action
# # If the user responds "y" or "yes", the function returns 0 to continue.
# # If the user responds anything else, the script exits with a message.
# -----------------------------------------------------------------------------
confirm_action() {
# Local variable to store the user's response
local response
# Check if the script is running in an interactive terminal
if [ -t 0 ]; then
# Prompt the user for confirmation
read -r -p "This script will modify your system. Do you want to continue? [y/N]: " response
# Convert the response to lowercase for easier comparison
response="${response,,}"
# Default to "no" if the user does not provide input
if [[ "$response" != "y" && "$response" != "yes" ]]; then
ok_exit "Operation aborted."
fi
else
# Non-interactive environment: automatically proceed
printf "No terminal detected, proceeding without confirmation.\n"
response="y"
fi
# Evaluate the user's response
case "$response" in
[yY][eE][sS]|[yY])
printf "\n"
return 0 # User confirmed, proceed with the script
;;
*)
ok_exit "Operation cancelled by user."
esac
}
# -----------------------------------------------------------------------------
# @brief Retrieve terminal control codes using tput if supported.
# @details This function uses `tput` to retrieve terminal control codes for
# text formatting (e.g., colors and attributes). If the terminal does
# not support the requested command or color, it returns an empty string.
#
# @param $1 [required] The tput command to execute (e.g., "setaf", "bold").
# @param $2 [optional] The color or attribute value for the tput command (e.g., "1" for red).
# Defaults to an empty string if not provided.
#
# @return The terminal control code if supported, or an empty string otherwise.
#
# @example
# RESET=$(terminal_code sgr0) # Reset text formatting
# BOLD=$(terminal_code bold) # Enable bold text
# FGRED=$(terminal_code setaf 1) # Set text color to red
# -----------------------------------------------------------------------------
terminal_code() {
# Local variables
local command=$1 # Required tput command
local color="${2:-}" # Optional color or attribute value
local tput_colors_available # Number of colors supported by the terminal
# Check the number of colors supported by the terminal
tput_colors_available=$(tput colors 2>/dev/null || echo "0")
# If the terminal supports at least 8 colors
if [ "$tput_colors_available" -ge 8 ]; then
# Special case: Substitute "setaf 220" with "setaf 3" (yellow) if limited to 256 colors
if [ "$tput_colors_available" -le 256 ] && [ "$command" = "setaf" ] && [ "$color" = "220" ]; then
tput "$command" 3 2>/dev/null || echo "" # Fallback to "setaf 3" for yellow
else
# Execute the tput command with the provided arguments
tput "$command" "$color" 2>/dev/null || echo ""
fi
else
# Fallback to an empty string if the terminal does not support sufficient colors
printf ""
fi
}
# -----------------------------------------------------------------------------
# @brief Initialize terminal color and formatting codes.
# @details This function sets up variables for terminal text attributes and
# foreground colors using `tput`. If the terminal does not support
# these features, the variables are assigned empty strings to ensure
# graceful fallback behavior.
#
# @global RESET Used to reset all text attributes to the terminal's default.
# @global MOVE_UP Moves the cursor up one line.
# @global CLEAR_LINE Clears the current line.
# @global BOLD Enables bold text formatting.
# @global FGRED Sets the foreground text color to red.
# @global FGGRN Sets the foreground text color to green.
# @global FGGLD Sets the foreground text color to gold (yellow for limited colors).
#
# @return None.
#
# @example
# init_colors
# printf "%bThis text is bold and red.%b\n" "${BOLD}${FGRED}" "${RESET}"
# -----------------------------------------------------------------------------
init_colors() {
# General text attributes
RESET=$(terminal_code sgr0) # Reset all attributes to default
MOVE_UP=$(terminal_code cuu1) # Move cursor up one line
CLEAR_LINE=$(terminal_code el) # Clear the current line
BOLD=$(terminal_code bold) # Enable bold text formatting
# Foreground colors
FGRED=$(terminal_code setaf 1) # Set text color to red
FGGRN=$(terminal_code setaf 2) # Set text color to green
FGGLD=$(terminal_code setaf 220) # Set text color to gold (fallback to yellow if limited)
}
# -----------------------------------------------------------------------------
# @brief Executes a command in a separate Bash process.
# @details This function manages the execution of a shell command, handling the
# display of status messages. It supports dry-run mode, where the
# command is simulated without execution. The function prints success
# or failure messages and handles the removal of the "Running" line
# once the command finishes.
#
# @param exec_name The name of the command or task being executed.
# @param exec_process The command string to be executed.
# @param debug Optional flag to enable debug messages. Set to "debug" to
# enable.
#
# @return Returns 0 if the command was successful, non-zero otherwise.
#
# @note The function supports dry-run mode, controlled by the DRY_RUN variable.
# When DRY_RUN is true, the command is only simulated without actual
# execution.
#
# @example
# exec_command "Test Command" "echo Hello World" "debug"
# -----------------------------------------------------------------------------
exec_command() {
local exec_name="$1"
local exec_process="$2"
# Basic status prefixes
local running_pre="Running"
local complete_pre="Complete"
local failed_pre="Failed"
# If DRY_RUN is enabled, show that in the prefix
if [[ "$DRY_RUN" == "true" ]]; then
running_pre+=" (dry)"
complete_pre+=" (dry)"
failed_pre+=" (dry)"
fi
running_pre+=":"
complete_pre+=":"
failed_pre+=":"
# 1) Print ephemeral “Running” line
printf "%b[-]%b %s %s.\n" "${FGGLD}" "${RESET}" "$running_pre" "$exec_name"
# Optionally ensure it shows up (especially if the command is super fast):
sleep 0.02
# 2) If DRY_RUN == "true", skip real exec
if [[ "$DRY_RUN" == "true" ]]; then
# Move up & clear ephemeral line
printf "%b%b" "$MOVE_UP" "$CLEAR_LINE"
printf "%b[✔]%b %s %s.\n" "${FGGRN}" "${RESET}" "$complete_pre" "$exec_name"
return 0
fi
# 3) Actually run the command (stdout/stderr handling is up to you):
bash -c "$exec_process" &>/dev/null
local status=$?
# 4) Move up & clear ephemeral “Running” line
printf "%b%b" "$MOVE_UP" "$CLEAR_LINE"
# 5) Print final success/fail
if [[ $status -eq 0 ]]; then
printf "%b[✔]%b %s %s.\n" "${FGGRN}" "${RESET}" "$complete_pre" "$exec_name"
else
printf "%b[✘]%b %s %s.\n" "${FGRED}" "${RESET}" "$failed_pre" "$exec_name"
# If specifically “command not found” exit code:
if [[ $status -eq 127 ]]; then
warn "Command not found: $exec_process"
else
warn "Command failed with status $status: $exec_process"
fi
fi
return $status
}
# -----------------------------------------------------------------------------
# @brief Attempt to fix broken apt packages and dependencies.
# @details This function runs the `apt-get --fix-broken install` command to
# resolve issues with broken package installations or unmet dependencies.
# If the command fails, the script exits with an error message.
#
# @throws Exits the script with an error message if the fix-broken operation fails.
#
# @example
# fix_broken
# # If broken packages are detected, this will attempt to fix them.
# # If the command fails, the script exits with an error.
# -----------------------------------------------------------------------------
fix_broken() {
# Execute the apt-get fix-broken install command
if ! exec_command "Fix broken installs" "sudo apt-get --fix-broken install -y"; then
# Exit with an error if the command fails
die 1 "Failed to fix broken installs."
fi
}
# -----------------------------------------------------------------------------
# @brief Install GitHub CLI via APT on Debian/Ubuntu.
# @details
# - Ensures wget is available (installs it if missing)
# - Creates /etc/apt/keyrings with proper permissions
# - Downloads and installs GitHub CLI GPG key
# - Adds the official GitHub CLI APT repository
#
# @param $1 Optional 'debug' flag to enable debug output.
# @global None
# @return 0 on success, non-zero on error.
# -----------------------------------------------------------------------------
install_github_cli_keys() {
# Prepare the keyrings directory
exec_command "Creating /etc/apt/keyrings" "sudo mkdir -p -m 755 /etc/apt/keyrings"
local url="https://cli.github.com/packages/githubcli-archive-keyring.gpg"
local dest="/etc/apt/keyrings/githubcli-archive-keyring.gpg"
# Download the script
if ! exec_command "Downloading GitHub GPG keys" "sudo curl -fsSL $url -o $dest"; then
warn "Failed to download GitHub GPG keys"
fi
# Set ownership and permissions
if ! exec_command "Setting permissions and ownership" "sudo chmod go+r $dest"; then
warn "Failed to set permissions and ownership"
fi
# Build the repo line
local arch repo_line cmd
arch=$(dpkg --print-architecture)
repo_line="deb [arch=${arch} signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main"
# Pass just the pipeline to exec_command
cmd="echo \"$repo_line\" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null"
if ! exec_command "Adding GitHub CLI APT repository" "$cmd"; then
warn "Failed to add GitHub CLI APT repository"
fi
return 0
}
# -----------------------------------------------------------------------------
# @brief Refresh the apt-get package cache.
# @details This function runs `apt-get update` to update the local package index,
# ensuring the system has the latest information about available packages.
# If the update operation fails, the script exits with an error message.
#
# @throws Exits the script with an error message if the `apt-get update` command fails.
#
# @example
# refresh_apt
# # Updates the package list. If successful, the function returns 0.
# # Otherwise, the script exits with an error.
# -----------------------------------------------------------------------------
refresh_apt() {
# Execute the apt-get update command to refresh the package cache
if ! exec_command "Update local package index" "sudo apt-get update -y"; then
# Exit with an error if the command fails
die 1 "Failed to update package list."
fi
# Install apt-transport-https to get past https errors before upgrades
if ! exec_command "Update https transport" "sudo apt-get install -y apt-transport-https"; then
# Exit with an error if the command fails
die 1 "Failed to update apt https transport."
fi
}
# -----------------------------------------------------------------------------
# @brief Perform package upgrades for all upgradable apt packages.
# @details This function checks for packages marked as upgradable using
# `apt list --upgradable`, and then upgrades them one at a time.
# If no packages are upgradable, the function exits without error.
#
# @throws Exits the script if a package upgrade command fails.
#
# @example
# upgrade_exist
# # Upgrades all packages listed as upgradable. If no packages are upgradable,
# # the function returns 0 without making any changes.
# -----------------------------------------------------------------------------
upgrade_exist() {
# Local variables to store the list of upgradable packages and current package name
local pkg_list="" # List of upgradable packages
local pkg_name="" # Current package being processed
# Fetch the list of upgradable packages
pkg_list=$(apt list --upgradable 2>/dev/null | awk -F '/' 'NR > 1 {print $1}')
# Check if there are any packages to upgrade
if [ -z "$pkg_list" ]; then
# No packages are upgradable; return successfully
return 0
fi
# Iterate through each package in the list and upgrade it
for pkg_name in $pkg_list; do
# Upgrade the current package
exec_command "Updating $pkg_name" "sudo apt-get install $pkg_name -y"
done
# Return successfully after processing all packages
return 0
}
# -----------------------------------------------------------------------------
# @brief Perform system updates, upgrades, and maintenance.
# @details This function executes a full system upgrade using `apt-get`.
#
# @throws Exits the script if the full system upgrade fails.
#
# @example
# run_upgrade
# # Performs a full system upgrade
# -----------------------------------------------------------------------------
run_upgrade() {
# Perform a full system upgrade
if ! exec_command "Perform system upgrade" "sudo apt-get full-upgrade -y"; then
die 1 "System upgrade failed."
fi
}
# -----------------------------------------------------------------------------
# @brief Install required packages.
# @details This function ensures that all packages listed in the `APTPACKAGES`
# array are installed. If a package is not installed, it is installed
# using `apt-get install`. The function skips already installed packages.
#
# @global APTPACKAGES An array of package names to be installed.
#
# @throws Exits the script if a package installation fails.
#
# @example
# install_apt
# # Installs all required packages from the APTPACKAGES array that are not
# # already installed.
# -----------------------------------------------------------------------------
install_apt() {
# Declare local variables
local package # Current package being processed
# Iterate through the list of packages
for package in "${APTPACKAGES[@]}"; do
# Check if the package is already installed
if ! dpkg-query -W -f='${Status}' "$package" 2>/dev/null | grep -q "install ok installed"; then
# Attempt to install the package if not installed
if ! exec_command "Install $package" "sudo apt-get install -y $package"; then
die 1 "Failed to install package: $package"
fi
fi
done
# Return successfully after processing all packages
return 0
}
# -----------------------------------------------------------------------------
# @brief Perform apt cache cleanup and remove unused packages.
# @details This function executes `apt-get autoremove` to remove unnecessary
# packages and `apt-get clean` to clear the package cache. These steps
# free up disk space and ensure a clean package environment.
#
# @throws Exits the script if either the autoremove or clean operation fails.
#
# @example
# run_cleanup
# # Removes unused packages and cleans up the apt package cache.
# -----------------------------------------------------------------------------
run_cleanup() {
# Remove unused packages and their configuration files
if ! exec_command "Remove unused packages" "sudo apt-get autoremove --purge -y"; then
die 1 "Failed to remove unused packages."
fi
# Clean up the apt package cache
if ! exec_command "Clean package cache" "sudo apt-get clean"; then
die 1 "Failed to clean package cache."
fi
}
# -----------------------------------------------------------------------------
# @brief Download the .bash_aliases file.
# @details This function downloads the `.bash_aliases` file from the URL
# specified in the `BASH_ALIASES_URL` variable and saves it to the
# user's home directory. It verifies that the file is downloaded,
# moves it to the correct location, and sets appropriate ownership
# and permissions.
#
# @global BASH_ALIASES_URL The URL from which the `.bash_aliases` file is downloaded.
#
# @throws Exits the script if:
# - `BASH_ALIASES_URL` is not set.
# - The file fails to download or move.
# - Permissions or ownership adjustments fail.
#
# @example
# download_aliases
# # Downloads the `.bash_aliases` file from the URL and saves it in the user's
# # home directory with correct ownership and permissions.
# -----------------------------------------------------------------------------
download_aliases() {
# Ensure the URL is set
if [ -z "$BASH_ALIASES_URL" ]; then
die 1 "Bash alias URL is not set."
fi
# Download the file to a temporary location in the user's home directory
if ! exec_command "Download aliases" "curl -fsSL \"$BASH_ALIASES_URL\" -o \"$HOME/temp.aliases\""; then
die 1 "Unable to download aliases."
fi
# Verify that the downloaded file exists
if [ ! -f "$HOME/temp.aliases" ]; then
die 1 "Downloaded file does not exist at $HOME/temp.aliases."
fi
# Move the downloaded file to `.bash_aliases` in the home directory
if ! exec_command "Move aliases to .bash_aliases" "mv \"$HOME/temp.aliases\" \"$HOME/.bash_aliases\""; then
die 1 "Failed to move .bash_aliases to the home directory."
fi
# Verify the file was moved successfully
if [ ! -f "$HOME/.bash_aliases" ]; then
die 1 "Download completed but .bash_aliases file is missing."
fi
# Adjust ownership of the `.bash_aliases` file
if ! exec_command "Change owner" "chown $USER:$USER \"$HOME/.bash_aliases\""; then
die 1 "Unable to change ownership."
fi
# Set appropriate permissions for the `.bash_aliases` file
if ! exec_command "Change execution mode" "chmod 600 \"$HOME/.bash_aliases\""; then
die 1 "Unable to change execution mode."
fi
}
# -----------------------------------------------------------------------------
# @brief Installs a command script to /usr/bin with the specified options.
# @details This function creates a script at /usr/bin, sets executable
# permissions, and assigns root ownership. The script allows for
# custom `ls` options and defaults to the current directory if no
# arguments are provided.
#
# @param $1 Name of the command (e.g., "la" or "ll").
# @param $2 `ls` options to use for the command (e.g., "-hal" or "-hl").
#
# @throws Writes error messages to stderr if permissions or ownership cannot
# be set, or if the script fails to create.
#
# @example
# install_command "la" "-hal"
# install_command "ll" "-hl"
# -----------------------------------------------------------------------------
install_command() {
local command_name="$1"
local bin_path="/usr/local/bin"
local command_path="$bin_path/${command_name}"
local ls_options="$2"
# Create a temporary script file
local tmp_file="/tmp/${command_name}.tmp"
cat << EOF > "$tmp_file"
#!/usr/bin/env bash
lsclean() {
local target="\${1:-.}"
# Ensure the target variable is always set
if [[ -z "\$target" ]]; then
target="."
fi
ls ${ls_options} --color=always "\$target" 2>/dev/null | sed -E 's/ -> .*//'
}
lsclean "\$@"
EOF
# Verify the file is written
if [[ ! -s "$tmp_file" ]]; then
die 1 "Failed to write temporary script file: $tmp_file"
fi
# Move it to /usr/local/bin with sudo
sudo mv "$tmp_file" "$command_path"
sudo chmod +x "$command_path"
sudo chown root:root "$command_path"
}
# -----------------------------------------------------------------------------
# @brief Installs the `la` command.
# @details The `la` command lists all files, including hidden ones, in a
# long format with human-readable sizes and color highlighting.
# -----------------------------------------------------------------------------
install_la_command() {
install_command "la" "-hal"
}
# -----------------------------------------------------------------------------
# @brief Installs the `ll` command.
# @details The `ll` command lists files (excluding hidden ones) in a
# long format with human-readable sizes and color highlighting.
# -----------------------------------------------------------------------------
install_ll_command() {
install_command "ll" "-hl"
}
# -----------------------------------------------------------------------------
# @brief Installs the `whitespace_clean` command.
# @details The `whitespace_clean` command lists files (excluding hidden ones) in a
# long format with human-readable sizes and color highlighting.
# -----------------------------------------------------------------------------
install_whitespace_clean() {
local url="https://gist.githubusercontent.com/lbussy/a0c00aa8b5f68d0aeb2e2c6dcd1676e8/raw/whitespace_clean"
local dest="/usr/local/bin/whitespace_clean"
# Download the script
if ! exec_command "Downloading whitespace_clean" "sudo curl -fsSL $url -o $dest"; then
warn "Failed to download whitespace_clean"
fi
# Set ownership and permissions
if ! exec_command "Setting permissions and ownership." "sudo chown root:root $dest && sudo chmod 755 $dest" ; then
warn "Failed to set permissions and ownership"
fi
return 0
}
# -----------------------------------------------------------------------------
# @brief Updates the `needrestart` configuration with the correct kernel filter.
#
# @details
# - Extracts the kernel suffix from `uname -r` (everything after the last `-`).
# - Writes the appropriate kernel filter regex to `/etc/needrestart/conf.d/kernel.conf`.
# - Ensures `sudo` permissions are used to write to the configuration file.
# - Uses `printf` for safer multi-line output handling.
#
# @throws
# - Exits if `uname -r` fails.
# - Requires `sudo` permissions; will fail if `sudo` is not available.
#
# @example
# need_restart_regex
# # Updates `/etc/needrestart/conf.d/kernel.conf` with the current kernel type.
# -----------------------------------------------------------------------------
need_restart_regex() {
set -e # Ensure function exits on failure
# Detect the kernel version suffix (last part after the last '-')
local kernel_ver
kernel_ver=$(uname -r | awk -F'-' '{print $NF}') || {
echo "❌ Failed to retrieve kernel version."
return 1
}
# Update the needrestart configuration with the correct kernel filter
sudo tee /etc/needrestart/conf.d/kernel.conf > /dev/null <<EOF
# Filter kernel image filenames by regex. This is required on Raspbian having
# multiple kernel image variants installed in parallel.
\$nrconf{kernelfilter} = qr(vmlinuz-.*-${kernel_ver}$);
EOF
}
# -----------------------------------------------------------------------------
# @brief Notify the user about personal aliases.
# @details This function creates a `.personal_aliases` file in the user's home
# directory (if it does not already exist). It sets the appropriate
# ownership and permissions for the file and provides instructions
# for adding personal aliases that are automatically loaded when
# `.bash_aliases` is sourced.
#
# @throws Issues warnings if the file cannot be created, or ownership and
# permissions cannot be updated.
#
# @example
# notify_personal_aliases
# # Ensures the `.personal_aliases` file exists, sets proper ownership
# # and permissions, and provides user instructions.
# -----------------------------------------------------------------------------
notify_personal_aliases() {
# Local variable for the .personal_aliases file path
local personal_aliases="$HOME/.personal_aliases"
# Create the .personal_aliases file if it does not exist
if ! exec_command "Create .personal_aliases" "touch $personal_aliases"; then
warn "Unable to create .personal_aliases file."
fi
# Update ownership of the .personal_aliases file
if ! exec_command "Change ownership" "chown $USER:$USER $personal_aliases"; then
warn "Unable to change .personal_aliases ownership."
fi
# Update permissions for the .personal_aliases file
if ! exec_command "Change execution mode" "chmod 600 $personal_aliases"; then
warn "Unable to change .personal_aliases execution mode."
fi
# Display instructions for adding personal aliases
cat <<EOF
If you want to save your own aliases, create a file named ${BOLD}.personal_aliases${RESET}
in your home directory.
Any aliases in that file will be automatically loaded when ${BOLD}.bash_aliases${RESET} is
sourced (e.g., during login or when starting a new shell session).
Example:
${BOLD}echo "alias ll='ls -lh'" >> ~/.personal_aliases${RESET}
Then, reload with:
${BOLD}reload_aliases${RESET}
EOF
# Indicate successful completion
return 0
}
# -----------------------------------------------------------------------------
# @brief Remind the user to reload their bash aliases.
# @details
# Prints a message instructing the user to source their
# .bash_aliases file immediately or reboot/restart their shell session.
#
# @param None
# @return None
# -----------------------------------------------------------------------------
reload_aliases_reminder() {
cat << 'EOF'
Your bash aliases have been updated.
To apply them right now, run:
source ./.bash_aliases
Or restart your shell session (e.g. log out and back in, or reboot).
EOF
}
# -----------------------------------------------------------------------------
# @brief Main script execution flow.
# @details This function orchestrates the script's execution by calling all the
# required functions in a defined sequence. It ensures the system meets
# necessary prerequisites, performs updates, installs packages, sets up
# aliases, and checks if a reboot is required. The flow is designed to
# handle errors gracefully and provide clear feedback to the user.
#
# @throws Exits the script if any critical errors are encountered in the checks
# or setup phases.
#
# @example
# _main "$@"
# # Executes the main script logic.
# -----------------------------------------------------------------------------
_main() {
# Clear the screen for a clean start
clear
# Display a message indicating the script's start
printf "%s beginning.\n\n" "$THIS_NAME"
# Check for required commands
check_commands
# Verify the system is Debian-based
check_debian_based
# Check for sudo presence and privileges if required
check_sudo
# Request user confirmation in interactive mode
confirm_action
# Initialize terminal colors and formatting
init_colors
# Fix broken package installations
fix_broken
# Install GitHub keys and add the GitHub CLI APT repository
install_github_cli_keys
# Update the apt-get package cache
refresh_apt
# Upgrade existing packages
upgrade_exist
# Perform system upgrades, including checking for kernel updates
run_upgrade
# Install necessary packages
install_apt
# Perform apt cache cleanup
run_cleanup
# Download and set up the .bash_aliases file
download_aliases
# Install the `la` and `ll` commands
install_la_command
install_ll_command
# Install the `whitespace_clean` command
install_whitespace_clean
# Fix regex for kernel detection and run
need_restart_regex
setsid sudo needrestart < /dev/tty
# Notify the user about setting up personal aliases
notify_personal_aliases
# Display a final message indicating successful completion
printf "\n%s completed successfully.\n" "$THIS_NAME"
# Display a notice about loading aliases
reload_aliases_reminder
}
# -----------------------------------------------------------------------------
# @brief Main function entry point.
# @details This function calls `_main` to initiate the script execution. By
# calling `main`, we enable the correct reporting of the calling
# function in Bash, ensuring that the stack trace and function call
# are handled appropriately during the script execution.
#
# @param "$@" Arguments to be passed to `_main`.
# @return Returns the status code from `_main`.
# -----------------------------------------------------------------------------
main() { _main "$@"; return "$?"; }
main "$@"
exit $?
#!/bin/env bash
# -----------------------------------------------------------------------------
# General Helpers and Shortcuts
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# @var cd..
# @brief Corrects a common typo when navigating directories.
# -----------------------------------------------------------------------------
alias cd..='cd ..'
# -----------------------------------------------------------------------------
# @var ll
# @brief Lists files in long format with human-readable sizes.
# -----------------------------------------------------------------------------
# Moved this to a function in path as ll
# alias ll='ls -lh'
# -----------------------------------------------------------------------------
# @var la
# @brief Lists all files, including hidden files, in long format with
# human-readable sizes.
# -----------------------------------------------------------------------------
# Moved this to a function in path as la
# alias la="ls -alh --color=always | awk '!/^l/ {print} /^l/ {sub(/ -> .*/, \"\"); print}'"
# -----------------------------------------------------------------------------
# @var grep
# @brief Highlights matches in grep output for better visibility.
# -----------------------------------------------------------------------------
alias grep='grep --color=auto'
# -----------------------------------------------------------------------------
# @var egrep
# @brief Alias for extended grep functionality.
# -----------------------------------------------------------------------------
alias egrep='grep -E'
# -----------------------------------------------------------------------------
# @var fgrep
# @brief Alias for fixed-string grep functionality.
# -----------------------------------------------------------------------------
alias fgrep='grep -F'
# -----------------------------------------------------------------------------
# @var cls
# @brief Clears the terminal screen.
# -----------------------------------------------------------------------------
alias cls='clear'
# -----------------------------------------------------------------------------
# @var clean_whitespace
# @brief Cleans excess whitespace from the end of lines (such as for source files)
# -----------------------------------------------------------------------------
# Removed in favor of /usr/local/bin/whitespace_clean
# -----------------------------------------------------------------------------
# System Commands
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# @var reboot
# @brief Reboots the system immediately with sudo privileges.
# -----------------------------------------------------------------------------
alias reboot='wall "Rebooting." 2>/dev/null && sleep 1 && sudo reboot now'
# -----------------------------------------------------------------------------
# @var shutdown
# @brief Shuts down the system immediately with sudo privileges.
# -----------------------------------------------------------------------------
alias shutdown='wall "Shutting down." && sleep 1 && sudo shutdown now -h'
# -----------------------------------------------------------------------------
# @var ping
# @brief Pings a host with three packets by default.
# -----------------------------------------------------------------------------
alias ping='ping -c 3'
# -----------------------------------------------------------------------------
# System Maintenance
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# @var update
# @brief Updates the system, upgrades packages, and performs cleanup with error handling.
# -----------------------------------------------------------------------------
alias update='echo "Running update..."; \
bash -e -c '\''trap "echo Error: Upgrade alias failed; exit 1" ERR; \
sudo apt --fix-broken install -y && \
sudo apt update && \
sudo apt full-upgrade -y && \
sudo apt autoremove --purge -y && \
sudo apt clean && \
sudo needrestart < /dev/tty'\'''
# -----------------------------------------------------------------------------
# @var upgrade
# @brief Alias for the `update` process.
# -----------------------------------------------------------------------------
alias upgrade='update'
# -----------------------------------------------------------------------------
# @var cleanup
# @brief Cleans up unneeded packages and clears system caches.
# -----------------------------------------------------------------------------
alias cleanup='echo "Cleaning apt system..."; sudo apt autoremove -y && \
sudo apt autoclean -y && rm -rf ~/.cache/thumbnails/*'
# -----------------------------------------------------------------------------
# Functions for Flexibility
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# @fn reload_aliases
# @brief Reloads aliases from the ~/.bash_aliases file.
# @param [optional] --debug Enables debugging output.
# -----------------------------------------------------------------------------
reload_aliases() {
local debug=false
if [[ $1 == "--debug" ]]; then
debug=true
fi
if [ -f ~/.bash_aliases ]; then
# shellcheck source=/dev/null
source ~/.bash_aliases
echo "Aliases reloaded!"
$debug && echo "DEBUG: Sourced ~/.bash_aliases"
else
echo "Error: ~/.bash_aliases not found!"
$debug && echo "DEBUG: Check if the file path is correct."
fi
}
# -----------------------------------------------------------------------------
# Source Personal Aliases
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# @brief Sources personal aliases from the ~/.personal_aliases file if it exists.
# -----------------------------------------------------------------------------
if [ -f ~/.personal_aliases ]; then
# shellcheck source=/dev/null
source ~/.personal_aliases
echo "Loaded personal aliases from ~/.personal_aliases"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment