30 Commits

Author SHA1 Message Date
fatalerrors
84e6fdd429 added entropy and password source to pwdscore 2026-04-01 18:15:11 +02:00
fatalerrors
8fe11776cb add --all-users to rmhost, hardening 2026-04-01 17:54:23 +02:00
fatalerrors
0737d0c647 reworked genpwd / introduce pwdscore 2026-04-01 17:52:09 +02:00
fatalerrors
d72fa1a712 hardening 2026-04-01 17:21:54 +02:00
fatalerrors
08e9e6c799 greatly improved upgrade system 2026-04-01 17:20:49 +02:00
fatalerrors
ac66e896dd exit -> return 2026-04-01 15:56:48 +02:00
fatalerrors
0712be626b wording and other minor improvments 2026-04-01 15:53:44 +02:00
fatalerrors
3f8b81562b sorted alias out 2026-04-01 15:53:10 +02:00
fatalerrors
96d1dc695d added missing function in help 2026-04-01 15:28:12 +02:00
fatalerrors
c039ab6ea0 improved and modernised file functions 2026-04-01 15:27:45 +02:00
fatalerrors
6b85556a53 typo 2026-03-26 17:19:06 +01:00
fatalerrors
cf9a85e61b update default conf with new values 2026-03-26 17:13:24 +01:00
fatalerrors
75e047d70e update help 2026-03-26 16:48:21 +01:00
fatalerrors
742ec484a7 made langage definition shortcuts configurable 2026-03-26 16:47:16 +01:00
fatalerrors
1b7262c0cd few minor update fix 2026-03-26 15:12:18 +01:00
fatalerrors
e387209c10 busy can properly exit, pattern can not inject code 2026-03-26 14:43:12 +01:00
fatalerrors
f5d59ec194 git parameter conflict, better matrix rendering, better speed unit handling 2026-03-25 18:11:01 +01:00
fatalerrors
2ee1c935ac make the rain able to be matrix 2026-03-25 17:48:27 +01:00
fatalerrors
e41c1a4c51 harden rmspc, better median calculation in file_stats 2026-03-25 16:10:41 +01:00
fatalerrors
60dfe19049 message type case unsensitive 2026-03-25 15:39:05 +01:00
fatalerrors
c32771a4ff default configuration updated 2026-03-25 15:28:08 +01:00
fatalerrors
080511d0bd add NO_COLOR support 2026-03-25 15:27:23 +01:00
fatalerrors
d8bdfefdf1 typo 2026-03-25 15:26:39 +01:00
fatalerrors
5f5f9c0e71 add trap protection, and option to force remplacement 2026-03-25 15:14:10 +01:00
fatalerrors
30387a4f08 update copyright info, uniformize sheebang 2026-03-25 14:39:58 +01:00
fatalerrors
0c51363d86 revert factorisation attempt 2026-03-25 14:37:20 +01:00
fatalerrors
043fbaef0b protect against code injection, interpret vars 2026-03-25 14:35:53 +01:00
fatalerrors
ed5587712e support for spaced filename 2026-03-25 14:19:08 +01:00
fatalerrors
58cc76c317 pre generate sections of the configuration file 2026-03-11 17:20:50 +01:00
fatalerrors
e82ee06e1d secured some implementation, check bash version 2026-03-11 11:41:56 +01:00
18 changed files with 1585 additions and 557 deletions

View File

