pacwrap/bin/pacwrap-utils
Xavier c0bb7f80d1 Release 0.4.1 - Compatibility bug and improved error handling.
- Disallow multiple type parameters applied to --create
- Provide the ROOT type in pacwrap-utils replicate function
- Updated help manual.
2023-10-22 07:13:41 -04:00

535 lines
14 KiB
Bash
Executable file

#!/bin/bash
#
# PacWrap -- Utility Script
#
# Copyright (C) 2023 Xavier R.M.
# sapphirus(at)azorium(dot)net
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, with only version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
source pacwrap-common
export PACWRAP_UTILS=1
NUMFMT="numfmt --to=si --suffix=B --round=nearest --from-unit=1000 --format=%2f "
main () {
trap exit INT
local links=
parse_args $@
init
case $SWITCH in
Urc) refresh_configuration;;
Uc*) [[ $(type -p paccache) ]] && paccache --cachedir $INSTANCE_PACMAN_CACHE ${ARGS[@]};;
Uml) initialize_data_directory;;
Ud*|Ulsd|Urmd) desktop_entry;;
Uo*) open;;
Urm*) remove;;
Ur*) replicate;;
Uv*) edit_file 1;;
Ue*) edit_file;;
Uls*) summary;;
Ul*) link_root;;
*) log_invalid_arguments $1
esac
}
script_init () {
[[ $SWITCH != Uls* ]] && log_to_file "Running '$RUNTIME_EXEC $RUNTIME_ARGS'"
[[ $SWITCH == *du* ]] && SHOW_SIZE=1
[[ $SWITCH == *b* ]] && NUMFMT="echo"
[[ $SWITCH == *f* ]] && FORCE_ACTION=1
[[ $REPLICATION_CONFIG_DIR ]] &&
INSTANCE_CONFIG_DIR=$REPLICATION_CONFIG_DIR
local linklist=$(ls -U -1F "$INSTANCE_ROOT_DIR" 2>/dev/null | grep -i "@" | tr -d "@")
for f in $linklist; do
links+=($f)
done
touch $LOCK_FILE
trap on_exit EXIT
}
parse_args () {
for var in "$@"; do case $var in
--utils) continue;;
-v|--verbose) SWITCH+="v";;
--force|-f) SWITCH+="f";;
--noconfirm|-n) SWITCH+="n";;
--desktop-entry|-d) SWITCH+="d";;
--list|-ls) SWITCH+="ls";;
--show-bytes|-b) SWITCH+="b";;
--disk-depth|-dd) SWITCH+="dd";;
--disk-util|-du) SWITCH+="du";;
--disk-sum|-uu) SWITCH+="uu";;
--edit|-e) SWITCH+="e";;
--view|-v) SWITCH+="v";;
--inspect-pacman-sync|-ips) SWITCH+="vips";;
--inspect-pacman-syncdb|-idb) SWITCH+="vidb";;
--pacman-temmplate|-pt) SWITCH+="pt";;
--pacman|-p) SWITCH+="p";;
--config|-c) SWITCH+="c";;
--remove|-rm) SWITCH+="rm";;
--home|-h) SWITCH+="h";;
--replicate|-r|--root) SWITCH+="r";;
--link|-l|--log) SWITCH+="l";;
--tar|-t) SWITCH+="t";;
--open|-o) SWITCH+="o";;
--mirror-list|-ml) SWITCH+="ml";;
-V|--version) SWITCH="V";;
-U*) SWITCH="${var:1}";;
--cfg-dir=*) REPLICATION_CONFIG_DIR="${var:10}";;
*) ARGS+=("$var");;
esac; done
}
summary() {
declare -A size
local total=0
load_configuration_all
[[ $SHOW_SIZE ]] && parse_du
list_root | column -t -s "$DELIMITER"
if [[ $SHOW_SIZE ]] && [[ $SWITCH == *uu* ]]; then
local actual_size=${size["root"]}
local diff=$(($total - $actual_size))
[[ $diff -le 0 ]] && diff=0
printf "\n${BOLD}Diskspace utilization summary$RESET\n"
printf "\n%s\t\t%s\n" "Total" " $($NUMFMT $total)"
[[ $SWITCH != *dd* ]] && return
local actual=$($NUMFMT $actual_size)
local difference=$($NUMFMT $diff)
local space=
for ((i=${#difference}; i<=${#actual}; i++)); do space+=" "; done
printf "%s\t%s\n" "Difference" "$UNDERLINE- $($NUMFMT $diff)$space$RESET"
printf "%s\t\t%s\n" "${BOLD}Actual$RESET:" " $actual"
fi
}
parse_du() {
for string in $(du -d 1 "$INSTANCE_ROOT_DIR" | tr "$DELIMITER" '-'); do
local bytes=${string%%-*}
local instance=${string##*/}
size["$instance"]=$bytes
if [[ $SWITCH == *dd* ]]; then
[[ $instance == "root" ]] && continue
local dh=$(du -d 1 "$INSTANCE_ROOT_DIR/$instance" | tail -n-1); bytes=${dh%$DELIMITER*}
size["$instance"]=$bytes
fi
[[ $instance == "root" ]] && continue
((total+=bytes))
done
}
list_root() {
local roots=(${BASEROOTDEPS[@]} ${ROOTDEPS[@]} ${ROOTS[@]} ${links[@]})
printf "$BOLD%s\t%s\t%s\t%s$RESET\n" "Name" "Dependency" \
$([[ $SHOW_SIZE ]] && echo Size) "Type"
for instance in ${roots[@]}; do
local dep="-"
local type="LINK"
local bytes=0
if [[ -L "$INSTANCE_ROOT_DIR/$instance" ]]; then
dep="$(ls -l "$INSTANCE_ROOT_DIR/$instance" | sed -z "s,$INSTANCE_ROOT_DIR/,,g")"
else
bytes=${size["$instance"]}; type=$(return_type)
fi
[[ $type != BASE ]] && [[ $type != LINK ]] && dep=$(return_dependency)
printf "%s\t%s\t%s\t%s\n" "$instance" "${dep##*> }" \
$([[ $SHOW_SIZE ]] && echo "$($NUMFMT $bytes)") "$type"
done
}
desktop_entry() {
local instance=${ARGS[0]}
local application=${ARGS[1]}
local applications_dir="$HOME/.local/share/applications"
[[ ! -d "$applications_dir" ]] && log_error "Application directory '~/${applications_dir##$HOME/}' not found." 1
case $SWITCH in
*ls*) if [[ ! $instance ]]; then
local entries=($(ls "$applications_dir"/pacwrap.* 2>/dev/null))
printf "$BOLD%s$RESET:\n" "Desktop Entries"
[[ ${#entries[@]} -le 0 ]] && echo "No entries found." && return
for file in ${entries[@]}; do
echo ${file##*/}; done
else check_root $instance
local entries=($(ls "$INSTANCE_ROOT_DIR/$instance/usr/share/applications/" 2>/dev/null))
printf "$BOLD%s$RESET in $BOLD%s$RESET:\n" "Desktop Entries" "$instance"
[[ ${#entries[@]} -le 0 ]] && echo "No entries found." && return
for file in ${entries[@]}; do
echo ${file##*/}; done
fi;;
*e) edit_file;;
*rm*) local desktop_entry="$applications_dir/pacwrap.$instance.desktop"
test_argument "Desktop entry" $instance
check_file "Desktop entry" "$desktop_entry"
rm "$desktop_entry"
log "$ARROW_GREEN Deleted '$desktop_entry'.";;
*) local desktop_entry_local="$applications_dir/pacwrap.$application.desktop"
local desktop_entry="$INSTANCE_ROOT_DIR/$instance/usr/share/applications/$application.desktop"
test_argument "Application" $application
check_root $instance
check_file "Desktop entry" "$desktop_entry_local" 1
check_file "Container desktop entry" "$desktop_entry"
sed -z "s/Exec=/Exec=pacwrap \-E $instance /g" < "$desktop_entry" > "$desktop_entry_local"
log "$ARROW_GREEN Created desktop entry for $BOLD$application$RESET derived from container $BOLD$instance$RESET."
esac
}
link_root() {
local instance=${ARGS[0]}
local link=${ARGS[1]}
test_argument "Targets" $instance
check_root $link
check_root 1 $instance
[[ ! $SWITCH_NO_CONFIRM ]] && printf "$BAR %s\n\n$BOLD%s\n\n" "Create virtual instance" "$instance $ARROW $BOLD$link"
[[ ! "$(query_confirm_Yn "Proceed with creation?")" ]] && return
log "$BAR_GREEN Creating virtual instance $instance $ARROW $BOLD$link"
ln -s "$INSTANCE_ROOT_DIR/$link" "$INSTANCE_ROOT_DIR/$instance"
mkdir -p "$INSTANCE_HOME_DIR/$instance"
echo 'PS1="'$instance'> "' > "$INSTANCE_HOME_DIR/$instance/.bashrc"
load_configuration $link; instance=${ARGS[0]}
TYPE=LINK
DEPS=(${INSTANCE_CONFIG[$link,$CONF_DEPS]} $link)
MOUNT="${INSTANCE_CONFIG[$link,$CONF_MOUNT]}"
PERMISSIONS="${INSTANCE_CONFIG[$link,$CONF_PERMS]}"
PERMISSIONS_DBUS="${INSTANCE_CONFIG[$link,$CONF_DBUS]}"
ENV_VARS="${INSTANCE_CONFIG[$link,$CONF_ENV]}"
populate_configuration
generate_config
log "$ARROW_GREEN Process complete!" \
"Created $instance -> $link"
}
replicate() {
BASEROOTDEPS=
ROOTDEPS=
ROOTS=
local instances=
if [[ $SWITCH == *t* ]]; then
local default=$([[ ${#ARGS[@]} -le 1 ]] && echo 1)
local tar_archive=${ARGS[0]}
test_argument "tar" "$tar_archive"
check_file "tar archive" "$tar_archive"
local tar_list=($(tar tf "${ARGS[0]}"))
for file in ${tar_list[@]}; do
local instance=${file##*/}
local this=$default
[[ ! $this ]] && for instance_list in ${ARGS[@]}; do
[[ "$instance" == "$instance_list" ]] && this=1 && break; done
[[ ! $instance ]] || [[ ! $this ]] && continue
eval $(tar axfO "$tar_archive" $file | grep -v "#")
populate_configuration
populate_container_array
reset_configuration_params
instances+=($instance)
done
else
instances=(${ARGS[@]})
[[ ${#ARGS[@]} -le 0 ]] && instances=$@
test_argument "Targets" $instances
for instance in ${instances[@]}; do
[[ ! -f "$INSTANCE_CONFIG_DIR/$instance.yml" ]] && continue
source_configuration
populate_configuration
populate_container_array
reset_configuration_params
done
fi
[[ ! $SWITCH_NO_CONFIRM ]] && echo -e "$BAR Replicate \n\n${instances[@]}\n"
[[ ! "$(query_confirm_yN "Proceed with replication?")" ]] && return
check_active_instances
invoke_replication ${BASEROOTDEPS[@]} ${ROOTDEPS[@]} ${ROOTS[@]} ${LINKS[@]}
}
invoke_replication() {
if [[ $SWITCH = *d* ]]; then
printf "$BAR_GREEN %s$RESET\n" "Deleting root instances..."
for instance in $@; do
[[ ! -d "$INSTANCE_ROOT_DIR/$instance" ]] && continue
rm -rf "$INSTANCE_ROOT_DIR/$instance"
printf "$ARROW_GREEN %s$RESET\n" "Deletion of $instance's root complete!"
log_to_file "Deleted root $instance."
done
fi
printf "$BAR_GREEN %s$RESET\n" "Replication of root filesystems from configuration..."
declare -A completed
for instance in $@; do
local deps_return=$(return_dependencies)
local deps=()
for dep in ${BASEROOTDEPS[@]}; do
for depr in $deps_return; do [[ $dep == $depr ]] && deps+=($dep); done
done
for dep in ${ROOTDEPS[@]}; do
for depr in $deps_return; do [[ $dep == $depr ]] && deps+=($dep); done
done
for dep in ${deps[@]}; do replicate_instance $dep; done
for depr in $deps_return; do
[[ ! -d "$INSTANCE_ROOT_DIR/$depr" ]] &&
log_error "Dependency $depr not fulfilled for $instance." 1
done
replicate_instance $instance
done
}
replicate_instance() {
local instance=$1
[[ ${completed[$instance]} ]] && return
log_to_file "Beginning replication of $instance."
local depend=
local type=$(return_type)
local params="c"
[[ $SWITCH == *v* ]] && params+="v"
[[ $type != BASE ]] && depend=$(return_dependency)
case $type in
ROOT) params+="r";;
BASE) params+="b";;
DEP) params+="d";;
LINK) ln -s "$INSTANCE_ROOT_DIR/$depend" "$INSTANCE_ROOT_DIR/$instance"
mkdir -p "$INSTANCE_HOME_DIR/$instance"
echo 'PS1="'$instance'> "' > "$INSTANCE_HOME_DIR/$instance/.bashrc"
generate_config
printf "$ARROW_GREEN %s$RESET\n" "Replication of $BOLD$instance$RESET complete!"
log_to_file "Replication of $instance complete!"
completed[$instance]=1
return;;
esac
local depend_params
for dep in $(return_dependencies); do
depend_params+="-t $dep "
done
$CREATE_SCRIPT -Syu$params -t $instance $(return_packages) $depend_params --noconfirm
if [[ $? == 0 ]]; then
printf " %s$RESET\n" "Replication of $BOLD$instance$RESET complete!"
log_to_file "Replication of $instance complete!"
fi
completed[$instance]=1
}
refresh_configuration() {
load_configuration_all
local roots=(${BASEROOTDEPS[@]} ${ROOTDEPS[@]} ${ROOTS[@]} ${LINKS[@]})
for instance in ${roots[@]}; do
generate_config $(return_packages)
done
log "$ARROW_GREEN Refreshed configurations."
}
remove() {
SWITCH=${SWITCH##*rm}
check_active_instances
[[ $SWITCH == *r* ]] && invoke_removal "root" "$INSTANCE_ROOT_DIR"
[[ $SWITCH == *h* ]] && invoke_removal "home" "$INSTANCE_HOME_DIR"
}
invoke_removal() {
local instances=${ARGS[@]};
test_argument "Targets" $instances
[[ ! $SWITCH_NO_CONFIRM ]] && printf "%s\n\n$BOLD%s\n\n$RESET" "$BAR Delete $1" "$instances"
[[ ! "$(query_confirm_yN "Proceed with $1?")" ]] && return
for instance in $instances; do
if [[ ! -d "$2/$instance" ]]; then
log_error $LOG_ERR_WARN "$instance does not exist."
continue
fi
rm -rf "$2/$instance"
printf "$ARROW_GREEN %s$RESET\n" "Deletion of $instance's $1 complete!"
log_to_file "Deleted $instance's $1."
done
}
open() {
local directory=
local instance=${ARGS[0]};
check_root $instance
case $SWITCH in
*r*) directory=$INSTANCE_ROOT_DIR/$instance ;;
*h*) directory=$INSTANCE_HOME_DIR/$instance ;;
esac
test_argument "Directory Type" $directory
gio open $directory
}
edit_file() {
local instance=${ARGS[0]};
local create=
local dissolve=
local file=
local tmpfile=
local error=
[[ ! $EDITOR ]] && EDITOR="vi"
[[ ! -f $(type -P $EDITOR) ]] &&
log_error "\$EDITOR was not found at $EDITOR." 1
[[ $1 ]] && dissolve=1
tmpfile=$(mktemp)
case $SWITCH in
*ml*) file="$INSTANCE_PACMAN_MIRRORLIST"
error="Pacman Mirrorlist $file";;
*idb*) file=$(return_pacman_syncdb $instance)
error="instanced pacman $file"
printf "##\n## %s\n" "For the purposes of overview or diagnostics only." > $tmpfile
dissolve=1;;
*ips*) file=$(return_pacman_sync $instance)
error="instanced template $file"
printf "##\n## %s\n" "For the purposes of overview or diagnostics only." > $tmpfile
dissolve=1;;
*c*) file="$INSTANCE_CONFIG_DIR/$instance.yml"
error="config file";;
*d*) file="$HOME/.local/share/applications/pacwrap.$instance.desktop"
error="pacwrap.$instance.desktop";;
*l*) file="$PACWRAP_DATA_DIR/pacwrap.log"
error="$file"
dissolve=1;;
*pt*) file=$(return_pacman_template $instance)
error="pacman template $file";;
*p*) file="$INSTANCE_PACMAN_CFG_DIR/pacman.conf"
error="default pacman.conf";;
*) log_invalid_arguments 1
esac
if ([[ ! -f "$file" ]] && [[ $dissolve ]]) ||
([[ ! -f "$file" ]] && [[ ! $create ]]); then
log_error "$error not found."
rm $tmpfile
return
fi
[[ -f "$file" ]] &&
cat "$file" >> $tmpfile
$EDITOR $tmpfile
local tmp_sum=$(sha256sum $tmpfile)
local blank_sum=$(sha256sum /dev/null)
local config_sum=
if [[ "${tmp_sum% *}" != "${blank_sum% *}" ]] && [[ ! $dissolve ]]; then
[[ -f $file ]] && config_sum=$(sha256sum $file)
if [[ "${tmp_sum% *}" != "${config_sum% *}" ]]; then
cp $tmpfile "$file"
log "$ARROW_GREEN Changes written." \
"Changes written to $file."
else
log "$ARROW No changes made"
fi
else
log "$ARROW No changes made."
fi
rm $tmpfile
}
check_active_instances() {
[[ $FORCE_ACTION ]] && return
[[ ! ${ARGS[@]} ]] && log_invalid_arguments 1
local running_instances=($($PS_SCRIPT -Pid ${ARGS[@]} 2>/dev/null))
[[ ${#running_instances[@]} > 0 ]] &&
log_error $ARROW_RED "There are running instances of the containers specified." \
"Please close or terminate them to perform this operation." 1
}
on_exit () {
[[ -f "$LOCK_FILE" ]] && rm "$LOCK_FILE"
}
main $@