2022-05-09 13:20:43 +02:00
##
# Utilities
##
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
## Performs a simple and generic parsing of CLI arguments. Creates a global associative array »args« and a global normal array »argv«.
# Named options may be passed as »--name[=value]«, where »value« defaults to »1«, and are assigned to »args«.
# Everything else, or everything following the »--« argument, ends up as positional arguments in »argv«.
# Checking the validity of the parsed arguments is up to the caller.
function generic-arg-parse { # ...
declare -g -A args = ( ) ; declare -g -a argv = ( ) # this ends up in the caller's scope
while ( ( " $# " ) ) ; do
2023-01-29 15:55:56 +01:00
if [ [ $1 = = -- ] ] ; then shift ; argv += ( " $@ " ) ; \r eturn 0 ; fi
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
if [ [ $1 = = --* ] ] ; then
if [ [ $1 = = *= * ] ] ; then
local key = ${ 1 /=*/ } ; args[ ${ key /--/ } ] = ${ 1 / $key =/ }
else args[ ${ 1 /--/ } ] = 1 ; fi
else argv += ( " $1 " ) ; fi
shift ; done
}
2022-08-31 08:38:33 +02:00
## Shows the help text for a program and exits, if »--help« was passed as argument and parsed, or does nothing otherwise.
# Expects to be called between parsing and verifying the arguments.
# Uses »allowedArgs« for the list of the named arguments (the values are the descriptions).
# »name« should be the program name/path (usually »$0«), »args« the form/names of any positional arguments expected (e.g. »SOURCE... DEST«) and is included in the "Usage" description,
# »description« the introductory text shown before the "Usage", and »suffix« any text printed after the argument list.
2023-06-16 02:14:51 +02:00
function generic-arg-help { # 1: name, 2?: args, 3?: description, 4?: suffix, 5?: usageLine
2023-01-29 15:55:56 +01:00
if [ [ ! ${ args [help] :- } ] ] ; then : ${ allowedArgs [help] : =1 } ; \r eturn 0 ; fi
2022-08-31 08:38:33 +02:00
[ [ ! ${ 3 :- } ] ] || echo " $3 "
2023-06-16 02:14:51 +02:00
printf " ${ 5 :- 'Usage:\n %s [FLAG[=value]]... [--] %s\n\nWhere »FLAG« may be any of:\n' } " " $1 " " ${ 2 :- } "
2023-01-29 15:55:56 +01:00
local name ; while IFS = read -u3 -r name ; do
2023-06-16 02:14:51 +02:00
printf ' %s\n %s\n' " $name " " ${ allowedArgs [ $name ]// $'\n' / $'\n ' } "
2023-01-29 15:55:56 +01:00
done 3< <( printf '%s\n' " ${ !allowedArgs[@] } " | LC_ALL = C sort )
2022-08-31 08:38:33 +02:00
printf ' %s\n %s\n' "--help" "Do nothing but print this message and exit with success."
[ [ ! ${ 4 :- } ] ] || echo " $4 "
2023-01-29 15:55:56 +01:00
\e xit 0
2022-08-31 08:38:33 +02:00
}
## Performs a basic verification of the named arguments passed by the user and parsed by »generic-arg-parse« against the names in »allowedArgs«.
# Entries in »allowedArgs« should have the form »[--name]="description"« for boolean flags, and »[--name=VAL]="description"« for string arguments.
# »description« is used by »generic-arg-help«. Boolean flags may only have the values »1« (as set by »generic-ags-parse« for flags without value) or be empty.
# »VAL« is purely nominal. Any argument passed that is not in »allowedArgs« raises an error.
function generic-arg-verify { # 1: exitCode
local exitCode = ${ 1 :- 1 }
local names = ' ' " ${ !allowedArgs[@] } "
for name in " ${ !args[@] } " ; do
if [ [ ${ allowedArgs [-- $name ] :- } ] ] ; then
if [ [ ${ args [ $name ] } = = '' || ${ args [ $name ] } = = 1 ] ] ; then continue ; fi
2023-01-29 15:55:56 +01:00
echo " Argument »-- $name « should be a boolean, but its value is: ${ args [ $name ] } " 1>& 2 ; \r eturn $exitCode
2022-08-31 08:38:33 +02:00
fi
if [ [ $names = = *' --' " $name " '=' * || $names = = *' --' " $name " '[=' * ] ] ; then continue ; fi
2023-01-29 15:55:56 +01:00
echo " Unexpected argument »-- $name «. ${ allowedArgs [help] : + Call with »--help« for a list of valid arguments. } " 1>& 2 ; \r eturn $exitCode
2022-08-31 08:38:33 +02:00
done
}
2022-05-09 13:20:43 +02:00
## Prepends a command to a trap. Especially useful fo define »finally« commands via »prepend_trap '<command>' EXIT«.
# NOTE: When calling this in a sub-shell whose parents already has traps installed, make sure to do »trap - trapName« first. On a new shell, this should be a no-op, but without it, the parent shell's traps will be added to the sub-shell as well (due to strange behavior of »trap -p« (in bash ~5.1.8)).
2022-06-04 20:54:09 +02:00
function prepend_trap { # 1: command, ...: trapNames
2023-01-29 15:55:56 +01:00
fatal( ) { printf " ERROR: $@ \n " 1>& 2 ; \r eturn 1 ; }
local cmd = $1 ; shift 1 || fatal " ${ FUNCNAME } usage error "
2022-05-09 13:20:43 +02:00
local name ; for name in " $@ " ; do
trap -- " $( set +x
printf '%s\n' " ( ${ cmd } ) || true ; "
2023-01-29 15:55:56 +01:00
p3( ) { printf '%s\n' " ${ 3 :- } " ; }
eval " p3 $( trap -p " ${ name } " ) "
2022-05-09 13:20:43 +02:00
) " " ${ name } " || fatal " unable to add to trap ${ name } "
done
2022-06-04 20:54:09 +02:00
}
declare -f -t prepend_trap # required to modify DEBUG or RETURN traps
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
2022-07-29 12:49:55 +02:00
## Given the name to an existing bash function, this creates a copy of that function with a new name (in the current scope).
function copy-function { # 1: existingName, 2: newName
2023-01-29 15:55:56 +01:00
local original = $( declare -f " ${ 1 ?existingName not provided } " ) ; if [ [ ! $original ] ] ; then echo " Function $1 is not defined " 1>& 2 ; \r eturn 1 ; fi
2022-07-29 12:49:55 +02:00
eval " ${ original / $1 / ${ 2 ?newName not provided } } " # run the code declaring the function again, replacing only the first occurrence of the name
}
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
2023-07-31 02:47:24 +02:00
## Ensures that a directory exists, like »mkdir -p«, but for any new path elements created, copies the user/group/mode of the closest existing parent.
# Only uses the fallback user/group/mode when the closest existing parent is already a sticky dir (whose (root-)ownership does not mean much, as it is meant for children owned by any/other user(s), like /tmp).
function mkdir-sticky { # 1: path, 2?: fallbackOwner, 3?: fallbackGroup, 4?: fallbackMode
local path ; path = $1 ; shift
if [ [ -d $path ] ] ; then return ; fi # existing (symlink to existing) dir
if [ [ -L $path || -e $path ] ] ; then echo " Can't create (child of) existing file (or broken symlink) ' $path ' " 1>& 2 ; return 1 ; fi
local parent ; parent = $( dirname " $path " ) || return
mkdir-sticky " $parent " " $@ " || return
parent = $( realpath " $parent " ) || return
stat = ( $( stat --format '%u %g %a' " $parent " ) ) || return
if [ [ ${ stat [2] } = ~ ^1...$ ] ] ; then # sticky parent
#echo "Can't infer correct ownership/permissions for child '$( basename "$path" )' of sticky dir '$parent'" 1>&2 ; return 1
install --directory --owner= " ${ 1 :- 0 } " --group= " ${ 2 :- 0 } " ${ 3 +--mode= " $3 " } " $path " || return
else
install --directory --owner= ${ stat [0] } --group= ${ stat [1] } --mode= ${ stat [2] } " $path " || return
fi
}
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
## Writes a »$name«d secret from stdin to »$targetDir«, ensuring proper file permissions.
2022-11-30 13:41:21 +01:00
function write-secret { ( set -u # 1: path, 2?: owner[:[group]], 3?: mode
mkdir -p -- " $( dirname " $1 " ) " / || exit
install -o root -g root -m 000 -T /dev/null " $1 " || exit
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
secret = $( tee " $1 " ) # copy stdin to path without removing or adding anything
2023-01-29 15:55:56 +01:00
if [ [ " ${# secret } " = = 0 ] ] ; then echo " write-secret to $1 was empty! " 1>& 2 ; \e xit 1 ; fi # could also stat the file ...
2022-11-30 13:41:21 +01:00
chown " ${ 2 :- root : root } " -- " $1 " || exit
chmod " ${ 3 :- 400 } " -- " $1 " || exit
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
) }
## Interactively prompts for a password to be entered and confirmed.
2022-11-30 13:41:21 +01:00
function prompt-new-password { ( set -u # 1: usage
read -s -p " Please enter the new password $1 : " password1 || exit ; echo 1>& 2
2023-07-20 17:47:47 +02:00
if ( ( ${# password1 } = = 0 ) ) ; then printf 'Password empty.\n' 1>& 2 ; \e xit 1 ; fi
2022-11-30 13:41:21 +01:00
read -s -p "Please enter the same password again: " password2 || exit ; echo 1>& 2
2023-07-20 17:47:47 +02:00
if [ [ " $password1 " != " $password2 " ] ] ; then printf 'Passwords mismatch.\n' 1>& 2 ; \e xit 1 ; fi
2022-11-30 13:41:21 +01:00
printf %s " $password1 " || exit
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
) }
2023-07-20 17:47:47 +02:00
## If »secretFile« does not exist, interactively prompts up to three times for the secret to be stored in that file.
function prompt-secret-as { ( set -u # 1: what, 2: secretFile, 3?: owner[:[group]], 4?: mode
if [ [ -e $2 ] ] ; then \r eturn ; fi
what = $1 ; shift
function prompt {
read -s -p " Please enter $what : " value || exit ; echo 1>& 2
if ( ( ${# value } = = 0 ) ) ; then printf 'Nothing entered. ' 1>& 2 ; \r eturn 1 ; fi
read -s -p "Please enter that again, or return empty to skip the check: " check || exit ; echo 1>& 2
if [ [ $check && $value != " $check " ] ] ; then printf 'Entered values mismatch. ' 1>& 2 ; \r eturn 1 ; fi
}
for attempt in 2 3 x ; do
if prompt && printf %s " $value " | write-secret " $@ " ; then break ; fi
if [ [ $attempt = = x ] ] ; then echo "Aborting." 1>& 2 ; \r eturn 1 ; fi
echo " Retrying ( $attempt /3): " 1>& 2
done
) }
2023-06-16 02:14:51 +02:00
declare-flag install-system inspectScripts "" "When running installation hooks (»...*Commands« composed as Nix strings) print out and pause before each command. This works ... semi-well."
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
## Runs an installer hook script, optionally stepping through the script.
2023-05-02 02:13:24 +02:00
function run-hook-script { ( # 1: title, 2: scriptPath
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
trap - EXIT # start with empty traps for sub-shell
if [ [ ${ args [inspectScripts] :- } && " $( cat " $2 " ) " != $'' ] ] ; then
2022-11-30 13:41:21 +01:00
echo " Running $1 commands. For each command printed, press Enter to continue or Ctrl+C to abort the installation: " 1>& 2
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
# (this does not help against intentionally malicious scripts, it's quite easy to trick this)
BASH_PREV_COMMAND = ; set -o functrace ; trap 'if [[ $BASH_COMMAND != "$BASH_PREV_COMMAND" ]] ; then echo -n "> $BASH_COMMAND" >&2 ; read ; fi ; BASH_PREV_COMMAND=$BASH_COMMAND' debug
fi
2023-05-02 02:13:24 +02:00
set -e # The called script snippets should not rely on this, but neither should this function rely on the scripts correctly exiting on errors.
improve installation, add support for:
ZFS, encryption (keys, keystore, LUKS), bootFS, ephemeral root (tmpfs, ZFS, F2FS, ...), testing in qemu, options & debugging, ... and many small things
2022-05-31 03:41:28 +02:00
source " $2 "
) }
2023-02-05 22:59:39 +01:00
## Lazily builds a nix derivation at run time, instead of when building the script.
# When maybe-using packages that take long to build, instead of »at{some.package.out}«, use: »$( build-lazy at{some.package.drvPath!unsafeDiscardStringContext} out )«
function build-lazy { # 1: drvPath, 2?: output
2023-05-02 02:13:24 +02:00
# Nix v2.14 introduced a new syntax for selecting the output of multi-output derivations, v2.15 then changed the default when passing the path to an on-disk derivation. »--print-out-paths« is also not available in older versions.
if version-gr-eq "@{native.nix.version}" '2.14' ; then
PATH = $PATH :@{ native.openssh} /bin @{ native.nix} /bin/nix --extra-experimental-features nix-command build --no-link --print-out-paths ${ args [quiet] : +--quiet } " $1 " '^' " ${ 2 :- out } "
else
PATH = $PATH :@{ native.openssh} /bin @{ native.nix} /bin/nix --extra-experimental-features nix-command build --no-link --json ${ args [quiet] : +--quiet } " $1 " | @{ native.jq} /bin/jq -r .[ 0] .outputs." ${ 2 :- out } "
fi
2023-02-05 22:59:39 +01:00
}
2023-05-02 02:13:24 +02:00
## Tests whether (returns 0/success if) the first version argument is greater/less than (or equal) the second version argument.
2023-07-31 02:47:24 +02:00
function version-gr-eq { printf '%s\n%s' " $1 " " $2 " | sort -C -V -r ; }
2023-05-02 02:13:24 +02:00
function version-lt-eq { printf '%s\n%s' " $1 " " $2 " | sort -C -V ; }
function version-gt { ! version-gt-eq " $2 " " $1 " ; }
function version-lt { ! version-lt-eq " $2 " " $1 " ; }