@@ -1,6 +1,6 @@
[system] [system]
# System section is used to set Bash behavior and other system related variables, # System section is used to set Bash behavior and other system related
# such as the default pager, the terminal type, etc. # variables, such as the default pager, the terminal type, etc.
# Set bash history # Set bash history
HISTSIZE=50000 HISTSIZE=50000
HISTIGNORE="&:[bf]g:exit" HISTIGNORE="&:[bf]g:exit"
@@ -11,12 +11,57 @@ PAGER=less
# Set terminal colors behavior # Set terminal colors behavior
TERM=xterm-256color TERM=xterm-256color
[compress]
# Section used by compress.sh
[debug]
# Section used by debug.sh
[disp]
# Section used by disp.sh
# Set to any value to disable colors in internal profile output (not controling binary output)
# NO_COLOR=1
[filefct]
# Section used by filefct.sh
[fun]
# Section used by fun.sh
[info] [info]
# Default city for weather forcast # Section used by info.sh
# Default city for weather forcast and local news
DEFAULT_CITY="Toulouse" DEFAULT_CITY="Toulouse"
[lang]
# Section used by lang.sh
# List of locale shortcuts to build, in the form "shortcut:locale,...".
# Generate a function setXX for each shortcut defined.
SET_LOCALE="fr:fr_FR.UTF-8,us:en_US.UTF-8"
[net]
# Section used by net.sh
[packages]
# Section used by packages.sh
[prompt]
# Section used by prompt.sh
[pwd]
# Section used by pwd.sh
[rain]
# Section used by rain.sh
[ssh]
# Section used by ssh.sh
[updates]
# Section used by updates.sh
[general] [general]
# General section allow to set any variable that can be used by the profile or its functions. # General section allow to set any variable that can be used by the user.
# It is also a good place to set freely global variables for personal use. # It is also a good place to set freely global variables for personal use.
# Set some compiling values # Set some compiling values
CFLAGS="-O2 -pipe -march=native" CFLAGS="-O2 -pipe -march=native"
@@ -25,26 +70,41 @@ MAKEFLAGS='-j12'
PKGSOURCES='/share/src/archives' PKGSOURCES='/share/src/archives'
[aliases] [aliases]
# Aliases section is used to set user aliases, it is loaded only for
# interactive shells.
# Various ls aliases
ll='ls -laFh --color=auto' ll='ls -laFh --color=auto'
la='ls -Ah --color=auto' la='ls -Ah --color=auto'
l='ls -CF --color=auto' l='ls -CF --color=auto'
ls='ls --color=auto' ls='ls --color=auto'
# Add color to grep output
grep='grep --color=auto' grep='grep --color=auto'
egrep='egrep --color=auto' egrep='egrep --color=auto'
fgrep='fgrep --color=auto' fgrep='fgrep --color=auto'
# Quick find alias
qfind="find . -name " qfind="find . -name "
# Some alias for compiling
mk='make'
mkck='make check' mkck='make check'
mkin='make install' mkin='make install'
mkdin='make DESTDIR=$PWD/dest-install install' mkdin='make DESTDIR=$PWD/dest-install install'
# ssh alias with X11 forwarding, without right restriction
ssh='ssh -Y' ssh='ssh -Y'
# Resume mode for wget
wget='wget -c' # resume mode by default wget='wget -c' # resume mode by default
myip='curl ip.appspot.com'
# Human readable by default # Human readable by default
df='df -H' df='df -H'
du='du -ch' du='du -ch'
sdu='du -sk ./* | sort -n' sdu='du -sk ./* | sort -n'
hdu='du -hs ./* | sort -H' hdu='du -hs ./* | sort -H'
# Readable dmesg timestamps
dmesg='dmesg -T'
# End of profile.conf

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -208,7 +208,22 @@ utaz()
disp E "The --create-dir and --no-dir options are mutually exclusive." disp E "The --create-dir and --no-dir options are mutually exclusive."
for zitem in "${FILES[@]}"; do for zitem in "${FILES[@]}"; do
for f in "${zitem}"/*; do # Build list of input files to process, with whitespace-safe handling.
local targets=()
if [[ -f "$zitem" ]]; then
targets=("$zitem")
elif [[ -d "$zitem" ]]; then
mapfile -d '' -t targets < <(find "$zitem" -mindepth 1 -maxdepth 1 -print0 2>/dev/null)
if [[ ${#targets[@]} -eq 0 ]]; then
disp I "Directory ${zitem} is empty, skipping."
continue
fi
else
disp W "Path ${zitem} is not a file or directory, skipping."
continue
fi
for f in "${targets[@]}"; do
local dir="${f%.*}" local dir="${f%.*}"
local extractor="" local extractor=""
case "$f" in case "$f" in
@@ -269,21 +284,21 @@ utaz()
# Verify binary existence # Verify binary existence
local cmd=${extractor//_un/} local cmd=${extractor//_un/}
if [[ $cmd == "deb" ]]; then if [[ $cmd == "deb" ]]; then
command -v dpkg-deb >/dev/null 2>&1 || { command -v -- dpkg-deb >/dev/null 2>&1 || {
disp E "The program 'dpkg-deb' is not installed, aborting." disp E "The program 'dpkg-deb' is not installed, aborting."
continue continue
} }
elif [[ $cmd == "rpm" ]]; then elif [[ $cmd == "rpm" ]]; then
command -v rpm2cpio >/dev/null 2>&1 || { command -v -- rpm2cpio >/dev/null 2>&1 || {
disp E "The program 'rpm2cpio' is not installed, aborting." disp E "The program 'rpm2cpio' is not installed, aborting."
continue continue
} }
command -v cpio >/dev/null 2>&1 || { command -v -- cpio >/dev/null 2>&1 || {
disp E "The program 'cpio' is not installed, aborting." disp E "The program 'cpio' is not installed, aborting."
continue continue
} }
else else
command -v ${cmd} >/dev/null 2>&1 || { command -v -- "${cmd}" >/dev/null 2>&1 || {
disp E "Binary ${cmd} necessary to extract ${f} is missing." disp E "Binary ${cmd} necessary to extract ${f} is missing."
continue continue
} }
@@ -318,7 +333,12 @@ utaz()
if [[ -n ${createdir} ]]; then if [[ -n ${createdir} ]]; then
disp I "Archive extracted successfully in subdirectory." disp I "Archive extracted successfully in subdirectory."
elif [[ -n ${nodir} ]]; then elif [[ -n ${nodir} ]]; then
mv "./${dir}/"* ./ && rmdir "${dir}" shopt -s nullglob
for child in "${dir}"/*; do
mv -- "$child" .
done
shopt -u nullglob
rmdir -- "${dir}"
disp I "Archive extracted successfully, no subdirectory needed." disp I "Archive extracted successfully, no subdirectory needed."
else else
# Set nullglob to ensure the array is empty if no files match # Set nullglob to ensure the array is empty if no files match
@@ -328,7 +348,12 @@ utaz()
# Check if exactly one item exists and if that item is a directory # Check if exactly one item exists and if that item is a directory
if [[ ${#contents[@]} -eq 1 ]] && [[ -d "${contents[0]}" ]]; then if [[ ${#contents[@]} -eq 1 ]] && [[ -d "${contents[0]}" ]]; then
# Single directory detected # Single directory detected
mv "${contents[0]}"/* ./ && rmdir "${dir}" shopt -s nullglob
for child in "${contents[0]}"/*; do
mv -- "$child" .
done
shopt -u nullglob
rmdir -- "${dir}"
disp I "Archive extracted successfully, no subdirectory needed." disp I "Archive extracted successfully, no subdirectory needed."
else else
disp I "Archive extracted successfully in subdirectory." disp I "Archive extracted successfully in subdirectory."

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -46,7 +46,7 @@ function backtrace()
"${FUNCNAME[$i]}" "${BASH_SOURCE[$i]}" "${BASH_LINENO[$(( i-1 ))]}" "${FUNCNAME[$i]}" "${BASH_SOURCE[$i]}" "${BASH_LINENO[$(( i-1 ))]}"
((i++)) ((i++))
done done
unset func i unset i
printf "==============================\n" printf "==============================\n"
} }
@@ -69,30 +69,37 @@ settrace()
#trap -p ERR #trap -p ERR
local PARSED local PARSED
PARSED=$(getopt -oh --long help,on,off,status -- "$@") PARSED=$(getopt -oh --long help,on,off,status,force -- "$@")
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"settrace --help\" to display usage." disp E "Invalid options, use \"settrace --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" eval set -- "$PARSED"
local force=0
while true; do while true; do
case $1 in case $1 in
-h|--help) -h|--help)
printf "Try to activate backtrace display for script debugging.\n\n" printf "Try to activate backtrace display for script debugging.\n\n"
printf "Options:\n" printf "Options:\n"
printf "\t--on\t\tActivate backtrace generation\n" printf "\t--on\t\tActivate backtrace generation\n"
printf "\t--force\t\tForce replacement of existing trap (use with --on)\n"
printf "\t--off\t\tDeactivate backtrace generation\n\n" printf "\t--off\t\tDeactivate backtrace generation\n\n"
printf "That function active a trap event on error. If the script you want to\n" printf "That function active a trap event on error. If the script you want to\n"
printf "debug overload the ERR bash trap, it will not work.\n" printf "debug overload the ERR bash trap, it will not work.\n"
return 0 return 0
;; ;;
--on) --on)
if [[ ${status} == "on" ]]; then if [[ ${status} == "on" ]] && [[ $force -eq 0 ]]; then
disp W "ERR signal trap is already set, replacing previous trap!" disp E "ERR signal trap is already set. Use --force to replace it."
return 1
fi fi
trap "error" ERR trap "error" ERR
shift shift
;; ;;
--force)
force=1
shift
;;
--off) --off)
if [[ ${status} != "on" ]]; then if [[ ${status} != "on" ]]; then
disp W "ERR signal trap is already unset!" disp W "ERR signal trap is already unset!"
@@ -101,7 +108,7 @@ settrace()
shift shift
;; ;;
--status) --status)
disp "ERR trap signal is ${status}." disp I "Trap signal is ${status}."
shift shift
;; ;;
--) --)
@@ -114,7 +121,7 @@ settrace()
;; ;;
esac esac
done done
unset status unset status force
} }
export -f settrace export -f settrace
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -124,25 +124,45 @@ export On_IWhite='\e[0;107m'
# D : debug (cyan) # D : debug (cyan)
disp() disp()
{ {
case $1 in # Handle NO_COLOR: disable colors if set
local color_enabled=1
[[ -n $NO_COLOR ]] && color_enabled=0
case ${1^^} in
"I") "I")
if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${IGreen}info${DEFAULTFG} ]" local heads="[ ${IGreen}info${DEFAULTFG} ]"
else
local heads="[ info ]"
fi
shift shift
[[ -z $QUIET || $QUIET -ne 1 ]] && \ [[ -z $QUIET || $QUIET -ne 1 ]] && \
printf "%b\n" "${heads} $*${RESETCOL}" printf "%b\n" "${heads} $*${RESETCOL}"
;; ;;
"W") "W")
if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${IYellow}Warning${DEFAULTFG} ]" local heads="[ ${IYellow}Warning${DEFAULTFG} ]"
else
local heads="[ Warning ]"
fi
shift shift
printf "%b\n" "${heads} $*${RESETCOL}" >&2 printf "%b\n" "${heads} $*${RESETCOL}" >&2
;; ;;
"E") "E")
if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${IRed}ERROR${DEFAULTFG} ]" local heads="[ ${IRed}ERROR${DEFAULTFG} ]"
else
local heads="[ ERROR ]"
fi
shift shift
printf "%b\n" "${heads} $*${RESETCOL}" >&2 printf "%b\n" "${heads} $*${RESETCOL}" >&2
;; ;;
"D") "D")
if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${ICyan}debug${DEFAULTFG} ]" local heads="[ ${ICyan}debug${DEFAULTFG} ]"
else
local heads="[ debug ]"
fi
shift shift
[[ -n $DEBUG && $DEBUG -gt 1 ]] && \ [[ -n $DEBUG && $DEBUG -gt 1 ]] && \
printf "%b\n" "${heads} $*${RESETCOL}" printf "%b\n" "${heads} $*${RESETCOL}"
@@ -157,4 +177,7 @@ export -f disp
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Load disp section variables
load_conf disp
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -35,32 +35,85 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# expandlist : treat wildcards in a file/directory list # Expand wildcards in a file/directory list and quote the results
# Usage: expandlist <item1 [item2 ... itemN]> # Usage: expandlist [options] <item1 [item2 ... itemN]>
expandlist() expandlist()
{ {
if [[ "$1" == "-h" || "$1" == "--help" ]]; then local separator=" "
printf "expandlist: Wraps a list of items in double quotes.\n\n" local PARSED
printf "Usage: expandlist <item1 [item2 ... itemN]>\n\n"
PARSED=$(getopt -o hs:n --long help,separator:,newline -n 'expandlist' -- "$@")
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"expandlist --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "expandlist: expand globs and wrap matched items in double quotes.\n\n"
printf "Usage: expandlist [options] <item1 [item2 ... itemN]>\n\n"
printf "Options:\n" printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n" printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-s, --separator SEP\tSet output separator (default: space)\n"
printf "\t-n, --newline\t\tUse a newline as separator\n"
return 0 return 0
;;
-s|--separator)
separator="$2"
shift 2
;;
-n|--newline)
separator=$'\n'
shift
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"expandlist --help\" to display usage."
return 1
;;
esac
done
local item="" result="" matched=0
shopt -s nullglob
for item in "$@"; do
local expanded=()
# True glob expansion when wildcards are present.
if [[ "$item" == *'*'* || "$item" == *'?'* || "$item" == *'['* ]]; then
expanded=( $item )
else
expanded=( "$item" )
fi fi
local result="" if [[ ${#expanded[@]} -eq 0 ]]; then
for item in "$1"; do continue
for content in "$item"; do fi
for content in "${expanded[@]}"; do
if (( matched )); then
result+="$separator"
fi
result+="\"$content\"" result+="\"$content\""
matched=1
done done
done done
echo $result
shopt -u nullglob
printf '%s\n' "$result"
} }
export -f expandlist export -f expandlist
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Clean a directory or a tree from temporary or backup files # Clean a directory tree from temporary or backup files
# Usage: clean [options] [directory1] [...[directoryX]] # Usage: clean [options] [directory1] [...[directoryX]]
# Options: # Options:
# -h, --help: display help screen # -h, --help: display help screen
@@ -85,7 +138,7 @@ clean()
while true; do while true; do
case "$1" in case "$1" in
-r|--recurs) -r|--recurs)
local recursive=1 recursive=1
shift shift
;; ;;
-h|--help) -h|--help)
@@ -100,11 +153,11 @@ clean()
return 0 return 0
;; ;;
-s|--shell) -s|--shell)
local outshell=1 outshell=1
shift shift
;; ;;
-f|--force) -f|--force)
local force=1 force=1
shift shift
;; ;;
--) --)
@@ -114,6 +167,7 @@ clean()
*) *)
disp E "Invalid parameter, use \"clean --help\" to display options list" disp E "Invalid parameter, use \"clean --help\" to display options list"
return 1 return 1
;;
esac esac
done done
@@ -121,20 +175,24 @@ clean()
local dirlist=("$@") local dirlist=("$@")
[[ ${#dirlist[@]} -eq 0 ]] && dirlist=(".") [[ ${#dirlist[@]} -eq 0 ]] && dirlist=(".")
[[ ! $recursive ]] && local findopt="-maxdepth 1" local findopt=() rmopt=()
[[ ! $force ]] && local rmopt="-i" (( ! recursive )) && findopt=(-maxdepth 1)
unset recursive force (( ! force )) && rmopt=(-i)
for dir in $dirlist; do for dir in "${dirlist[@]}"; do
find "$dir" "${findopt[@]}" -type f \( -name "*~" -o -name "#*#" -o -name "*.bak" -o -name ".~*#" \) -print0 | while IFS= read -r -d '' f; do find "$dir" "${findopt[@]}" -type f \( -name "*~" -o -name "#*#" -o -name "*.bak" -o -name ".~*#" \) -print0 |
if [[ ! $outshell ]]; then while IFS= read -r -d '' f; do
rm $rmopt $f if (( outshell )); then
if (( ${#rmopt[@]} )); then
printf 'rm %s -- "%s"\n' "${rmopt[*]}" "$f"
else else
echo "rm $rmopt $f" printf 'rm -- "%s"\n' "$f"
fi
else
rm "${rmopt[@]}" -- "$f"
fi fi
done done
done done
unset outshell dirlist dellist findopt rmopt
} }
export -f clean export -f clean
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -167,7 +225,7 @@ export -f mcd
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Rename all files in current directory to replace spaces with _ # Rename files and directories to replace spaces with another character
# Usage: rmspc [options] # Usage: rmspc [options]
# Options: # Options:
# -h, --help: display help screen # -h, --help: display help screen
@@ -177,8 +235,11 @@ export -f mcd
# -s, --shell: do nothing and display commands that would be executed # -s, --shell: do nothing and display commands that would be executed
rmspc() rmspc()
{ {
local lst="" local recurs=0 verb=0 shell=0
local substchar="_" substchar_set=0
local mvopt=()
local PARSED local PARSED
PARSED=$(getopt -o hr:c::vs --long help,recursive,subst-char::,verbose,shell -n 'rmspc' -- "$@") PARSED=$(getopt -o hr:c::vs --long help,recursive,subst-char::,verbose,shell -n 'rmspc' -- "$@")
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"rmspc --help\" to display usage." disp E "Invalid options, use \"rmspc --help\" to display usage."
@@ -202,27 +263,20 @@ rmspc()
return 0 return 0
;; ;;
-r|--recursive) -r|--recursive)
local recurs=1 recurs=1
shift shift
;; ;;
-c|--subst-char) -c|--subst-char)
# Handle optional argument for short/long options substchar_set=1
case "$2" in
"")
substchar=""
;;
*)
substchar="$2" substchar="$2"
;;
esac
shift 2 shift 2
;; ;;
-v|--verbose) -v|--verbose)
local verb=1 verb=1
shift shift
;; ;;
-s|--shell) -s|--shell)
local shell=1 shell=1
shift shift
;; ;;
--) --)
@@ -231,45 +285,58 @@ rmspc()
;; ;;
*) *)
disp E "Invalid parameter, use \"rmspc --help\" to display options list" disp E "Invalid parameter, use \"rmspc --help\" to display options list"
echo
return 1 return 1
;; ;;
esac esac
done done
[[ ! $substchar ]] && substchar="_" [[ "$substchar" == "none" ]] && substchar=""
[[ $substchar == "none" ]] && local substchar="" (( verb )) && mvopt=(-v)
[[ $verb ]] && local mvopt="-v"
shopt -s nullglob
for f in *; do for f in *; do
[[ $recurs ]] && [[ -d "$f" ]] && ( if (( recurs )) && [[ -d "$f" ]]; then
[[ $verb ]] && disp I "Entering directory $(pwd)/$f ..." (
local lastdir=$f local lastdir=$f
pushd "$f" >/dev/null (( verb )) && disp I "Entering directory $(pwd)/$f ..."
rmspc $@ pushd "$f" >/dev/null || return 1
popd >/dev/null
[[ $verb ]] && disp I "Leaving directory $(pwd)/$lastdir"
unset lastdir
)
if [[ $(echo $f | grep " ") ]]; then if (( substchar_set )); then
local newf="${f// /${substchar}}" rmspc ${recurs:+-r} -c "$substchar" ${verb:+-v} ${shell:+-s}
local command="mv $mvopt \"$f\" \"$newf\""
if [[ $shell ]]; then
echo $command
else else
$command rmspc ${recurs:+-r} ${verb:+-v} ${shell:+-s}
fi
popd >/dev/null || return 1
(( verb )) && disp I "Leaving directory $(pwd)/$lastdir"
)
fi
if [[ "$f" == *" "* ]]; then
local newf="${f// /${substchar}}"
[[ "$f" == "$newf" ]] && continue
if (( shell )); then
if (( ${#mvopt[@]} )); then
printf 'mv %s -- "%s" "%s"\n' "${mvopt[*]}" "$f" "$newf"
else
printf 'mv -- "%s" "%s"\n' "$f" "$newf"
fi
else
mv "${mvopt[@]}" -- "$f" "$newf" || {
disp E "Failed renaming \"$f\" to \"$newf\"."
continue
}
fi fi
fi fi
done done
unset lst substchar verb shell newf command mvopt shopt -u nullglob
} }
export -f rmspc export -f rmspc
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# display stats about a file structure # Display statistics about a file tree
# Usage: file_stats [options] [path] # Usage: file_stats [options] [path]
# Options: # Options:
# -H, --human Human readable sizes\n" # -H, --human Human readable sizes\n"
@@ -382,12 +449,12 @@ local human=0 details=0 only_avg=0 only_med=0 only_count=0 only_total=0
# Prepare find filters # Prepare find filters
local find_cmd=(find "$path" -type f) local find_cmd=(find "$path" -type f)
# Extension simple # Single extension filter
if [[ -n "$ext_filter" ]]; then if [[ -n "$ext_filter" ]]; then
find_cmd+=(-iname "*.$ext_filter") find_cmd+=(-iname "*.$ext_filter")
fi fi
# Extension liste # Extension list filter
if [[ -n "$ext_list" ]]; then if [[ -n "$ext_list" ]]; then
IFS=',' read -ra exts <<< "$ext_list" IFS=',' read -ra exts <<< "$ext_list"
find_cmd+=('(') find_cmd+=('(')
@@ -398,7 +465,7 @@ local human=0 details=0 only_avg=0 only_med=0 only_count=0 only_total=0
find_cmd+=(')') find_cmd+=(')')
fi fi
# Taille min/max (à évaluer en octets) # Minimum/maximum size filters (evaluated in bytes)
if [[ -n "$min_size" ]]; then if [[ -n "$min_size" ]]; then
find_cmd+=(-size +"$(numfmt --from=iec "$min_size")"c) find_cmd+=(-size +"$(numfmt --from=iec "$min_size")"c)
fi fi
@@ -406,7 +473,7 @@ local human=0 details=0 only_avg=0 only_med=0 only_count=0 only_total=0
find_cmd+=(-size -"$(( $(numfmt --from=iec "$max_size") + 1 ))"c) find_cmd+=(-size -"$(( $(numfmt --from=iec "$max_size") + 1 ))"c)
fi fi
# Exécution # Execution
"${find_cmd[@]}" -printf "%s\n" 2>/dev/null | sort -n | \ "${find_cmd[@]}" -printf "%s\n" 2>/dev/null | sort -n | \
awk -v human="$human" -v details="$details" -v only_avg="$only_avg" \ awk -v human="$human" -v details="$details" -v only_avg="$only_avg" \
-v only_med="$only_med" -v only_count="$only_count" \ -v only_med="$only_med" -v only_count="$only_count" \
@@ -448,9 +515,14 @@ local human=0 details=0 only_avg=0 only_med=0 only_count=0 only_total=0
} }
average = total / count average = total / count
# Simple sort for median (awk is not very good at this, we use existing logic)
if (count % 2 == 1) median = sizes[(count + 1) / 2] # Median calculation: exact using sorted array values
else median = (sizes[count / 2] + sizes[count / 2 + 1]) / 2 if (count % 2 == 1) {
median = sizes[(count + 1) / 2]
} else {
idx = count / 2
median = (sizes[idx] + sizes[idx + 1]) / 2
}
if (only_avg) out("Average size", average, 1) if (only_avg) out("Average size", average, 1)
else if (only_med) out("Median size", median, 1) else if (only_med) out("Median size", median, 1)
@@ -502,10 +574,10 @@ export -f file_stats
# -l : limit : number of files to return (default is 10) # -l : limit : number of files to return (default is 10)
findbig() findbig()
{ {
local details=0 limit=10 no_change=0 one_fs=0 local details=0 limit=10 one_fs=0
local PARSED local PARSED
PARSED=$(getopt -o hd:l:x --long help,details,one-fs,limit: -n 'findbig' -- "$@") PARSED=$(getopt -o hdl:x --long help,details,limit:,one-fs -n 'findbig' -- "$@")
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"findbig --help\" to display usage." disp E "Invalid options, use \"findbig --help\" to display usage."
return 1 return 1
@@ -528,14 +600,18 @@ findbig()
details=1 details=1
shift shift
;; ;;
-n|--no-change)
no_change=1
shift
;;
-l|--limit) -l|--limit)
limit="$2" limit="$2"
[[ "$limit" =~ ^[0-9]+$ ]] || {
disp E "Invalid limit: must be a positive integer."
return 1
}
shift 2 shift 2
;; ;;
-x|--one-fs)
one_fs=1
shift
;;
--) --)
shift shift
break break
@@ -550,13 +626,16 @@ findbig()
local dir="${1:-.}" local dir="${1:-.}"
# Prepare find arguments in an array for cleaner handling # Prepare find arguments in an array for cleaner handling
local find_args=("-L" "$dir" "-type" "f") local find_args=(-L "$dir")
(( one_fs )) && find_args+=("-xdev") (( one_fs )) && find_args+=(-xdev)
find_args+=(-type f)
# Logic: find files, print size and path, sort numeric reverse, take N # Logic: find files, print size and path, sort numeric reverse, take N
if (( details )); then if (( details )); then
find "${find_args[@]}" -printf "%s %p\n" 2>/dev/null | sort -rn | head -n "$limit" | while read -r size path; do find "${find_args[@]}" -printf "%s %p\n" 2>/dev/null | sort -rn | head -n "$limit" |
ls -ld "$path" while IFS= read -r line; do
local path="${line#* }"
ls -ld -- "$path"
done done
else else
find "${find_args[@]}" -printf "%s %p\n" 2>/dev/null | sort -rn | head -n "$limit" find "${find_args[@]}" -printf "%s %p\n" 2>/dev/null | sort -rn | head -n "$limit"
@@ -574,8 +653,9 @@ export -f findbig
# -d : display details (ls -l) for each file # -d : display details (ls -l) for each file
# -x : do not cross filesystem boundaries # -x : do not cross filesystem boundaries
# --delete : delete empty files and display their paths # --delete : delete empty files and display their paths
findzero() { findzero()
local delete=0 details=0 one_fs=0 no_change=0 {
local delete=0 details=0 one_fs=0
local PARSED local PARSED
# o: options, long: long equivalents # o: options, long: long equivalents
@@ -647,8 +727,9 @@ export -f findzero
# -d : display details (ls -l) for each link # -d : display details (ls -l) for each link
# -x : do not cross filesystem boundaries # -x : do not cross filesystem boundaries
# --delete : delete dead links and display their paths # --delete : delete dead links and display their paths
finddead() { finddead()
local delete=0 details=0 one_fs=0 no_change=0 {
local delete=0 details=0 one_fs=0
local PARSED local PARSED
PARSED=$(getopt -o hdx --long help,details,one-fs,delete -n 'finddead' -- "$@") PARSED=$(getopt -o hdx --long help,details,one-fs,delete -n 'finddead' -- "$@")

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -88,19 +88,23 @@ busy()
esac esac
done done
# If a pattern was provided as a positional argument (e.g., 'busy "ff 00"'),
# it is captured here.
[[ -n "$1" ]] && pattern="$1"
# Convert milliseconds to seconds for 'sleep' # Convert milliseconds to seconds for 'sleep'
local delay_s=$(awk "BEGIN { printf \"%.3f\", $delay_ms / 1000 }") local delay_s=$(awk "BEGIN{
printf \"%.3f\", $delay_ms / 1000 }")
# Monitor /dev/urandom # Monitor /dev/urandom
cat /dev/urandom | hexdump -C | grep --line-buffered "$pattern" | \ (
hexdump -C < /dev/urandom | grep -iF --line-buffered "$pattern" | \
while read -r line; do while read -r line; do
echo "$line" echo "$line"
[[ $delay_ms -gt 0 ]] && sleep "$delay_s" [[ $delay_ms -gt 0 ]] && sleep "$delay_s"
done done
) & local sub_pid=$!
IFS= read -r -n 1 -s _ </dev/tty
kill -- -"$sub_pid" 2>/dev/null || kill "$sub_pid" 2>/dev/null
wait "$sub_pid" 2>/dev/null
return 0
} }
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -39,34 +39,44 @@
# Usage: help # Usage: help
help() help()
{ {
printf "check_updates\tCheck for profile updates\n" printf "${BIWhite}Welcome to your profile! Here is a list of available commands:${DEFAULTCOL}\n\n"
printf "check_updates\tCheck for new versions of profile\n"
printf "clean\t\tErase backup files\n" printf "clean\t\tErase backup files\n"
printf "dpkgs\t\tSearch for the given package in the installed ones\n" printf "disp\t\tDisplay formatted info/warning/error/debug messages\n"
printf "findbig\t\tFind big files in the given (or current) directory\n" printf "dwl\t\tDownload a URL to a local file\n"
printf "findempty\tFind empty files and directories in the given (or current) directory\n" printf "expandlist\tExpand and quote item lists\n"
printf "file_stats\tDisplay file size statistics for a path\n"
printf "findbig\t\tFind biggest files in the given (or current) directory\n"
printf "finddead\tFind dead symbolic links in the given (or current) directory\n" printf "finddead\tFind dead symbolic links in the given (or current) directory\n"
printf "findzero\tFind empty files in the given (or current) directory\n"
printf "genpwd\t\tGenerate secure passwords\n"
printf "gpid\t\tGive the list of PIDs for the given process name\n" printf "gpid\t\tGive the list of PIDs for the given process name\n"
printf "isipv4\t\tTell if the given IPv4 is valid\n" printf "isipv4\t\tTell if the given IPv4 is valid\n"
printf "isipv6\t\tTell if the given IPv6 is valid\n" printf "isipv6\t\tTell if the given IPv6 is valid\n"
printf "ku\t\tKill process owned by users in parameter\n" printf "ku\t\tKill process owned by users in parameter\n"
printf "matrix\t\tDisplay matrix-style digital rain\n"
printf "mcd\t\tCreate a directory and go inside\n" printf "mcd\t\tCreate a directory and go inside\n"
printf "meteo\t\tDisplay curent weather forecast for the configured city\n" printf "meteo\t\tDisplay current weather forecast for the configured city\n"
printf "myextip\tDisplay current external/public IP\n"
printf "pkgs\t\tSearch for the given package in installed ones\n"
printf "ppg\t\tDisplay process matching the given parameter\n" printf "ppg\t\tDisplay process matching the given parameter\n"
printf "ppn\t\tDisplay process matching the exact process name given in parameter\n" printf "ppn\t\tDisplay process matching the exact process name given in parameter\n"
printf "ppu\t\tDisplay processes owned by the given user\n" printf "ppu\t\tDisplay processes owned by the given user\n"
printf "profile_update\tUpdate profile to the latest version\n" printf "profile_upgrade\tUpgrade profile to the latest version\n"
printf "pwdscore\tCalculate password strength score\n"
printf "rain\t\tLet the rain fall\n" printf "rain\t\tLet the rain fall\n"
printf "rmhost\t\tRemove host (IP and/or DNS name) for current known_host\n" printf "rmhost\t\tRemove host (IP and/or DNS name) from current known_hosts\n"
printf "rmspc\t\tRemove spaces from all the files in working directory\n" printf "rmspc\t\tRemove spaces from file and directory names\n"
printf "setlocale\tSet console language to the current locale\n" printf "setlocale\tSet console language to the current locale\n"
printf " * setc\tSet console language to C\n" printf " * setc\tSet console language to C\n"
printf " * setfr\tSet console language to French\n" printf " * setfr\tSet console language to French\n"
printf " * setus\tSet console language to US English\n" printf " * setus\tSet console language to US English\n"
printf "settrace\tActivate/deactivate call trace for script debugging\n" printf "settrace\tActivate/deactivate call trace for script debugging\n"
printf "showinfo\tShow the welcoming baner with basic system information\n" printf "showinfo\tShow welcome banner with basic system information\n"
printf "ssr\t\tDo a root login to the given address\n" printf "ssr\t\tDo a root login to the given address\n"
printf "taz\t\tCompress smartly the given files or directory\n" printf "taz\t\tCompress smartly the given files or directory\n"
printf "utaz\t\tUncompress all zip files in the given (or current) directory\n" printf "urlencode\tURL-encode the given text\n"
printf "utaz\t\tUncompress archives in the given (or current) directory\n"
printf "ver\t\tDisplay version of your copy of profile\n\n" printf "ver\t\tDisplay version of your copy of profile\n\n"
printf "\nPlease use <command> --help to obtain usage details.\n" printf "\nPlease use <command> --help to obtain usage details.\n"

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -39,11 +39,14 @@
# Usage: ver # Usage: ver
ver() ver()
{ {
local PARSED=$(getopt -o h --long help -n 'ver' -- "$@") local PARSED
PARSED=$(getopt -o h --long help -n 'ver' -- "$@")
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"ver --help\" to display usage." disp E "Invalid options, use \"ver --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" eval set -- "$PARSED"
while true; do while true; do
case "$1" in case "$1" in
@@ -72,11 +75,13 @@ export -f ver
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Display weather of the given city (or default one) # Display weather for the given city (or the default one)
# Usage: meteo [city1 city2 ...] # Usage: meteo [city1 city2 ...]
meteo() meteo()
{ {
local PARSED=$(getopt -o h --long help -n 'meteo' -- "$@") local PARSED
PARSED=$(getopt -o h --long help -n 'meteo' -- "$@")
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"meteo --help\" to display usage." disp E "Invalid options, use \"meteo --help\" to display usage."
return 1 return 1
@@ -85,7 +90,9 @@ meteo()
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
printf "meteo: Fetch weather data.\nUsage: meteo [city1 city2 ...]\n" printf "meteo: Fetch weather data.\n"
printf "Usage: meteo [city1 city2 ...]\n"
printf "If no city is provided, the default city from configuration will be used.\n"
return 0 return 0
;; ;;
--) --)
@@ -100,12 +107,13 @@ meteo()
done done
local cities=("$@") local cities=("$@")
local city="" encoded=""
[[ $# -eq 0 ]] && cities=("$DEFAULT_CITY") [[ $# -eq 0 ]] && cities=("$DEFAULT_CITY")
for city in "${cities[@]}"; do for city in "${cities[@]}"; do
encoded=$(urlencode "$city") encoded=$(urlencode "$city")
dwl "https://wttr.in/$encoded" || \ dwl "https://wttr.in/$encoded" || \
disp E "Failed fetching datas for $city." disp E "Failed to fetch weather data for $city."
done done
} }
export -f meteo export -f meteo
@@ -117,16 +125,20 @@ export -f meteo
# Usage: showinfo # Usage: showinfo
showinfo() showinfo()
{ {
local PARSED=$(getopt -o h --long help -n 'showinfo' -- "$@") local PARSED
PARSED=$(getopt -o h --long help -n 'showinfo' -- "$@")
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"showinfo --help\" to display usage." disp E "Invalid options, use \"showinfo --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" eval set -- "$PARSED"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
printf "showinfo: Display system information (hostname, kernel, uptime).\nUsage: showinfo\n" printf "showinfo: Display system information (hostname, kernel, uptime and fetch output when available).\n"
printf "Usage: showinfo\n"
return 0 return 0
;; ;;
--) --)
@@ -140,20 +152,20 @@ showinfo()
esac esac
done done
local hostname_str
local figopt=()
hostname_str="$(hostname)"
printf "\n" printf "\n"
if command -v figlet >/dev/null 2>&1; then if command -v figlet >/dev/null 2>&1; then
if [[ -s /usr/share/figlet/ansi_shadow.flf ]]; then [[ -s /usr/share/figlet/ansi_shadow.flf ]] && \
local figopt="-f ansi_shadow" figopt=(-f ansi_shadow)
fi figlet -k "${figopt[@]}" "$hostname_str"
if [[ -n $figopt ]]; then
figlet -k $figopt $(hostname)
else else
figlet $(hostname) printf "%s\n" "$hostname_str"
fi fi
else
hostname -f printf "\n"
fi
echo ""
if command -v neofetch >/dev/null 2>&1; then if command -v neofetch >/dev/null 2>&1; then
neofetch neofetch
elif command -v fastfetch >/dev/null 2>&1; then elif command -v fastfetch >/dev/null 2>&1; then
@@ -163,11 +175,11 @@ showinfo()
if [[ -s /etc/os-release ]]; then if [[ -s /etc/os-release ]]; then
# shellcheck disable=SC1091 # shellcheck disable=SC1091
. /etc/os-release . /etc/os-release
printf "$NAME $VERSION\n" printf "%s %s\n" "$NAME" "$VERSION"
else else
cat /proc/version cat /proc/version
fi fi
printf "Uptime: $(uptime -p)\n" printf "Uptime: %s\n" "$(uptime -p)"
) )
fi fi
} }

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -109,27 +109,73 @@ export -f setc
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Change locale to French # Build dynamic locale shortcuts from SET_LOCALE
# Usage: setfr # Expected format:
setfr() # SET_LOCALE="fr:fr_FR.UTF-8,us:en_US.UTF-8,es:es_ES.UTF-8"
# This creates functions:
# setfr, setus, setes, ...
build_locale_shortcuts()
{ {
# Set fr locale definitions local cfg="${SET_LOCALE:-}"
setlocale "fr_FR.UTF-8" local item="" alias="" loc="" fname=""
local -a locale_items=()
[[ -z "$cfg" ]] && return 0
IFS=',' read -r -a locale_items <<< "$cfg"
for item in "${locale_items[@]}"; do
# Trim surrounding spaces
item="${item#"${item%%[![:space:]]*}"}"
item="${item%"${item##*[![:space:]]}"}"
[[ -z "$item" ]] && continue
if [[ "$item" != *:* ]]; then
disp W "Ignoring invalid SET_LOCALE entry: '$item' (expected alias:locale)."
continue
fi
alias="${item%%:*}"
loc="${item#*:}"
# Trim alias/locale spaces
alias="${alias#"${alias%%[![:space:]]*}"}"
alias="${alias%"${alias##*[![:space:]]}"}"
loc="${loc#"${loc%%[![:space:]]*}"}"
loc="${loc%"${loc##*[![:space:]]}"}"
# Validate alias for safe function names
if [[ ! "$alias" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
disp W "Ignoring unsafe locale alias '$alias' in SET_LOCALE."
continue
fi
[[ -z "$loc" ]] && {
disp W "Ignoring empty locale for alias '$alias' in SET_LOCALE."
continue
} }
export -f setfr
# ------------------------------------------------------------------------------
fname="set${alias}"
# ------------------------------------------------------------------------------ # Optional collision warning
# Change locale to US (needed by Steam) if declare -F "$fname" >/dev/null 2>&1; then
# Usage: setus disp W "Overriding existing function '$fname'."
setus() fi
{
setlocale "en_US.UTF-8" # Build function dynamically
# shellcheck disable=SC2016
eval "${fname}() { setlocale \"$loc\"; }"
# shellcheck disable=SC2163
export -f "$fname"
done
unset cfg item alias loc fname locale_items
} }
export -f setus export -f build_locale_shortcuts
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf lang
build_locale_shortcuts
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -53,21 +53,22 @@
genpwd() genpwd()
{ {
local length=16 local length=16
local occurs=2 # Bug, if set to 1, seems to be ignored local occurs=2
local symb=1 maj=1 min=1 numb=1 local symb=1 maj=1 min=1 numb=1
local nbpwd=1 local nbpwd=1
local extcar local extcar=""
local PARSED local PARSED
PARSED=$(getopt -o hsnule:L:o: --long \ PARSED=$(getopt -o hsnule:L:o: --long \
help,nosymbols,nonumbers,noup,nolow,extracars:,length:,occurences: -n 'genpwd' -- "$@") help,nosymbols,nonumbers,noup,nolow,extracars:,length:,occurences:,occurrences: \
-n 'genpwd' -- "$@")
if [[ $? -ne 0 ]]; then return 1; fi if [[ $? -ne 0 ]]; then return 1; fi
eval set -- "$PARSED" eval set -- "$PARSED"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
printf "genpwd: Generate secure random password(s).\n\n" printf "genpwd: Generate random password(s).\n\n"
printf "Usage: genpwd [options] [nb_passwd]\n\n" printf "Usage: genpwd [options] [nb_passwd]\n\n"
printf "Options:\n" printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n" printf "\t-h, --help\t\tDisplay this help screen\n"
@@ -75,9 +76,9 @@ local PARSED
printf "\t-n, --nonumbers\t\tExclude numbers\n" printf "\t-n, --nonumbers\t\tExclude numbers\n"
printf "\t-u, --noup\t\tExclude uppercase letters\n" printf "\t-u, --noup\t\tExclude uppercase letters\n"
printf "\t-l, --nolow\t\tExclude lowercase letters\n" printf "\t-l, --nolow\t\tExclude lowercase letters\n"
printf "\t-e, --extracars <c>\tAdd characters to list\n" printf "\t-e, --extracars <c>\tAdd characters to the pool\n"
printf "\t-L, --length <n>\tSet password length (default: 16)\n" printf "\t-L, --length <n>\tSet password length (default: 16)\n"
printf "\t-o, --occurences <n>\tMax occurences per character (default: 2)\n" printf "\t-o, --occurences <n>\tMax occurrences per character (default: 2)\n"
return 0 return 0
;; ;;
-s|--nosymbols) -s|--nosymbols)
@@ -102,22 +103,209 @@ local PARSED
;; ;;
-L|--length) -L|--length)
length="$2" length="$2"
if ! [[ $length =~ ^[0-9]+$ ]]; then if ! [[ $length =~ ^[1-9][0-9]*$ ]]; then
disp E "The --length parameter requires a number." disp E "The --length parameter requires a positive integer."
return 1 return 1
fi fi
shift 2 shift 2
;; ;;
-o|--occurences) -o|--occurences|--occurrences)
occurs="$2" occurs="$2"
if ! [[ $occurs =~ ^[1-9]+$ ]]; then if ! [[ $occurs =~ ^[1-9][0-9]*$ ]]; then
disp E "The --occurs parameter requires a number from 1 to 9." disp E "The --occurences parameter requires a positive integer."
return 1 return 1
fi fi
shift 2 shift 2
;; ;;
--) --)
shift; break shift
break
;;
*)
disp E "Invalid options, use \"genpwd --help\" to display usage."
return 1
;;
esac
done
if [[ $# -gt 1 ]]; then
disp E "Too many positional arguments. Use only [nb_passwd]."
return 1
fi
if [[ $# -eq 1 ]]; then
nbpwd="$1"
if ! [[ $nbpwd =~ ^[1-9][0-9]*$ ]]; then
disp E "The number of passwords to generate must be a positive integer."
return 1
fi
fi
local carset=""
local unique_carset=""
local ch=""
local i=0
local n=0
local idx=0
local attempts=0
local count=0
local max_attempts=0
local set=""
local char=""
local -a required_sets=()
declare -A seen_chars=()
(( symb )) && {
required_sets+=('!.@#&%/^-_')
carset+='!.@#&%/^-_'
}
(( numb )) && {
required_sets+=('0123456789')
carset+='0123456789'
}
(( maj )) && {
required_sets+=('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
carset+='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
}
(( min )) && {
required_sets+=('abcdefghijklmnopqrstuvwxyz')
carset+='abcdefghijklmnopqrstuvwxyz'
}
if [[ -n $extcar ]]; then
required_sets+=("$extcar")
carset+="$extcar"
fi
if [[ -z $carset ]]; then
disp E "No characters are available. Re-enable at least one character class."
return 1
fi
for (( i=0; i<${#carset}; i++ )); do
ch=${carset:i:1}
if [[ -z ${seen_chars["$ch"]+x} ]]; then
seen_chars["$ch"]=1
unique_carset+="$ch"
fi
done
unset seen_chars
carset="$unique_carset"
if (( ${#required_sets[@]} > length )); then
disp E "The selected character classes require a longer password."
return 1
fi
if (( length > ${#carset} * occurs )); then
disp E "The occurrence limit is too strict for the selected length."
disp E "Please allow more characters or increase --occurences."
return 1
fi
disp I "Generating $nbpwd password(s), please wait..."
for (( n=1; n<=nbpwd; n++ )); do
local -a password_chars=()
local -A char_count=()
max_attempts=$(( ${#carset} * (occurs + 1) + 32 ))
for set in "${required_sets[@]}"; do
attempts=0
while :; do
if (( attempts >= max_attempts )); then
disp E "Unable to satisfy the occurrence limit with the current settings."
return 1
fi
idx=$(( RANDOM % ${#set} ))
char=${set:idx:1}
count=${char_count["$char"]:-0}
if (( count < occurs )); then
char_count["$char"]=$(( count + 1 ))
password_chars+=("$char")
break
fi
((attempts++))
done
done
while (( ${#password_chars[@]} < length )); do
attempts=0
while :; do
if (( attempts >= max_attempts )); then
disp E "Unable to satisfy the occurrence limit with the current settings."
return 1
fi
idx=$(( RANDOM % ${#carset} ))
char=${carset:idx:1}
count=${char_count["$char"]:-0}
if (( count < occurs )); then
char_count["$char"]=$(( count + 1 ))
password_chars+=("$char")
break
fi
((attempts++))
done
done
for (( i=${#password_chars[@]} - 1; i>0; i-- )); do
idx=$(( RANDOM % (i + 1) ))
char=${password_chars[i]}
password_chars[i]=${password_chars[idx]}
password_chars[idx]=$char
done
printf '%s' "${password_chars[@]}"
printf '\n'
done
}
export -f genpwd
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# pwdscore : score a password quality from 1 to 100
# Usage: pwdscore [options] <password>
pwdscore()
{
local verbose=0
local read_stdin=0
local password=""
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
printf "pwdscore: Score a password from 1 to 100.\n\n"
printf "Usage: pwdscore [options] <password>\n"
printf " pwdscore [options] --stdin\n"
printf " pwdscore [options] # prompt on terminal\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-v, --verbose\t\tShow details about the computed score\n"
printf "\t-i, --stdin\t\tRead the password from standard input\n\n"
printf "Note:\n"
printf " Passwords containing '!' should be quoted, or passed via --stdin.\n"
return 0
;;
-v|--verbose)
verbose=1
shift
;;
-i|--stdin)
read_stdin=1
shift
;;
--)
shift
break
;;
-*)
disp E "Invalid option '$1'. Use \"pwdscore --help\" to display usage."
return 1
;; ;;
*) *)
break break
@@ -125,78 +313,164 @@ local PARSED
esac esac
done done
if [[ -n "$1" ]]; then if (( read_stdin )); then
nbpwd="$1" [[ $# -eq 0 ]] || {
if ! [[ $nbpwd =~ ^[0-9]+$ ]]; then disp E "Do not pass a positional password when using --stdin."
disp E "The number of password to generate must be a number."
return 1 return 1
fi
fi
# Function selecting a random caracter from the list in parameter
pickcar() {
# When a character is picked we check if it's not appearing already twice
# elsewhere, we choose an other char, to compensate weak bash randomizer
while [[ -z $char ]]; do
local char="${1:RANDOM%${#1}:1} $RANDOM"
if [[ $(awk -F"$char" '{print NF-1}' <<<"$picked") -gt $occurs ]]; then
unset char
fi
done
picked+="$char"
echo "$char"
} }
IFS= read -r password || true
disp I "Generating $nbpwd passwords, please wait..." elif [[ $# -eq 0 ]]; then
for (( n=1; n<=nbpwd; n++ )); do if [[ -t 0 ]]; then
{ read -r -s -p 'Password: ' password < /dev/tty || true
local carset='' # store final caracter set to use printf '\n' > /dev/tty
local picked='' # store already used caracter else
local rlength=0 # store already assigned length of caracters IFS= read -r password || true
# ?, *, $ and \ impossible to use to my knowledge as it would be interpreted
if [[ $symb == 1 ]]; then
pickcar '!.@#&%/^-_'
carset+='!.@#&%/^-_'
((rlength++))
fi fi
if [[ $numb == 1 ]]; then else
pickcar '0123456789' [[ $# -eq 1 ]] || {
carset+='0123456789' disp E "Please provide exactly one password to score."
((rlength++))
fi
if [[ $maj == 1 ]]; then
pickcar 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
carset+='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
((rlength++))
fi
if [[ $min == 1 ]]; then
pickcar 'abcdefghijklmnopqrstuvwxyz'
carset+='abcdefghijklmnopqrstuvwxyz'
((rlength++))
fi
if [[ -n $extcar ]]; then
pickcar "$extcar"
carset+=$extcar
((rlength++))
fi
# Check if we have enough car to have something viable
if [[ ${#carset} -lt $length ]]; then
disp E 'Not enought caracters are authorised for the password length.'
disp E 'Please allow more caracter (preferably) or reduce password lentgh.'
return 1 return 1
}
password="$1"
fi fi
for i in $(seq 1 $(($length - $rlength))); do local lower=${password,,}
pickcar "$carset" local length=${#password}
local score=0
local rating="very weak"
local unique_count=0
local i=0 idx=0
local c1=0 c2=0 c3=0
local ch=""
local has_lower=0 has_upper=0 has_digit=0 has_symbol=0
local pool_size=0
local entropy_bits="0.0"
local entropy_score=0
local -A seen=()
if [[ -z $password ]]; then
printf '1\n'
return 0
fi
if (( length >= 20 )); then
score=40
elif (( length >= 16 )); then
score=34
elif (( length >= 12 )); then
score=28
elif (( length >= 8 )); then
score=18
else
score=$(( length * 2 ))
fi
if [[ $password =~ [a-z] ]]; then
has_lower=1
pool_size=$(( pool_size + 26 ))
score=$(( score + 12 ))
fi
if [[ $password =~ [A-Z] ]]; then
has_upper=1
pool_size=$(( pool_size + 26 ))
score=$(( score + 12 ))
fi
if [[ $password =~ [0-9] ]]; then
has_digit=1
pool_size=$(( pool_size + 10 ))
score=$(( score + 12 ))
fi
if [[ $password =~ [^[:alnum:]] ]]; then
has_symbol=1
pool_size=$(( pool_size + 33 ))
score=$(( score + 14 ))
fi
for (( i=0; i<length; i++ )); do
ch=${password:i:1}
if [[ -z ${seen["$ch"]+x} ]]; then
seen["$ch"]=1
unique_count=$(( unique_count + 1 ))
fi
done done
} | sort -R | awk '{printf "%s", $1}' score=$(( score + (unique_count * 10) / length ))
unset picked carset rlength
echo if (( pool_size > 1 )); then
entropy_bits=$(awk -v len="$length" -v pool="$pool_size" \
'BEGIN { printf "%.1f", len * (log(pool) / log(2)) }')
entropy_score=$(awk -v bits="$entropy_bits" 'BEGIN {
if (bits < 28) print -35;
else if (bits < 36) print -25;
else if (bits < 60) print -10;
else if (bits < 80) print 0;
else if (bits < 100) print 5;
else print 10;
}')
score=$(( score + entropy_score ))
fi
if [[ $lower =~ (password|admin|root|qwerty|azerty|welcome|letmein|secret|changeme) ]]; then
score=$(( score - 25 ))
fi
if [[ $lower =~ (1234|abcd|qwer|0000|1111|aaaa) ]]; then
score=$(( score - 15 ))
fi
if [[ $password =~ (.)\1\1 ]]; then
score=$(( score - 10 ))
fi
if (( length < 8 )); then
score=$(( score - 10 ))
fi
if (( unique_count * 2 < length )); then
score=$(( score - 10 ))
fi
for (( idx=0; idx<length-2; idx++ )); do
printf -v c1 '%d' "'${lower:idx:1}"
printf -v c2 '%d' "'${lower:idx+1:1}"
printf -v c3 '%d' "'${lower:idx+2:1}"
if (( (c2 == c1 + 1 && c3 == c2 + 1) || \
(c2 == c1 - 1 && c3 == c2 - 1) )); then
score=$(( score - 10 ))
break
fi
done done
if (( score < 1 )); then
score=1
elif (( score > 100 )); then
score=100
fi
if (( score >= 90 )); then
rating='excellent'
elif (( score >= 75 )); then
rating='strong'
elif (( score >= 60 )); then
rating='good'
elif (( score >= 40 )); then
rating='fair'
elif (( score >= 20 )); then
rating='weak'
fi
if (( verbose )); then
printf 'Score: %d/100\n' "$score"
printf 'Rating: %s\n' "$rating"
printf 'Length: %d\n' "$length"
printf 'Lowercase: %s\n' "$([[ $has_lower -eq 1 ]] && echo yes || echo no)"
printf 'Uppercase: %s\n' "$([[ $has_upper -eq 1 ]] && echo yes || echo no)"
printf 'Digits: %s\n' "$([[ $has_digit -eq 1 ]] && echo yes || echo no)"
printf 'Symbols: %s\n' "$([[ $has_symbol -eq 1 ]] && echo yes || echo no)"
printf 'Unique chars: %d\n' "$unique_count"
printf 'Entropy: ~%s bits\n' "$entropy_bits"
printf 'Entropy modifier: %+d\n' "$entropy_score"
else
printf '%d\n' "$score"
fi
} }
export -f genpwd export -f pwdscore
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -35,166 +35,213 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Let the rain fall # Generic rain-like engine and presets
# Usage: rain [OPTIONS]
# Options: _rain_build_colors()
# -s, --speed NUM Set the drop delay in seconds (default: 0.050).
# Lower values = faster rain.
# -c, --color COLOR Set the color theme (default: white).
# -h, --help Display this help message and exit.
# Available Colors:
# green : The classic Matrix digital rain
# blue : Deep ocean blue gradients
# red : Crimson/Blood rain
# yellow : Amber and gold tones
# cyan : Electric cyan/turquoise
# white : Greyscale and white (original style)
rain()
{ {
show_usage() { local base_color="$1"
printf "Usage: rain [OPTIONS]\n" RAIN_ENGINE_COLORS=()
printf "Options:\n"
printf "\t-s, --speed NUM Set the drop delay in seconds (default: 0.050).\n" case $base_color in
printf "\t Lower values = faster rain.\n" green)
printf "\t-c, --color COLOR Set the color theme (default: white).\n" for i in {22..28} {34..40} {46..48}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
printf "\t-h, --help Display this help message and exit.\n\n" blue)
printf "Available Colors:\n" for i in {17..21} {27..33} {39..45}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
printf "\t\e[32mgreen\e[0m\t: The classic Matrix digital rain\n" red)
printf "\t\e[34mblue\e[0m\t: Deep ocean blue gradients\n" for i in {52..52} {88..88} {124..124} {160..160} {196..201}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
printf "\t\e[31mred\e[0m\t: Crimson/Blood rain\n" yellow)
printf "\t\e[33myellow\e[0m\t: Amber and gold tones\n" for i in {58..58} {100..100} {142..142} {184..184} {226..229}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
printf "\t\e[36mcyan\e[0m\t: Electric cyan/turquoise\n" cyan)
printf "\twhite\t: Greyscale and white (original style)\n\n" for i in {30..31} {37..38} {44..45} {50..51}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
printf "Example: rain --color green --speed 0.03\n" *)
RAIN_ENGINE_COLORS=("\e[37m" "\e[37;1m")
for i in {244..255}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
esac
} }
local step_duration=0.050 _rain_build_chars()
local base_color="white" # default color scheme, can be overridden by --color {
local mode="$1"
local charset="$2"
RAIN_ENGINE_CHARS=()
# Analyse arguments case "$mode" in
while [[ "$#" -gt 0 ]]; do matrix)
case $1 in case "$charset" in
-s|--speed) ""|binary)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then RAIN_ENGINE_CHARS=("0" "1")
step_duration="$2"; shift
else
disp E "--speed requires a numeric value."
show_usage && return 1
fi
;; ;;
-c|--color) kana|kanji)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then # Half-width katakana set, generally rendered as single-cell glyphs.
base_color="$2"; shift RAIN_ENGINE_CHARS=("ア" "イ" "ウ" "エ" "オ" "カ" "キ" "ク" "ケ" "コ" "サ" "シ" "ス" "セ" "ソ" "タ" "チ" "ツ" "テ" "ト" "ナ" "ニ" "ヌ" "ネ" "ノ" "ハ" "ヒ" "フ" "ヘ" "ホ" "マ" "ミ" "ム" "メ" "モ" "ヤ" "ユ" "ヨ" "ラ" "リ" "ル" "レ" "ロ" "ワ" "ン")
else
disp E "--color requires a color name."
show_usage && return 1
fi
;; ;;
-h|--help) ascii)
show_usage && return 0 RAIN_ENGINE_CHARS=("0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "A" "B" "C" "D" "E" "F")
;; ;;
*) *)
disp E "Unknown option: $1" disp E "Unknown charset: ${charset} (supported: binary, kana, ascii)."
show_usage && return 1 return 1
;; ;;
esac esac
shift ;;
done *)
RAIN_ENGINE_CHARS=("|" "│" "┃" "┆" "┇" "┊" "┋" "╽" "╿")
# Define colors (256-colors gradients) ;;
local rain_colors=()
case $base_color in
green) # Matrix style green
for i in {22..28} {34..40} {46..48}; do rain_colors+=("\e[38;5;${i}m"); done ;;
blue) # Deep ocean blues
for i in {17..21} {27..33} {39..45}; do rain_colors+=("\e[38;5;${i}m"); done ;;
red) # Crimson / blood red
for i in {52..52} {88..88} {124..124} {160..160} {196..201}; do rain_colors+=("\e[38;5;${i}m"); done ;;
yellow) # Amber / gold
for i in {58..58} {100..100} {142..142} {184..184} {226..229}; do rain_colors+=("\e[38;5;${i}m"); done ;;
cyan) # Electric cyan / turquoise
for i in {30..31} {37..38} {44..45} {50..51}; do rain_colors+=("\e[38;5;${i}m"); done ;;
*) # Greyscale / white (original style)
rain_colors=("\e[37m" "\e[37;1m")
for i in {244..255}; do rain_colors+=("\e[38;5;${i}m"); done ;;
esac esac
local exit_st=0 return 0
local rain_cars=("|" "│" "┃" "┆" "┇" "┊" "┋" "╽" "╿") }
local rain_tab=${#rain_cars[@]}
local rain_color_tab=${#rain_colors[@]}
local num_rain_metadata=5
local term_height=$(tput lines)
local term_width=$(tput cols)
local X=0 Y=0 drop_length=0 rain_drop=0
local max_rain_width=0 new_rain_odd=0 falling_odd=0
sigwinch() { _rain_normalize_speed()
{
local raw_speed="$1"
# Accept integer/floating values. UI scale is centiseconds by default:
# 5 -> 0.05s, 2.5 -> 0.025s. Values < 1 are treated as direct seconds
# for backward compatibility (e.g. 0.03).
if [[ ! "$raw_speed" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
return 1
fi
if awk -v s="$raw_speed" 'BEGIN { exit !(s < 1) }'; then
printf "%s" "$raw_speed"
else
awk -v s="$raw_speed" 'BEGIN { printf "%.3f", s / 100 }'
fi
}
_rain_engine()
{
local step_duration="$1"
local base_color="$2"
local mode="$3"
local charset="$4"
command -v tput >/dev/null 2>&1 || {
disp E "The program 'tput' is required but not installed."
return 1
}
_rain_build_colors "$base_color"
_rain_build_chars "$mode" "$charset" || return 1
local rain_colors=("${RAIN_ENGINE_COLORS[@]}")
local rain_chars=("${RAIN_ENGINE_CHARS[@]}")
local rain_color_tab=${#rain_colors[@]}
local rain_tab=${#rain_chars[@]}
local matrix_head_color=$'\e[1;97m'
local exit_st=0
local num_rain_metadata=5
local term_height=0 term_width=0
local X=0 Y=0 drop_length=0 rain_drop=0
local max_rain_width=0 max_rain_height=0
local new_rain_odd=0 falling_odd=0
local term_area=0
local frame_sleep="$step_duration"
sigwinch()
{
term_width=$(tput cols) term_width=$(tput cols)
term_height=$(tput lines) term_height=$(tput lines)
#step_duration=0.025 ((term_area = term_width * term_height))
((max_rain_width = term_width * term_height / 4))
case "$mode" in
matrix)
((max_rain_width = term_area / 3))
((max_rain_height = term_height < 8 ? 1 : term_height / 6))
((new_rain_odd = term_height > 50 ? 100 : term_height * 2))
((new_rain_odd = new_rain_odd * 85 / 100))
((falling_odd = 100))
# Adapt cadence and density to terminal size for smoother rendering.
if ((term_area < 1200)); then
((max_rain_width = term_area / 4))
frame_sleep=$(awk -v s="$step_duration" 'BEGIN { printf "%.3f", s * 1.15 }')
elif ((term_area > 5000)); then
((max_rain_width = term_area / 2))
frame_sleep=$(awk -v s="$step_duration" 'BEGIN { printf "%.3f", s * 0.85 }')
else
frame_sleep="$step_duration"
fi
;;
*)
((max_rain_width = term_area / 4))
((max_rain_height = term_height < 10 ? 1 : term_height / 10)) ((max_rain_height = term_height < 10 ? 1 : term_height / 10))
# In percentage
((new_rain_odd = term_height > 50 ? 100 : term_height * 2)) ((new_rain_odd = term_height > 50 ? 100 : term_height * 2))
((new_rain_odd = new_rain_odd * 75 / 100)) ((new_rain_odd = new_rain_odd * 75 / 100))
((falling_odd = term_height > 25 ? 100 : term_height * 4)) ((falling_odd = term_height > 25 ? 100 : term_height * 4))
((falling_odd = falling_odd * 90 / 100)) ((falling_odd = falling_odd * 90 / 100))
frame_sleep="$step_duration"
;;
esac
} }
do_exit() { do_exit()
{
exit_st=1 exit_st=1
} }
do_render() { do_render()
# Clean screen first {
local idx=0 local idx=0 y=0 drop_color="" current_char="" render_color=""
for ((idx = 0; idx < num_rains * num_rain_metadata; idx += num_rain_metadata)); do for ((idx = 0; idx < num_rains * num_rain_metadata; idx += num_rain_metadata)); do
X=${rains[idx]} X=${rains[idx]}
Y=${rains[idx + 1]} Y=${rains[idx + 1]}
drop_length=${rains[idx + 4]} drop_length=${rains[idx + 4]}
for ((y = Y; y < Y + drop_length; y++)); do for ((y = Y; y < Y + drop_length; y++)); do
((y < 1 || y > term_height)) && continue ((y < 1 || y > term_height)) && continue
echo -ne "\e[${y};${X}H " printf "\e[%d;%dH " "$y" "$X"
done done
done done
for ((idx = 0; idx < num_rains * num_rain_metadata; idx += num_rain_metadata)); do for ((idx = 0; idx < num_rains * num_rain_metadata; idx += num_rain_metadata)); do
if ((100 * RANDOM / 32768 < falling_odd)); then if ((100 * RANDOM / 32768 < falling_odd)); then
# Falling
if ((++rains[idx + 1] > term_height)); then if ((++rains[idx + 1] > term_height)); then
# Out of screen, bye sweet <3 rains=("${rains[@]:0:idx}" "${rains[@]:idx+num_rain_metadata:num_rains*num_rain_metadata}")
rains=("${rains[@]:0:idx}"
"${rains[@]:idx+num_rain_metadata:num_rains*num_rain_metadata}")
((num_rains--)) ((num_rains--))
continue continue
fi fi
fi fi
X=${rains[idx]} X=${rains[idx]}
Y=${rains[idx + 1]} Y=${rains[idx + 1]}
rain_drop=${rains[idx + 2]} rain_drop=${rains[idx + 2]}
drop_color=${rains[idx + 3]} drop_color=${rains[idx + 3]}
drop_length=${rains[idx + 4]} drop_length=${rains[idx + 4]}
for ((y = Y; y < Y + drop_length; y++)); do for ((y = Y; y < Y + drop_length; y++)); do
((y < 1 || y > term_height)) && continue ((y < 1 || y > term_height)) && continue
printf "\e[${y};${X}H${drop_color}${rain_drop}" if [[ "$mode" == "matrix" ]]; then
current_char="${rain_chars[rain_tab * RANDOM / 32768]}"
if ((y == Y + drop_length - 1)); then
render_color="$matrix_head_color"
else
render_color="$drop_color"
fi
else
current_char="$rain_drop"
render_color="$drop_color"
fi
printf "\e[%d;%dH%b%s" "$y" "$X" "$render_color" "$current_char"
done done
done done
} }
trap do_exit TERM INT trap do_exit TERM INT
trap sigwinch WINCH trap sigwinch WINCH
# No echo stdin and hide the cursor
stty -echo stty -echo
printf "\e[?25l" printf "\e[?25l"
printf "\e[2J" printf "\e[2J"
local rains=() local rains=()
local num_rains=0 local num_rains=0
local ch=""
sigwinch sigwinch
while ((exit_st <= 0)); do while ((exit_st <= 0)); do
if (($exit_st <= 0)); then read -r -n 1 -t "$frame_sleep" ch
read -n 1 -t $step_duration ch
case "$ch" in case "$ch" in
q|Q) q|Q)
do_exit do_exit
@@ -202,8 +249,7 @@ rain()
esac esac
if ((num_rains < max_rain_width)) && ((100 * RANDOM / 32768 < new_rain_odd)); then if ((num_rains < max_rain_width)) && ((100 * RANDOM / 32768 < new_rain_odd)); then
# Need new |, 1-based rain_drop="${rain_chars[rain_tab * RANDOM / 32768]}"
rain_drop="${rain_cars[rain_tab * RANDOM / 32768]}"
drop_color="${rain_colors[rain_color_tab * RANDOM / 32768]}" drop_color="${rain_colors[rain_color_tab * RANDOM / 32768]}"
drop_length=$((max_rain_height * RANDOM / 32768 + 1)) drop_length=$((max_rain_height * RANDOM / 32768 + 1))
X=$((term_width * RANDOM / 32768 + 1)) X=$((term_width * RANDOM / 32768 + 1))
@@ -212,20 +258,176 @@ rain()
((num_rains++)) ((num_rains++))
fi fi
# Let rain fall!
do_render do_render
fi
done done
echo -ne "\e[${term_height};1H\e[0K"
# Show cursor and echo stdin printf "\e[%d;1H\e[0K" "$term_height"
echo -ne "\e[?25h" printf "\e[?25h"
stty echo stty echo
unset exit_st
trap - TERM INT trap - TERM INT
trap - WINCH trap - WINCH
} }
# ------------------------------------------------------------------------------
# Let the rain fall (current style)
# Usage: rain [OPTIONS]
rain()
{
_rain_show_usage()
{
printf "Usage: rain [OPTIONS]\n"
printf "Options:\n"
printf "\t-s, --speed NUM Set speed value (default: 5 => 0.050s).\n"
printf "\t Values >=1 use a /100 scale (5 => 0.05s).\n"
printf "\t Values <1 are interpreted as raw seconds.\n"
printf "\t-c, --color COLOR Set the color theme (default: white).\n"
printf "\t-h, --help Display this help message and exit.\n\n"
printf "Available Colors:\n"
printf "\t\e[32mgreen\e[0m\t: Matrix-like green shades\n"
printf "\t\e[34mblue\e[0m\t: Deep ocean blue gradients\n"
printf "\t\e[31mred\e[0m\t: Crimson/Blood rain\n"
printf "\t\e[33myellow\e[0m\t: Amber and gold tones\n"
printf "\t\e[36mcyan\e[0m\t: Electric cyan/turquoise\n"
printf "\twhite\t: Greyscale and white (original style)\n\n"
printf "Example: rain --color green --speed 3\n"
}
local step_duration=0.050
local base_color="white"
while [[ "$#" -gt 0 ]]; do
case $1 in
-s|--speed)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
step_duration=$(_rain_normalize_speed "$2") || {
disp E "--speed requires a numeric value."
_rain_show_usage
return 1
}
shift
else
disp E "--speed requires a numeric value."
_rain_show_usage
return 1
fi
;;
-c|--color)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
base_color="$2"
shift
else
disp E "--color requires a color name."
_rain_show_usage
return 1
fi
;;
-h|--help)
_rain_show_usage
return 0
;;
--)
shift
break
;;
*)
disp E "Unknown option: $1"
_rain_show_usage
return 1
;;
esac
shift
done
_rain_engine "$step_duration" "$base_color" "rain" ""
}
export -f rain export -f rain
# ------------------------------------------------------------------------------
# Matrix style digital rain
# Usage: matrix [OPTIONS]
matrix()
{
_matrix_show_usage()
{
printf "Usage: matrix [OPTIONS]\n"
printf "Options:\n"
printf "\t-s, --speed NUM Set speed value (default: 3.5 => 0.035s).\n"
printf "\t Values >=1 use a /100 scale (3.5 => 0.035s).\n"
printf "\t Values <1 are interpreted as raw seconds.\n"
printf "\t-c, --color COLOR Set color theme (default: green).\n"
printf "\t-C, --charset SET Character set: binary, kana, ascii (default: binary).\n"
printf "\t-h, --help Display this help message and exit.\n\n"
printf "Example: matrix -C kana -c green --speed 2\n"
}
local step_duration=0.035
local base_color="green"
local charset="binary"
while [[ "$#" -gt 0 ]]; do
case $1 in
-s|--speed)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
step_duration=$(_rain_normalize_speed "$2") || {
disp E "--speed requires a numeric value."
_matrix_show_usage
return 1
}
shift
else
disp E "--speed requires a numeric value."
_matrix_show_usage
return 1
fi
;;
-c|--color)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
case "${2,,}" in
binary|kana|kanji|ascii)
disp W "'${2}' looks like a charset value. Use -C/--charset for clarity."
charset="${2,,}"
;;
*)
base_color="$2"
;;
esac
shift
else
disp E "--color requires a color name."
_matrix_show_usage
return 1
fi
;;
-C|--charset)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
charset="${2,,}"
shift
else
disp E "--charset requires a value."
_matrix_show_usage
return 1
fi
;;
-h|--help)
_matrix_show_usage
return 0
;;
--)
shift
break
;;
*)
disp E "Unknown option: $1"
_matrix_show_usage
return 1
;;
esac
shift
done
_rain_engine "$step_duration" "$base_color" "matrix" "$charset"
}
export -f matrix
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -35,74 +35,118 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Remove host from know_host (name and IP) for the active user # Remove host entries (name and IP) from ~/.ssh/known_hosts for the active user
# Usage: rmhost <hostname|ip> [hostname2|ip2 [...]] # Usage: rmhost <hostname|ip> [hostname2|ip2 [...]]
rmhost() rmhost()
{ {
local PARSED local PARSED
PARSED=$(getopt -o h --long help -n 'rmhost' -- "$@") local all_users=0
local -a known_hosts_files=()
PARSED=$(getopt -o ha --long help,all-users -n 'rmhost' -- "$@")
if [[ $? -ne 0 ]]; then return 1; fi if [[ $? -ne 0 ]]; then return 1; fi
eval set -- "$PARSED" eval set -- "$PARSED"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
printf "rmhost: Remove host/IP from ~/.ssh/known_hosts.\n\n" printf "rmhost: Remove host/IP from known_hosts files.\n\n"
printf "Usage: rmhost <hostname|ip> [hostname2|ip2 ...]\n\n" printf "Usage: rmhost [--all-users] <hostname|ip> [hostname2|ip2 ...]\n\n"
printf "Options:\n" printf "Options:\n"
printf " -a, --all-users Remove entries from all local users when run as root\n"
printf " -h, --help Display this help screen\n" printf " -h, --help Display this help screen\n"
return 0 return 0
;; ;;
-a|--all-users)
all_users=1
shift
;;
--) --)
shift shift
break break
;; ;;
*) *)
disp E "Invalid options, use \"rmhost --help\" to display usage." disp E "Invalid options, use \"rmhost --help\" to display usage."
break return 1
;; ;;
esac esac
done done
# Validation: Ensure at least one argument remains [[ $# -eq 0 ]] && {
if [[ $# -eq 0 ]]; then
disp E "Missing argument. Use 'rmhost --help' for usage." disp E "Missing argument. Use 'rmhost --help' for usage."
return 1 return 1
}
command -v ssh-keygen >/dev/null 2>&1 || {
disp E "ssh-keygen is not installed."
return 127
}
if (( all_users )); then
[[ ${EUID:-$(id -u)} -eq 0 ]] || {
disp E "Option --all-users is only available when run as root."
return 1
}
while IFS=: read -r _ _ _ _ _ home _; do
[[ -n $home && -f $home/.ssh/known_hosts ]] || continue
known_hosts_files+=("$home/.ssh/known_hosts")
done < /etc/passwd
[[ -f /etc/ssh/ssh_known_hosts ]] && \
known_hosts_files+=("/etc/ssh/ssh_known_hosts")
[[ ${#known_hosts_files[@]} -gt 0 ]] || {
disp W "No known_hosts files found for local users."
return 0
}
else
known_hosts_files=("${HOME}/.ssh/known_hosts")
fi fi
for target in "$@"; do for target in "$@"; do
local hst=$target local hst="$target"
isipv4 "$hst" >/dev/null local ip=""
local v4=$? local v4=1
isipv6 "$hst" >/dev/null local v6=1
local v6=$?
isipv4 "$hst" >/dev/null 2>&1; v4=$?
isipv6 "$hst" >/dev/null 2>&1; v6=$?
if [[ $v4 -eq 0 || $v6 -eq 0 ]]; then if [[ $v4 -eq 0 || $v6 -eq 0 ]]; then
local ip=$hst ip="$hst"
unset hst hst=""
fi
unset v4 v6
if [[ ! $ip && $hst ]]; then
if ! ip=$(host "$hst" 2>/dev/null | awk '/has address/ {print $NF; exit}'); then
disp E "Impossible to extract IP from hostname." &&
return 1
fi
[[ -z $ip ]] && {
disp E "Impossible to extract IP from hostname."
return 1;
}
fi fi
if [[ $hst ]]; then if [[ -z ${ip:-} && -n ${hst:-} ]]; then
disp I "Removing host $hst from ssh known_host..." if command -v host >/dev/null 2>&1; then
ssh-keygen -R $hst >/dev/null ip=$(host "$hst" 2>/dev/null |
awk '/has address|has IPv6 address/ {print $NF; exit}')
elif command -v getent >/dev/null 2>&1; then
ip=$(getent ahosts "$hst" 2>/dev/null | awk 'NR == 1 {print $1; exit}')
else
disp W "No resolver tool found; removing hostname only for '$hst'."
fi fi
if [[ $ip ]]; then
disp I "Removing IP $ip from ssh known_host..." [[ -z ${ip:-} ]] && \
ssh-keygen -R $ip >/dev/null disp W "Could not resolve IP for '$hst'; removing hostname only."
fi fi
unset hst ip
local known_hosts_file=""
for known_hosts_file in "${known_hosts_files[@]}"; do
if [[ -n ${hst:-} ]]; then
disp I "Removing host $hst from $known_hosts_file..."
if ! ssh-keygen -R "$hst" -f "$known_hosts_file" >/dev/null 2>&1; then
disp W "No known_hosts entry found for '$hst' in '$known_hosts_file'."
fi
fi
if [[ -n ${ip:-} ]]; then
disp I "Removing IP $ip from $known_hosts_file..."
if ! ssh-keygen -R "$ip" -f "$known_hosts_file" >/dev/null 2>&1; then
disp W "No known_hosts entry found for '$ip' in '$known_hosts_file'."
fi
fi
done
done done
} }
export -f rmhost export -f rmhost
@@ -114,41 +158,33 @@ export -f rmhost
# Usage: ssr <server [ssh options]> # Usage: ssr <server [ssh options]>
ssr() ssr()
{ {
local PARSED case "${1:-}" in
PARSED=$(getopt -o h --long help -n 'ssr' -- "$@")
if [[ $? -ne 0 ]]; then return 1; fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help) -h|--help)
printf "ssr: SSH into a server as root.\n\n" printf "ssr: SSH into a server as root.\n\n"
printf "Usage: ssr <server> [ssh_options...]\n\n" printf "Usage: ssr <server> [ssh_options...]\n\n"
printf "Options:\n" printf "Notes:\n"
printf "\t-h, --help\t\tDisplay this help screen\n" printf " The first argument is the target server.\n"
printf " All remaining arguments are passed directly to ssh.\n\n"
printf "Examples:\n"
printf " ssr srv01\n"
printf " ssr srv01 -p 2222\n"
printf " ssr srv01 -i ~/.ssh/id_ed25519 -J bastion\n"
return 0 return 0
;; ;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"ssr --help\" to display usage."
return 1
;;
esac esac
done
command -v ssh >/dev/null 2>&1 || { command -v ssh >/dev/null 2>&1 || {
disp E "ssh is not installed." disp E "ssh is not installed."
return 127 return 127
} }
[[ ! $1 ]] && {
[[ $# -eq 0 || -z ${1:-} ]] && {
disp E "Please specify the server you want to log in." disp E "Please specify the server you want to log in."
return 1 return 1
} }
local srv=$1 && shift local srv=$1
shift
ssh -Y root@"$srv" "$@" ssh -Y root@"$srv" "$@"
} }

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -39,24 +39,27 @@ export UPDT_URL="$BASE_URL/raw/branch/master"
export ARCH_URL="$BASE_URL/archive/master.tar.gz" export ARCH_URL="$BASE_URL/archive/master.tar.gz"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Check for profile updates # Check whether a newer profile version is available
# Usage: check_updates [-q] # Usage: check_updates [-q]
# If -q is specified, the function will operate in quiet mode (internal use only) # If -q is specified, the function will operate in quiet mode (internal use only)
check_updates() check_updates()
{ {
local quiet=0 local quiet=0 result=5 PARSED
local PARSED=$(getopt -o hq --long help,quiet -n 'check_updates' -- "$@") local vfile="" lastver=""
PARSED=$(getopt -o hq --long help,quiet -n 'check_updates' -- "$@")
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"check_updates --help\" to display usage." disp E "Invalid options, use \"check_updates --help\" to display usage."
return 1 return 2
fi fi
eval set -- "$PARSED" eval set -- "$PARSED"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
printf "check_updates: Check for new versions.\n\n" printf "check_updates: Check whether a newer profile version is available.\n\n"
printf "Usage: check_updates\n" printf "Usage: check_updates [-q|--quiet]\n"
printf "This command only checks availability; it does not modify the installation.\n"
return 0 return 0
;; ;;
-q|--quiet) -q|--quiet)
@@ -73,51 +76,99 @@ check_updates()
esac esac
done done
(( $quiet != 1 )) && disp I "Checking for updates..." (( quiet != 1 )) && disp I "Checking for updates..."
local vfile="/tmp/version"
wget "$UPDT_URL/version" -O $vfile >/dev/null 2>&1 || { vfile=$(mktemp /tmp/profile_version.XXXXXX) || {
disp E "Can't download version file, impossible to proceed!" disp E "Failed to create a temporary file."
return 4
}
dwl "$UPDT_URL/version" "$vfile" >/dev/null 2>&1 || {
rm -f "$vfile"
disp E "Cannot download version file; unable to continue."
return 5 return 5
} }
if [[ -s $vfile ]]; then if [[ -s $vfile ]]; then
local lastver=$(cat $vfile) lastver=$(<"$vfile")
if [[ $lastver != $PROFVERSION ]]; then if [[ "$lastver" != "$PROFVERSION" ]]; then
disp I "You have version $PROFVERSION installed. Version $lastver is available." disp I "Installed: $PROFVERSION. Available: $lastver."
(( $quiet != 1 )) && disp I "You should upgrade to last version when possible." (( quiet != 1 )) && disp I "You should upgrade when possible."
result=1 result=1
else else
(( $quiet != 1 )) && disp I "Your version is up-to-date." (( quiet != 1 )) && disp I "Your version is up-to-date."
result=0 result=0
fi fi
rm -f $vfile rm -f "$vfile"
else else
disp E "Impossible to read temporary file, impossible to proceed." rm -f "$vfile"
disp E "Temporary file is unreadable; unable to continue."
fi fi
unset lastver vfile
return $result return $result
} }
export -f check_updates
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Apply update to profile # Apply the available profile upgrade
# Usage: profile_upgrade # Usage: profile_upgrade [options]
profile_upgrade() profile_upgrade()
{ {
local PARSED=$(getopt -o h --long help -n 'profile_upgrade' -- "$@") local PARSED
local check_rc=0 dry_run=0 force_git=0 switch_to_git=0
local archive_file="" tmpbase="" use_archive=0 branch=""
local tmpdir="" archive="" extracted_root=""
PARSED=$(getopt -o hf:t:nFb:g --long help,file:,tmpdir:,dry-run,force,branch:,switch-to-git -n 'profile_upgrade' -- "$@")
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
printf "Invalid options, use \"profile_upgrade --help\" to display usage." disp E "Invalid options, use \"profile_upgrade --help\" to display usage."
return 1 return 2
fi fi
eval set -- "$PARSED" eval set -- "$PARSED"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
printf "profile_upgrade: Upgrade the profile to the latest version.\n\n" printf "profile_upgrade: Apply the available profile upgrade.\n\n"
printf "Usage: profile_upgrade\n" printf "Usage: profile_upgrade [options]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-f, --file ARCHIVE\tUse a local archive file for the upgrade\n"
printf "\t-t, --tmpdir DIR\tCreate the temporary working directory under DIR\n"
printf "\t-b, --branch NAME\tUse NAME as the target Git branch\n"
printf "\t-g, --switch-to-git\tReplace current install with a fresh Git clone\n"
printf "\t-n, --dry-run\t\tDisplay what would be done without changing anything\n"
printf "\t-F, --force\t\tDiscard local changes before upgrading\n\n"
printf "If the profile is installed from Git, the upgrade uses 'git pull'.\n"
printf "Otherwise, it downloads or applies an archive and refreshes the files.\n"
return 0 return 0
;; ;;
-f|--file)
archive_file="$2"
use_archive=1
shift 2
;;
-t|--tmpdir)
tmpbase="$2"
shift 2
;;
-b|--branch)
branch="$2"
shift 2
;;
-g|--switch-to-git)
switch_to_git=1
shift
;;
-n|--dry-run)
dry_run=1
shift
;;
-F|--force)
force_git=1
shift
;;
--) --)
shift shift
break break
@@ -129,59 +180,232 @@ profile_upgrade()
esac esac
done done
if check_updates -q; then if (( ! use_archive && ! switch_to_git )); then
disp "No update available." check_updates -q
check_rc=$?
if (( check_rc == 0 )); then
disp I "No update available."
return 0 return 0
elif (( check_rc > 1 )); then
disp E "Unable to check whether an update is available."
return "$check_rc"
fi
fi fi
if [[ -s $MYPATH/profile.sh ]]; then if [[ ! -s $MYPATH/profile.sh ]]; then
disp E "Installation path detection failed, cannot upgrade automatically." disp E "Install path detection failed; cannot upgrade automatically."
return 1 return 1
fi fi
if [[ -d $MYPATH/.git ]]; then if [[ -d $MYPATH/.git ]] && (( use_archive )) && (( ! force_git )); then
disp I "Git installation detected, applying git pull." disp E "Refusing archive upgrade on a Git install without --force."
local curdir=$(pwd) return 1
cd $MYPATH fi
git pull || {
disp E "Git pull failed, upgrade not applyed." if (( switch_to_git )); then
cd "$curdir" command -v git >/dev/null 2>&1 || {
return 2 disp E "Git is required to switch this install to a Git clone."
return 3
} }
disp I "Successfully upgraded using git." if (( dry_run )); then
cd "$curdir" disp I "[dry-run] rm -rf \"$MYPATH\"/.git"
disp I "[dry-run] git clone "$BASE_URL" \"$MYPATH\""
[[ -n "$branch" ]] && disp I "[dry-run] git -C \"$MYPATH\" checkout "$branch""
return 0
fi
if [[ -d $MYPATH/.git ]]; then
disp W "Git repository already present; no switch is needed."
else else
disp I "No Git detected. Downloading and applying upgrade from archive..." local backup_dir="${MYPATH}.pre-git.$$.bak"
local tmpdir="/tmp/profile_upg.$$" mv "$MYPATH" "$backup_dir" || {
mkdir -p "$tmpdir" || { disp E "Failed to move current install out of the way."
disp E "Failed to create temporary directory." return 3
}
git clone "$BASE_URL" "$MYPATH" || {
disp E "Git clone failed; previous install kept in $backup_dir."
mv "$backup_dir" "$MYPATH" 2>/dev/null || true
return 3
}
[[ -n "$branch" ]] && (
cd "$MYPATH" && git checkout "$branch"
) || true
disp I "Switched installation to Git source."
disp I "Previous install kept in $backup_dir."
return 0
fi
fi
if [[ -d $MYPATH/.git ]] && (( ! use_archive )); then
disp I "Git installation detected, applying git pull."
command -v git >/dev/null 2>&1 || {
disp E "Git is required for this upgrade but is not available."
return 3
}
pushd "$MYPATH" >/dev/null || {
disp E "Failed to change directory to $MYPATH."
return 3
}
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || {
disp E "Install directory is not a valid Git working tree."
popd >/dev/null || return 1
return 3
}
if ! git diff --quiet || ! git diff --cached --quiet || [[ -n $(git ls-files --others --exclude-standard) ]]; then
if (( force_git )); then
disp W "Force mode: local Git changes and untracked files will be lost."
if (( dry_run )); then
disp I "[dry-run] git fetch --all --prune"
disp I "[dry-run] git reset --hard HEAD"
disp I "[dry-run] git clean -fd"
else
git fetch --all --prune || {
disp E "Git fetch failed, upgrade not applied."
popd >/dev/null || return 1
return 4 return 4
} }
git reset --hard HEAD || {
disp E "Git reset failed, upgrade not applied."
popd >/dev/null || return 1
return 4
}
git clean -fd || {
disp E "Git clean failed, upgrade not applied."
popd >/dev/null || return 1
return 4
}
fi
else
disp W "The Git working tree contains local changes."
disp W "Consider committing or stashing them before upgrading, or use --force."
disp W "Upgrade may fail if the changes conflict with the upgrade."
fi
fi
if [[ -n "$branch" ]]; then
if (( dry_run )); then
disp I "[dry-run] git fetch origin $branch"
disp I "[dry-run] git checkout $branch"
else
git fetch origin "$branch" || {
disp E "Git fetch failed for branch $branch."
popd >/dev/null || return 1
return 2
}
git checkout "$branch" || {
disp E "Git checkout failed for branch $branch."
popd >/dev/null || return 1
return 2
}
fi
fi
local archive="$tmpdir/profile.tar.gz" if (( dry_run )); then
wget -q "$ARCH_URL" -O "$archive" || { if [[ -n "$branch" ]]; then
disp E "Failed to download archive." disp I "[dry-run] git pull origin $branch"
rm -rf "$tmpdir" else
disp I "[dry-run] git pull"
fi
else
if [[ -n "$branch" ]]; then
git pull origin "$branch" || {
disp E "Git pull failed, upgrade not applied."
popd >/dev/null || return 1
return 2
}
else
git pull || {
disp E "Git pull failed, upgrade not applied."
popd >/dev/null || return 1
return 2
}
fi
disp I "Successfully upgraded using git."
fi
popd >/dev/null || return 1
else
if (( use_archive )); then
[[ -r "$archive_file" ]] || {
disp E "Local archive '$archive_file' is missing or unreadable."
return 4
}
disp I "Using local archive $archive_file."
else
disp W "No Git repo found. Git is the recommended source."
disp I "Applying upgrade from archive..."
fi
if [[ -n "$tmpbase" ]]; then
if (( dry_run )); then
disp I "[dry-run] mkdir -p \"$tmpbase\""
disp I "[dry-run] mktemp -d \"$tmpbase/profile_upg.XXXXXX\""
tmpdir="$tmpbase/profile_upg.DRYRUN"
else
mkdir -p "$tmpbase" || {
disp E "Failed to create temporary directory base $tmpbase."
return 5 return 5
} }
tmpdir=$(mktemp -d "$tmpbase/profile_upg.XXXXXX") || {
disp E "Failed to create temp working directory under $tmpbase."
return 5
}
fi
else
if (( dry_run )); then
disp I "[dry-run] mktemp -d /tmp/profile_upg.XXXXXX"
tmpdir="/tmp/profile_upg.DRYRUN"
else
tmpdir=$(mktemp -d /tmp/profile_upg.XXXXXX) || {
disp E "Failed to create temporary directory."
return 5
}
fi
fi
tar -xzf "$archive" -C "$tmpdir" || { if (( use_archive )); then
disp E "Archive extraction failed." archive="$archive_file"
else
archive="$tmpdir/profile.tar.gz"
if (( dry_run )); then
disp I "[dry-run] dwl \"$ARCH_URL\" \"$archive\""
else
dwl "$ARCH_URL" "$archive" || {
disp E "Failed to download archive."
rm -rf "$tmpdir" rm -rf "$tmpdir"
return 6 return 6
} }
fi
fi
disp I "Installing new version..." if (( dry_run )); then
cp -r "$tmpdir"/profile/* "$MYPATH"/ || { disp I "[dry-run] tar -xzf \"$archive\" -C \"$tmpdir\""
disp E "Failed to copy new files to $MYPATH." disp I "[dry-run] cp -a <extracted_profile>/. \"$MYPATH\"/"
else
tar -xzf "$archive" -C "$tmpdir" || {
disp E "Archive extraction failed."
rm -rf "$tmpdir" rm -rf "$tmpdir"
return 7 return 7
} }
disp I "Upgrade complete. You should now logout and login again." extracted_root=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d ! -name '.*' | head -n 1)
if [[ -z "$extracted_root" ]]; then
disp E "Could not find extracted profile files."
rm -rf "$tmpdir"
return 8
fi
disp I "Installing new version..."
cp -a "$extracted_root"/. "$MYPATH"/ || {
disp E "Failed to copy new files into $MYPATH."
rm -rf "$tmpdir"
return 9
}
disp I "Upgrade complete. Please log out and log in again."
rm -rf "$tmpdir" rm -rf "$tmpdir"
fi fi
fi
} }
export -f profile_upgrade
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Begin profile # Begin profile
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -35,12 +35,19 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
if [[ ! $SHELL =~ bash|zsh ]]; then if [[ ! $SHELL =~ bash ]]; then
echo "That environment script is designed to be used with bash or zsh being the shell." echo "That environment script is designed to be used with bash being the shell."
echo "Please consider using bash or zsh instead, or patch me ;)!" echo "Please consider using bash to enjoy our features!"
return 1 return 1
fi 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 1 2>/dev/null || exit 1
fi
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# path* : private functions for PATH variable management # path* : private functions for PATH variable management
pathremove() pathremove()
@@ -79,33 +86,49 @@ parse_conf()
{ {
local config_file="$1" local config_file="$1"
local current_section="" local current_section=""
local line key value
[[ ! -f "$config_file" ]] && return 1 [[ ! -f "$config_file" ]] && return 1
while IFS='=' read -r key value || [[ -n "$key" ]]; do while IFS='=' read -r key value || [[ -n "$key" ]]; do
# Clean key and value (strip CR and whitespace) # Internal trimming (removes leading/trailing whitespace & CR)
key=$(printf '%s' "$key" | tr -d '\r' | xargs 2>/dev/null) key="${key%"${key##*[![:space:]]}"}"
value=$(printf '%s' "$value" | tr -d '\r' | xargs 2>/dev/null) key="${key#"${key%%[![:space:]]*}"}"
key="${key%$'\r'}" # Strip potential Windows line endings
# Skip comments and empty lines # Skip comments and empty lines
[[ -z "$key" || "$key" =~ ^[#\;] ]] && continue [[ -z "$key" || "$key" =~ ^[#\;] ]] && continue
# Section Detection: [section_name] # Section Detection: [section_name]
if [[ "$key" =~ ^\[(.*)\]$ ]]; then if [[ "$key" =~ ^\[([a-zA-Z0-9_]+)\]$ ]]; then
current_section="${BASH_REMATCH[1]}" current_section="${BASH_REMATCH[1]}"
# Dynamically declare the associative array for this section
declare -g -A "CONF_$current_section" declare -g -A "CONF_$current_section"
continue continue
fi fi
# If we have a key/value pair and are inside a section # Secure Assignment (if inside a section)
if [[ -n "$current_section" && -n "$value" ]]; then if [[ -n "$current_section" ]]; then
# Strip quotes from value # 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#\"}"
value="${value%\'}"; value="${value#\'}" value="${value%\'}"; value="${value#\'}"
# Store in the dynamic array: CONF_sectionname[key]=value # Use a nameref for safe, eval-free assignment
eval "CONF_${current_section}['$key']='$value'" local -n current_array="CONF_$current_section"
current_array["$key"]="$value"
fi fi
done < "$config_file" done < "$config_file"
} }
@@ -118,20 +141,20 @@ load_alias()
{ {
local section_name="CONF_$1" local section_name="CONF_$1"
# Check if the associative array for this section exists # Check if the associative array exists using declare -p
if [[ "$(declare -p "$section_name" 2>/dev/null)" != "declare -A"* ]]; then [[ "$(declare -p "$section_name" 2>/dev/null)" != "declare -A"* ]] && return 1
return 1
fi
# Reference the array keys # Create a nameref to the section array
eval "local keys=\"\${!$section_name[@]}\"" local -n current_aliases="$section_name"
for key in $keys; do # Iterate safely over the keys of the associative array
# Fetch the value for this specific key for key in "${!current_aliases[@]}"; do
eval "local cmd=\"\${$section_name[$key]}\"" local cmd="${current_aliases[$key]}"
# Portability check: only alias if the command exists # Extract the base command (first word) safely without awk
local base_cmd=$(echo "$cmd" | awk '{print $1}') local base_cmd="${cmd%% *}"
# Only alias if the base command is executable
if command -v "$base_cmd" >/dev/null 2>&1; then if command -v "$base_cmd" >/dev/null 2>&1; then
alias "$key"="$cmd" alias "$key"="$cmd"
fi fi
@@ -146,16 +169,14 @@ load_conf()
{ {
local section_name="CONF_$1" local section_name="CONF_$1"
if [[ "$(declare -p "$section_name" 2>/dev/null)" != "declare -A"* ]]; then [[ "$(declare -p "$section_name" 2>/dev/null)" != "declare -A"* ]] && return 1
return 1
fi
eval "local keys=\"\${!$section_name[@]}\"" local -n current_vars="$section_name"
for key in $keys; do for key in "${!current_vars[@]}"; do
eval "local val=\"\${$section_name[$key]}\"" # Export the key/value pair as a standard shell variable
# Export as a standard shell variable # We use 'export' directly; Bash handles the assignment safely here
export "$key"="$val" export "$key"="${current_vars[$key]}"
done done
} }
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -168,8 +189,10 @@ load_conf()
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Store script's path (realpath -s resolve symlinks if profile.sh is a symlink) # 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 if [[ -z "$PROFILE_PATH" ]]; then
export MYPATH=$(dirname "$(realpath -s "$0")") export MYPATH=$(dirname "$(realpath -s "${BASH_SOURCE[0]}")")
else else
export MYPATH="$PROFILE_PATH" export MYPATH="$PROFILE_PATH"
fi fi