#!/usr/bin/env bash # Begin profile # ------------------------------------------------------------------------------ # Copyright (c) 2013-2026 Geoffray Levasseur # Protected by the BSD3 license. Please read bellow for details. # # * Redistribution and use in source and binary forms, # * with or without modification, are permitted provided # * that the following conditions are met: # * # * Redistributions of source code must retain the above # * copyright notice, this list of conditions and the # * following disclaimer. # * # * Redistributions in binary form must reproduce the above # * copyright notice, this list of conditions and the following # * disclaimer in the documentation and/or other materials # * provided with the distribution. # * # * Neither the name of the copyright holder nor the names # * of any other contributors may be used to endorse or # * promote products derived from this software without # * specific prior written permission. # * # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND # * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, # * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR # * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # * OF SUCH DAMAGE. # ------------------------------------------------------------------------------ if [[ ! $SHELL =~ bash ]]; then echo "That environment script is designed to be used with bash being the shell." echo "Please consider using bash to enjoy our features!" return 1 fi # Required for associative arrays (4.0+) and namerefs (4.3+) if ((BASH_VERSINFO[0] < 4)) || [[ ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -lt 3 ]]; then echo "[ Error ] This profile requires Bash 4.3 or higher." echo "Current version: $BASH_VERSION" (return 0 2>/dev/null) && return 1 || exit 1 fi # ------------------------------------------------------------------------------ # path* : private functions for PATH variable management pathremove() { [[ -z "$1" ]] && return 0 local IFS=':' local newpath dir local pathvar="${2:-PATH}" [[ "$pathvar" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || { printf "pathremove: unsafe variable name '%s'\n" "$pathvar" >&2 return 1 } for dir in ${!pathvar}; do [[ "$dir" != "$1" ]] && newpath="${newpath:+$newpath:}$dir" done export "$pathvar=$newpath" } #pathprepend() # Unused for now, but might be useful in the future #{ # [[ -z "$1" ]] && return 0 # local pathvar="${2:-PATH}" # [[ "$pathvar" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || { # printf "pathprepend: unsafe variable name '%s'\n" "$pathvar" >&2 # return 1 # } # pathremove "$1" "$pathvar" # export "$pathvar=$1${!pathvar:+:${!pathvar}}" #} pathappend() { [[ -z "$1" ]] && return 0 local pathvar="${2:-PATH}" [[ "$pathvar" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || { printf "pathappend: unsafe variable name '%s'\n" "$pathvar" >&2 return 1 } pathremove "$1" "$pathvar" export "$pathvar=${!pathvar:+${!pathvar}:}$1" } # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # Configuration file parser parse_conf() { local config_file="$1" local current_section="" local key value [[ ! -f "$config_file" ]] && return 1 while IFS='=' read -r key value || [[ -n "$key" ]]; do # Internal trimming (removes leading/trailing whitespace & CR) key="${key%"${key##*[![:space:]]}"}" key="${key#"${key%%[![:space:]]*}"}" key="${key%$'\r'}" # Strip potential Windows line endings # Skip comments and empty lines [[ -z "$key" || "$key" =~ ^[#\;] ]] && continue # Section Detection: [section_name] if [[ "$key" =~ ^\[([a-zA-Z0-9_]+)\]$ ]]; then current_section="${BASH_REMATCH[1]}" declare -g -A "CONF_$current_section" continue fi # Secure Assignment (if inside a section) if [[ -n "$current_section" ]]; then # Clean the value value="${value%"${value##*[![:space:]]}"}" value="${value#"${value%%[![:space:]]*}"}" value="${value%$'\r'}" # Protect against command injection by disallowing certain characters in keys value="${value//\`/}" value="${value//\$\(/}" # Correctly interpretet internal variables (e.g. $HOME) if [[ "$value" == *\$* ]]; then value=$(envsubst <<< "$value") fi # Strip quotes (handling both " and ') value="${value%\"}"; value="${value#\"}" value="${value%\'}"; value="${value#\'}" # Use a nameref for safe, eval-free assignment local -n current_array="CONF_$current_section" # shellcheck disable=SC2034 # Dynamic var creation current_array["$key"]="$value" fi done < "$config_file" } # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # Load command aliases from configuration load_alias() { local section_name="CONF_$1" # Check if the associative array exists using declare -p [[ "$(declare -p "$section_name" 2>/dev/null)" != "declare -A"* ]] && return 1 # Create a nameref to the section array local -n current_aliases="$section_name" # Iterate safely over the keys of the associative array for key in "${!current_aliases[@]}"; do local cmd="${current_aliases[$key]}" # Extract the base command (first word) safely without awk local base_cmd="${cmd%% *}" # Only alias if the base command is executable if command -v "$base_cmd" >/dev/null 2>&1; then # shellcheck disable=SC2139 # Dynamic alias creation alias "$key"="$cmd" fi done } # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # Load configuration values as environment variables load_conf() { local section_name="CONF_$1" [[ "$(declare -p "$section_name" 2>/dev/null)" != "declare -A"* ]] && return 1 local -n current_vars="$section_name" for key in "${!current_vars[@]}"; do # Export the key/value pair as a standard shell variable # We use 'export' directly; Bash handles the assignment safely here export "$key"="${current_vars[$key]}" done } # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # ********************************** MAIN PROGRAM ****************************** # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # Store script's path (realpath -s resolve symlinks if profile.sh is a symlink) # Because we're more likely to be sourced, we use BASH_SOURCE to get the path # of the sourced file instead of $0 if [[ -z "$PROFILE_PATH" ]]; then MYPATH=$(dirname "$(realpath -s "${BASH_SOURCE[0]}")") else MYPATH="$PROFILE_PATH" fi export MYPATH if [[ ! -e "$MYPATH/profile.sh" ]]; then echo "[ Warning ] Path detection failed, trying to use pwd..." MYPATH=$(pwd) if [[ ! -e "$MYPATH/profile.sh" ]]; then echo "[ Error ] Impossible to determine installation path, pretty much nothing will work." fi fi if [[ ! -s "$MYPATH/version" ]]; then echo "[ Warning ] Impossible to determine running version of profile, your installation might be broken." fi PROFVERSION=$(cat "$MYPATH"/version) export PROFVERSION # Build PATH environment variable if [[ $EUID -eq 0 ]]; then pathappend /sbin:/usr/sbin fi [[ -d ~/bin ]] && pathappend ~/bin [[ -d ~/.local/bin ]] && pathappend ~/.local/bin # Parse and load general configuration export PROFILE_CONF="$MYPATH/profile.conf" parse_conf "$PROFILE_CONF" load_conf system # Load Bash system behavior configuration (history, pager, etc.) load_conf general # General purpose configuration (compilation flags, etc.) # Load module scripts shopt -s nullglob for script in "$MYPATH/profile.d/"*.sh; do if [[ -f "$script" && -r "$script" ]]; then # shellcheck source=/dev/null . "$script" || printf "[ Warning ] Failed to source module: %s\n" "$script" >&2 fi done shopt -u nullglob # Interactive shell detection, two methods available each one of those might have different result # depending on distribution #shopt -q login_shell && INTERACTIVE=1 [[ $- == *i* ]] && export INTERACTIVE=1 if [[ $INTERACTIVE ]]; then # For compiling (as we often compile with LFS/0linux...) #Aliases load_alias aliases # Define PS1 trap 'timer_start' DEBUG PROMPT_COMMAND='set_prompt' # Set default language from DEFAULT_LANG config key (set in [general]). # The value must match one of the alias names defined in SET_LOCALE so that # the corresponding set function exists after build_locale_shortcuts. if [[ -n "${DEFAULT_LANG:-}" ]]; then _lang_fn="set${DEFAULT_LANG}" if declare -F "$_lang_fn" >/dev/null 2>&1; then "$_lang_fn" else disp W "DEFAULT_LANG '$DEFAULT_LANG' has no matching locale shortcut (check SET_LOCALE in profile.conf)." fi unset _lang_fn fi showinfo && printf "\n" check_updates -q disp I "Profile version $PROFVERSION chargé..." fi # Cleanup unset pathremove pathprepend pathappend #return 0 # End profile.sh