10 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
7 changed files with 930 additions and 300 deletions

View File

@@ -70,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

@@ -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"
printf "Options:\n" PARSED=$(getopt -o hs:n --long help,separator:,newline -n 'expandlist' -- "$@")
printf "\t-h, --help\t\tDisplay this help screen\n" if [[ $? -ne 0 ]]; then
return 0 disp E "Invalid options, use \"expandlist --help\" to display usage."
fi 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 "\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
;;
-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
local result=""
for item in "$@"; do for item in "$@"; do
for content in "$item"; do local expanded=()
result+="\"$content\" "
# True glob expansion when wildcards are present.
if [[ "$item" == *'*'* || "$item" == *'?'* || "$item" == *'['* ]]; then
expanded=( $item )
else
expanded=( "$item" )
fi
if [[ ${#expanded[@]} -eq 0 ]]; then
continue
fi
for content in "${expanded[@]}"; do
if (( matched )); then
result+="$separator"
fi
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,21 +175,24 @@ clean()
local dirlist=("$@") local dirlist=("$@")
[[ ${#dirlist[@]} -eq 0 ]] && dirlist=(".") [[ ${#dirlist[@]} -eq 0 ]] && dirlist=(".")
local findopt=() rmopt local findopt=() rmopt=()
[[ ! $recursive ]] && findopt=(-maxdepth 1) (( ! recursive )) && findopt=(-maxdepth 1)
[[ ! $force ]] && rmopt="-i" (( ! force )) && rmopt=(-i)
unset recursive force
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
else if (( ${#rmopt[@]} )); then
echo "rm $rmopt $f" printf 'rm %s -- "%s"\n' "${rmopt[*]}" "$f"
fi else
done printf 'rm -- "%s"\n' "$f"
fi
else
rm "${rmopt[@]}" -- "$f"
fi
done
done done
unset outshell dirlist dellist findopt rmopt
} }
export -f clean export -f clean
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -168,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
@@ -178,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."
@@ -203,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="$2"
"")
substchar=""
;;
*)
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
;; ;;
--) --)
@@ -232,49 +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 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 ${recurs:+-r} ${substchar:+-c "$substchar"} ${verb:+-v} ${shell:+-s} pushd "$f" >/dev/null || return 1
popd >/dev/null
[[ $verb ]] && disp I "Leaving directory $(pwd)/$lastdir" if (( substchar_set )); then
unset lastdir rmspc ${recurs:+-r} -c "$substchar" ${verb:+-v} ${shell:+-s}
) else
rmspc ${recurs:+-r} ${verb:+-v} ${shell:+-s}
fi
popd >/dev/null || return 1
(( verb )) && disp I "Leaving directory $(pwd)/$lastdir"
)
fi
if [[ "$f" == *" "* ]]; then if [[ "$f" == *" "* ]]; then
local newf="${f// /${substchar}}" local newf="${f// /${substchar}}"
[[ "$f" == "$newf" ]] && continue # protection but should never happen [[ "$f" == "$newf" ]] && continue
if [[ -n $shell ]]; then if (( shell )); then
echo "mv ${mvopt:+$mvopt }\"$f\" \"$newf\"" if (( ${#mvopt[@]} )); then
printf 'mv %s -- "%s" "%s"\n' "${mvopt[*]}" "$f" "$newf"
else
printf 'mv -- "%s" "%s"\n' "$f" "$newf"
fi
else else
mv ${mvopt:+$mvopt} "$f" "$newf" || { mv "${mvopt[@]}" -- "$f" "$newf" || {
disp E "Failed renaming \"$f\" to \"$newf\"." disp E "Failed renaming \"$f\" to \"$newf\"."
continue 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"
@@ -290,7 +352,7 @@ export -f rmspc
# --max [size] Maximum size (e.g., 100M) # --max [size] Maximum size (e.g., 100M)
file_stats() file_stats()
{ {
local human=0 details=0 only_avg=0 only_med=0 only_count=0 only_total=0 local human=0 details=0 only_avg=0 only_med=0 only_count=0 only_total=0
local path="." show_all=1 ext_filter="" ext_list="" min_size="" max_size="" local path="." show_all=1 ext_filter="" ext_list="" min_size="" max_size=""
local PARSED local PARSED
@@ -387,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+=('(')
@@ -403,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
@@ -411,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" \
@@ -512,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
@@ -538,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
@@ -560,14 +626,17 @@ 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
done local path="${line#* }"
ls -ld -- "$path"
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"
fi fi
@@ -586,7 +655,7 @@ export -f findbig
# --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
@@ -660,7 +729,7 @@ export -f findzero
# --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

@@ -39,6 +39,8 @@
# Usage: help # Usage: help
help() help()
{ {
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 "disp\t\tDisplay formatted info/warning/error/debug messages\n" printf "disp\t\tDisplay formatted info/warning/error/debug messages\n"
printf "dwl\t\tDownload a URL to a local file\n" printf "dwl\t\tDownload a URL to a local file\n"
@@ -60,6 +62,8 @@ help()
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_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) from current known_hosts\n" printf "rmhost\t\tRemove host (IP and/or DNS name) from current known_hosts\n"
printf "rmspc\t\tRemove spaces from file and directory names\n" printf "rmspc\t\tRemove spaces from file and directory names\n"

View File

@@ -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
figlet $(hostname)
fi
else else
hostname -f printf "%s\n" "$hostname_str"
fi fi
echo ""
printf "\n"
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

@@ -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 hsnu l e: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
}
IFS= read -r password || true
elif [[ $# -eq 0 ]]; then
if [[ -t 0 ]]; then
read -r -s -p 'Password: ' password < /dev/tty || true
printf '\n' > /dev/tty
else
IFS= read -r password || true
fi fi
else
[[ $# -eq 1 ]] || {
disp E "Please provide exactly one password to score."
return 1
}
password="$1"
fi fi
# Function selecting a random caracter from the list in parameter local lower=${password,,}
pickcar() { local length=${#password}
# When a character is picked we check if it's not appearing already twice local score=0
# elsewhere, we choose an other char, to compensate weak bash randomizer local rating="very weak"
while [[ -z $char ]]; do local unique_count=0
local char="${1:RANDOM%${#1}:1} $RANDOM" local i=0 idx=0
if [[ $(awk -F"$char" '{print NF-1}' <<<"$picked") -gt $occurs ]]; then local c1=0 c2=0 c3=0
unset char local ch=""
fi local has_lower=0 has_upper=0 has_digit=0 has_symbol=0
done local pool_size=0
picked+="$char" local entropy_bits="0.0"
echo "$char" local entropy_score=0
} local -A seen=()
disp I "Generating $nbpwd passwords, please wait..." if [[ -z $password ]]; then
for (( n=1; n<=nbpwd; n++ )); do printf '1\n'
{ return 0
local carset='' # store final caracter set to use fi
local picked='' # store already used caracter
local rlength=0 # store already assigned length of caracters
# ?, *, $ and \ impossible to use to my knowledge as it would be interpreted if (( length >= 20 )); then
if [[ $symb == 1 ]]; then score=40
pickcar '!.@#&%/^-_' elif (( length >= 16 )); then
carset+='!.@#&%/^-_' score=34
((rlength++)) elif (( length >= 12 )); then
fi score=28
if [[ $numb == 1 ]]; then elif (( length >= 8 )); then
pickcar '0123456789' score=18
carset+='0123456789' else
((rlength++)) score=$(( length * 2 ))
fi 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 [[ $password =~ [a-z] ]]; then
if [[ ${#carset} -lt $length ]]; then has_lower=1
disp E 'Not enought caracters are authorised for the password length.' pool_size=$(( pool_size + 26 ))
disp E 'Please allow more caracter (preferably) or reduce password lentgh.' score=$(( score + 12 ))
return 1 fi
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 in $(seq 1 $(($length - $rlength))); do for (( i=0; i<length; i++ )); do
pickcar "$carset" ch=${password:i:1}
done if [[ -z ${seen["$ch"]+x} ]]; then
} | sort -R | awk '{printf "%s", $1}' seen["$ch"]=1
unset picked carset rlength unique_count=$(( unique_count + 1 ))
echo fi
done done
score=$(( score + (unique_count * 10) / length ))
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
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

@@ -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 fi
unset v4 v6
if [[ ! $ip && $hst ]]; then if [[ -z ${ip:-} && -n ${hst:-} ]]; then
if ! ip=$(host "$hst" 2>/dev/null | awk '/has address/ {print $NF; exit}'); then if command -v host >/dev/null 2>&1; then
disp E "Impossible to extract IP from hostname." && ip=$(host "$hst" 2>/dev/null |
return 1 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
[[ -z $ip ]] && {
disp E "Impossible to extract IP from hostname." [[ -z ${ip:-} ]] && \
return 1; disp W "Could not resolve IP for '$hst'; removing hostname only."
}
fi fi
if [[ $hst ]]; then local known_hosts_file=""
disp I "Removing host $hst from ssh known_host..." for known_hosts_file in "${known_hosts_files[@]}"; do
ssh-keygen -R $hst >/dev/null if [[ -n ${hst:-} ]]; then
fi disp I "Removing host $hst from $known_hosts_file..."
if [[ $ip ]]; then if ! ssh-keygen -R "$hst" -f "$known_hosts_file" >/dev/null 2>&1; then
disp I "Removing IP $ip from ssh known_host..." disp W "No known_hosts entry found for '$hst' in '$known_hosts_file'."
ssh-keygen -R $ip >/dev/null fi
fi fi
unset hst ip 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' -- "$@") -h|--help)
if [[ $? -ne 0 ]]; then return 1; fi printf "ssr: SSH into a server as root.\n\n"
eval set -- "$PARSED" printf "Usage: ssr <server> [ssh_options...]\n\n"
printf "Notes:\n"
while true; do printf " The first argument is the target server.\n"
case "$1" in printf " All remaining arguments are passed directly to ssh.\n\n"
-h|--help) printf "Examples:\n"
printf "ssr: SSH into a server as root.\n\n" printf " ssr srv01\n"
printf "Usage: ssr <server> [ssh_options...]\n\n" printf " ssr srv01 -p 2222\n"
printf "Options:\n" printf " ssr srv01 -i ~/.ssh/id_ed25519 -J bastion\n"
printf "\t-h, --help\t\tDisplay this help screen\n" return 0
return 0 ;;
;; esac
--)
shift
break
;;
*)
disp E "Invalid options, use \"ssr --help\" to display usage."
return 1
;;
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

@@ -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,28 +76,35 @@ 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 export -f check_updates
@@ -102,23 +112,63 @@ 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
@@ -130,59 +180,229 @@ 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
return 0 check_rc=$?
if (( check_rc == 0 )); then
disp I "No update available."
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 E "Refusing archive upgrade on a Git install without --force."
return 1
fi
if (( switch_to_git )); then
command -v git >/dev/null 2>&1 || {
disp E "Git is required to switch this install to a Git clone."
return 3
}
if (( dry_run )); then
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
local backup_dir="${MYPATH}.pre-git.$$.bak"
mv "$MYPATH" "$backup_dir" || {
disp E "Failed to move current install out of the way."
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." disp I "Git installation detected, applying git pull."
pushd "$MYPATH" || { 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." disp E "Failed to change directory to $MYPATH."
return 3 return 3
} }
git pull || { git rev-parse --is-inside-work-tree >/dev/null 2>&1 || {
disp E "Git pull failed, upgrade not applyed." disp E "Install directory is not a valid Git working tree."
popd popd >/dev/null || return 1
return 2 return 3
} }
disp I "Successfully upgraded using git." if ! git diff --quiet || ! git diff --cached --quiet || [[ -n $(git ls-files --others --exclude-standard) ]]; then
popd 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
}
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
if (( dry_run )); then
if [[ -n "$branch" ]]; then
disp I "[dry-run] git pull origin $branch"
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 else
disp I "No Git detected. Downloading and applying upgrade from archive..." if (( use_archive )); then
local tmpdir="/tmp/profile_upg.$$" [[ -r "$archive_file" ]] || {
mkdir -p "$tmpdir" || { disp E "Local archive '$archive_file' is missing or unreadable."
disp E "Failed to create temporary directory." return 4
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
local archive="$tmpdir/profile.tar.gz" if [[ -n "$tmpbase" ]]; then
wget -q "$ARCH_URL" -O "$archive" || { if (( dry_run )); then
disp E "Failed to download archive." 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
}
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
if (( use_archive )); then
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"
return 6
}
fi
fi
if (( dry_run )); then
disp I "[dry-run] tar -xzf \"$archive\" -C \"$tmpdir\""
disp I "[dry-run] cp -a <extracted_profile>/. \"$MYPATH\"/"
else
tar -xzf "$archive" -C "$tmpdir" || {
disp E "Archive extraction failed."
rm -rf "$tmpdir"
return 7
}
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"
return 5 fi
}
tar -xzf "$archive" -C "$tmpdir" || {
disp E "Archive extraction failed."
rm -rf "$tmpdir"
return 6
}
disp I "Installing new version..."
cp -r "$tmpdir"/profile/* "$MYPATH"/ || {
disp E "Failed to copy new files to $MYPATH."
rm -rf "$tmpdir"
return 7
}
disp I "Upgrade complete. You should now logout and login again."
rm -rf "$tmpdir"
fi fi
} }
export -f profile_upgrade export -f profile_upgrade