pacwrap v0.9.8

- Heavy refactor with code cleanup
- Completed common logging infrastructure
- Distribution agnosticism attained!
- pacwrap-common: Initial commit
- pacwrap-common: Dependency check
- pacwrap-utils: Replication functionality
- pacwrap-sync: Increased resolution of linker progress
- pacwrap-sync: Container configuration with explicit package array
- pacwrap-sync: Improved UX, catching erronous exit codes from pacman
- pacwrap-create: Refactor and cleanup
- pacwrap-create: Replaced pacstrap with tarball installer
This commit is contained in:
Xavier Moffett 2023-04-23 06:41:49 -04:00
parent 72e88098c3
commit a16f74191d
5 changed files with 847 additions and 568 deletions

View file

@ -18,33 +18,27 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
BOLD=$(tput bold) source pacwrap-common
RED=$(tput setaf 1)
RESET=$(tput sgr0)
VER="v0.9.7 R4 ${BOLD}BETA$RESET"
EXEC_NAME="pacwrap" EXEC_NAME="pacwrap"
UTILS_SCRIPT="pacwrap-utils"
main () { main () {
distro_check
parse_args "$@" parse_args "$@"
init_structures
distro_warn
case $SWITCH in case $SWITCH in
C) C)
pacwrap-create ${ARGS[@]} $CREATE_SCRIPT ${ARGS[@]}
;; ;;
E) E)
pacwrap-exec ${ARGS[@]} $EXEC_SCRIPT ${ARGS[@]}
;; ;;
S) S)
pacwrap-sync ${ARGS[@]} $SYNC_SCRIPT ${ARGS[@]}
;; ;;
U) U)
pacwrap-utils ${ARGS[@]} $UTILS_SCRIPT ${ARGS[@]}
;; ;;
h) h)
help_operation $HELP help_operation $HELP
@ -63,21 +57,6 @@ main () {
esac esac
} }
init_structures() {
INSTANCE_DATA_DIR="$HOME/.local/share/pacwrap"
INSTANCE_CACHE_DIR="$HOME/.cache/pacwrap/pkg"
INSTANCE_CONFIG_DIR="$HOME/.config/pacwrap"
if [[ $PACWRAP_DEBUG ]]; then
INSTANCE_DATA_DIR="$PACWRAP_DEBUG"
INSTANCE_CONFIG_DIR="$PACWRAP_DEBUG/cfg"
fi
if [[ ! -d $INSTANCE_DATA_DIR ]] || [[ ! -d $INSTANCE_DATA_DIR ]] || [[ ! -d $INSTANCE_DATA_DIR ]]; then
$UTILS_SCRIPT -Ui
fi
}
parse_args () { parse_args () {
ARGS=() ARGS=()
for var in "$@"; do for var in "$@"; do
@ -115,30 +94,6 @@ parse_args () {
done done
} }
distro_check() {
source /etc/os-release
if [[ ! -f /usr/bin/pacman ]]; then
version
cat << _WARN
$BOLD${RED}error:$RESET An attempt was made to execute pachwrap on an unsupported distribution.
pachwrap is currently only supported on distributions utilising Arch Linux's pacman package manager.
Support for other package managers with a similar, requisite subset of features may be possible in future.
_WARN
exit 1
fi
if [[ ! $ID == "arch" ]]; then cat << _WARN
$BOLD${RED}WARNING:$RESET You are running pachwrap on an unsupported derviative of Arch Linux.
Support is not guaranteed. Use at your ${BOLD}OWN$RESET risk.
_WARN
fi
}
version () { version () {
if [[ ! $VER_DISPLAY ]]; then if [[ ! $VER_DISPLAY ]]; then
@ -283,4 +238,14 @@ _USAGE
esac esac
} }
function distro_warn() {
source /etc/os-release
[[ ! $ID == "arch" ]] && cat << _WARN
$BOLD${RED}WARNING:$RESET You are running pacwrap on an unsupported distribution of Linux or Unix.
Support is not guaranteed. Use at your ${BOLD}OWN$RESET risk.
_WARN
}
main $@ main $@

343
bin/pacwrap-common Executable file
View file

@ -0,0 +1,343 @@
#!/bin/bash
#
# PacWrap -- common 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/>.
RUNTIME_ARGS="$0 $@"
BOLD=$(tput bold)
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
CYAN=$(tput setaf 6)
YELLOW=$(tput setaf 11)
RESET=$(tput sgr0)
UNDERLINE=$(tput smul)
BAR="$RED$BOLD::$RESET$BOLD"
BAR_GREEN="$GREEN$BOLD::$RESET$BOLD"
ARROW="$CYAN$BOLD->$RESET"
ARROW_GREEN="$GREEN$BOLD->$RESET"
ARROW_RED="$RED$BOLD->$RESET"
VER="v0.9.8 ${BOLD}BETA$RESET"
EXEC_SCRIPT="pacwrap-exec"
CREATE_SCRIPT="pacwrap-create"
UTILS_SCRIPT="pacwrap-utils"
SYNC_SCRIPT="pacwrap-sync"
LOG_ERR_HELP=1
LOG_ERR_WARN=2
LOG_ERR_ERROR=3
declare -A INSTANCE_CONFIG
[[ ! $PACWRAP_DATA_DIR ]] && PACWRAP_DATA_DIR="$HOME/.local/share/pacwrap"
[[ ! $PACWRAP_CACHE_DIR ]] && PACWRAP_CACHE_DIR="$HOME/.cache/pacwrap"
[[ ! $PACWRAP_CONFIG_DIR ]] && PACWRAP_CONFIG_DIR="$HOME/.config/pacwrap"
VERBOSE="/dev/null"
LOCK_FILE="$PACWRAP_DATA_DIR/pacwrap.lck"
LOG_FILE="$PACWRAP_DATA_DIR/pacwrap.log"
OUTPUT_DEST=$LOG_FILE
INSTANCE_ROOT_DIR=$PACWRAP_DATA_DIR/root
INSTANCE_HOME_DIR=$PACWRAP_DATA_DIR/home
INSTANCE_CONFIG_DIR=$PACWRAP_CONFIG_DIR/root
INSTANCE_DB_DIR=$PACWRAP_DATA_DIR/database
INSTANCE_PACMAN_DIR="$PACWRAP_DATA_DIR/pacman"
INSTANCE_PACMAN_GNUPG="$INSTANCE_PACMAN_DIR/gnupg"
INSTANCE_PACMAN_SYNC="$INSTANCE_PACMAN_DIR/sync"
INSTANCE_PACMAN_MIRRORLIST="$PACWRAP_CONFIG_DIR/pacman.d/mirrorlist"
INSTANCE_PACMAN_CACHE="$PACWRAP_CACHE_DIR/pkg"
INSTANCE_PACMAN_CFG_DIR="$PACWRAP_CONFIG_DIR/pacman"
init() {
runtime_check
[[ $SWITCH == *n* ]] && SWITCH_NOCONFIRM=1
[[ $SWITCH == *v* ]] && VERBOSE="/dev/stdout"
[[ ! -f $LOG_FILE ]] && touch $LOG_FILE
[[ ! $1 ]] && [[ -f $LOCK_FILE ]] &&
log_error "pacwrap is locked: $LOCK_FILE$RESET" 2
local list="ls -U -1F $INSTANCE_ROOT_DIR"
local rootlist=$($list 2>/dev/null | grep -i "/" | tr -d "/")
for instance in $rootlist; do
source_configuration
populate_configuration
populate_container_array
done
if [[ $SWITCH == v ]]; then
VER_DISPLAY=$EXEC_NAME pacwrap -v
exit
fi
script_init
perform_datadir_check
}
populate_container_array() {
case $TYPE in
BASE)
baserootdeps+=($instance)
;;
DEP)
rootdeps+=($instance)
;;
*)
roots+=($instance)
;;
esac
}
source_configuration() {
source $INSTANCE_CONFIG_DIR/$instance
}
populate_configuration() {
INSTANCE_CONFIG[$instance,0]=$TYPE
INSTANCE_CONFIG[$instance,1]="deps=(${DEPS[@]})"
INSTANCE_CONFIG[$instance,2]=$SYNC_PACMANDB
INSTANCE_CONFIG[$instance,3]="pkgs=(${PKG[@]})"
}
return_sync_pacmandb() {
echo ${INSTANCE_CONFIG[$instance,2]}
}
return_base() {
[[ $(return_type) == "BASE" ]] && echo $instance && return
eval ${INSTANCE_CONFIG[$instance,1]}
echo ${deps[0]}
}
return_dependencies() {
eval ${INSTANCE_CONFIG[$instance,1]}
echo ${deps[@]}
}
return_packages() {
eval ${INSTANCE_CONFIG[$instance,3]}
echo ${pkgs[@]}
}
return_type() {
echo ${INSTANCE_CONFIG[$instance,0]}
}
return_dependency() {
eval ${INSTANCE_CONFIG[$instance,1]}
echo ${deps[$((${#deps[@]} - 1))]}
}
return_pacman_template() {
echo "$INSTANCE_PACMAN_CFG_DIR/template/pacman.$1$INSTANCE.conf"
}
return_pacman_sync() {
echo "$INSTANCE_PACMAN_CFG_DIR/sync/pacman.$1$INSTANCE.conf"
}
return_pacman_syncdb() {
echo "$INSTANCE_PACMAN_CFG_DIR/syncdb/pacman.$1$INSTANCE.conf"
}
log () {
printf "%s$RESET\n" "$1"
[[ $2 ]] && case $2 in
1)
log_to_file "$1"
;;
*)
log_to_file "$2"
;;
esac
}
log_error() {
case $1 in
$LOG_ERR_HELP)
printf "%s %s\n%s\n" "$EXEC_NAME error:" "$2" \
"Try 'pacwrap -h' for more information on valid operational parameters."
exit
;;
$ARROW_RED)
printf "$ARROW_RED %s\n %s\n" "$2" "$3"
log_to_file "$2"
[[ $4 ]] && exit $4
;;
$LOG_ERR_WARN)
printf "$BOLD$YELLOW%s$RESET %s\n" "warning:" "$2"
[[ $3 ]] && exit $3
;;
*)
printf "$BOLD$RED%s$RESET %s\n" "error:" "$1"
[[ $2 ]] && exit $2
;;
esac
}
log_to_file() {
printf "[%s] [%s] %s\n" $(date '+%FT%H:%M:%S%z') $EXEC_NAME "$1" >>$LOG_FILE
}
print_progress_bar() {
printf "\r$PROGRESS_LABEL$RESET"
printf "%-*s" $(($2+1)) '[' | tr ' ' '#'
printf "%*s% 3d%%\r" $(($1-$2)) "]" "$3"
}
init_progress() {
TTY_SIZE=($(stty size))
PROGRESS_LENGTH=$((${TTY_SIZE[1]}/2))
}
set_progress_label() {
[[ $PROGRESS_OFF ]] && printf "\r%s" "$2 $1" && return
PROGRESS_LABEL="$1"
local label_length=${#PROGRESS_LABEL}
local padding_length=$(($PROGRESS_LENGTH-$label_length-8))
[[ $2 ]] && PROGRESS_LABEL="$2 $1" && padding_length=$(($padding_length-3))
for ((i=0; i<=padding_length; i+=1)); do
PROGRESS_LABEL+=" "
done
progress_bar
}
progress_bar() {
[[ $PROGRESS_OFF ]] && return
local bar_percent=$(($((PROGRESS_LENGTH*2))*$amt_done/$amt % 2 + $PROGRESS_LENGTH*$amt_done/$amt))
local percent=$((200*$amt_done/$amt % 2 + 100*$amt_done/$amt))
print_progress_bar $((PROGRESS_LENGTH+1)) $bar_percent $percent
}
query_confirm_yN () {
if [[ $SWITCH == *n* ]]; then
echo 1
return
fi
read -rp "$BAR $@ [y/N]$RESET " input
if [[ "$input" != "y" ]] &&
[[ "$input" != "Y" ]] &&
([[ "$input" == "" ]] ||
[[ "$input" != "" ]]); then
return
fi
echo 1
}
query_confirm_Yn () {
if [[ $SWITCH == *n* ]]; then
echo 1
return
fi
read -rp "$BAR $@ [Y/n]$RESET " input
if [[ "$input" != "Y" ]] &&
[[ "$input" != "y" ]] &&
[[ "$input" != "" ]]; then
return
fi
echo 1
}
perform_datadir_check() {
([[ ! -d $PACWRAP_DATA_DIR ]] ||
[[ ! -d $PACWRAP_CACHE_DIR ]] ||
[[ ! -d $PACWRAP_CONFIG_DIR ]]) &&
initialize_data_directory
([[ ! -d $PACWRAP_DATA_DIR ]] ||
[[ ! -d $PACWRAP_CACHE_DIR ]] ||
[[ ! -d $PACWRAP_CONFIG_DIR ]]) &&
printf "$BOLD$RED%s$RESET %s\n%s" "error:" "Data directories not found." && exit 2
}
initialize_data_directory() {
mkdir -p $PACWRAP_DATA_DIR/root \
$PACWRAP_DATA_DIR/home \
$PACWRAP_DATA_DIR/database \
$PACWRAP_DATA_DIR/pacman/sync \
$PACWRAP_DATA_DIR/pacman/gnupg \
$PACWRAP_CACHE_DIR/pkg \
$PACWRAP_CONFIG_DIR/root \
$PACWRAP_CONFIG_DIR/pacman.d \
$PACWRAP_CONFIG_DIR/pacman/sync \
$PACWRAP_CONFIG_DIR/pacman/syncdb \
$PACWRAP_CONFIG_DIR/pacman/template \
$PACWRAP_CONFIG_DIR/bwrap
local ins_string="\[options\]"
local template="\n\n###IGNOREPKG###\n"
([[ ! -f $INSTANCE_PACMAN_MIRRORLIST ]] || [[ $SWITCH == *m* ]]) && cp /etc/pacman.d/mirrorlist $INSTANCE_PACMAN_MIRRORLIST
[[ ! -f $INSTANCE_PACMAN_CFG_DIR/pacman.conf ]] && echo -e $(pacman_conf) > $INSTANCE_PACMAN_CFG_DIR/pacman.conf
cat $INSTANCE_PACMAN_CFG_DIR/pacman.conf | sed -z "s,$ins_string,$ins_string$template,g" > $INSTANCE_PACMAN_CFG_DIR/pacman.template.conf
}
pacman_conf() {
echo $(cat << _CONFIG
[options]\nHoldPkg = pacman glibc\nArchitecture = auto\nNoExtract = pacman-mirrorlist\n\nColor\nCheckSpace
\n#ParallelDownloads = 5\n\nSigLevel = Required DatabaseOptional\nLocalFileSigLevel = Optional\n\n#[testing]
\n#Include = /etc/pacman.d/mirrorlist\n\n[core]\nInclude = /etc/pacman.d/mirrorlist\n\n[extra]
\nInclude = /etc/pacman.d/mirrorlist\n\n#[community-testing]\n#Include = /etc/pacman.d/mirrorlist
\n\n[community]\nInclude = /etc/pacman.d/mirrorlist\n\n#[multilib-testing]\n#Include = /etc/pacman.d/mirrorlist
\n\n#[multilib]\n#Include = /etc/pacman.d/mirrorlist
_CONFIG
)
}
generate_config() {
local config=$(cat << _CONFIG
##\n## Configuration for $instance container\n## generated on $(date "+%F %T") by $EXEC_NAME.
\n##\n\n# Container type (DO NOT EDIT)\nTYPE=($(return_type))\n\n# Container dependency array (DO NOT EDIT)
\nDEPS=($(return_dependencies))\n\n# Container explicit install array\nPKG=($@)\n\n# Forcibly apply pacman -Sy to container
\n# Not applicable to ROOT containers\nSYNC_PACMANDB=$(return_sync_pacmandb)
_CONFIG
)
echo -e $config > $INSTANCE_CONFIG_DIR/$instance
}
runtime_check() {
if [[ ! $(type -P bwrap) ]] || [[ ! $(type -P bwrap) ]] || [[ ! $(type -P pacman-key gpg) ]]; then
cat << _WARN
$BOLD${RED}error:$RESET Requisite dependencies are missing.
Please make sure that the following binaries are present in \$PATH:
${BOLD}bwrap$RESET, ${BOLD}zstd$RESET, ${BOLD}pacman-key$RESET or ${BOLD}gpg$RESET.
_WARN
exit 2
fi
}

View file

@ -18,33 +18,22 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
BOLD=$(tput bold) source pacwrap-common
RED=$(tput setaf 1)
RESET=$(tput sgr0)
EXEC_NAME="pacwrap-exec" EXEC_NAME="pacwrap-exec"
main () { main () {
export PACWRAP_EXEC=1
parse_args "$@" parse_args "$@"
init 1
if [[ $SWITCH == v ]]; then [[ ! $INSTANCE ]] &&
VER_DISPLAY=$EXEC_NAME pachwrap -v log_error $LOG_ERR_HELP "Target not specified." 1
exit
fi
if [[ $SANDBOX == "" ]]; then [[ ! -d $INSTANCE_ROOT ]] || [[ ! -d $INSTANCE_HOME ]] &&
echo "$EXEC_NAME $BOLD${RED}ERROR$RESET: Target not specified." log_error $LOG_ERR_HELP "Container $INSTANCE not found." 1
echo "Try 'pacwrap -h' for more information on valid operational parameters."
exit 1
fi
init_vars
if [[ ! -d $INSTANCE_ROOT ]] || [[ ! -d $INSTANCE_HOME ]]; then
echo "$EXEC_NAME $BOLD${RED}ERROR$RESET: Sandbox $SANDBOX not found."
echo "Try 'pacwrap -h' for more information on valid operational parameters."
exit 1
fi
case $SWITCH in case $SWITCH in
*rc*|*cr*) *rc*|*cr*)
@ -62,24 +51,12 @@ main () {
esac esac
} }
init_vars () { script_init () {
INSTANCE_DATA_DIR="$HOME/.local/share/pacwrap" INSTANCE_ROOT=$INSTANCE_ROOT_DIR/$INSTANCE
INSTANCE_CACHE_DIR="$HOME/.cache/pacwrap/pkg" INSTANCE_HOME=$INSTANCE_HOME_DIR/$INSTANCE
INSTANCE_CONFIG_DIR="$HOME/.config/pacwrap"
if [[ $PACWRAP_DEBUG ]]; then
INSTANCE_DATA_DIR="$PACWRAP_DEBUG"
INSTANCE_CONFIG_DIR="$PACWRAP_DEBUG/cfg"
fi
LOG_FILE="$INSTANCE_DATA_DIR/pacwrap.log"
INSTANCE_ROOT_DIR=$INSTANCE_DATA_DIR/root
INSTANCE_HOME_DIR=$INSTANCE_DATA_DIR/home
INSTANCE_ROOT=$INSTANCE_ROOT_DIR/$SANDBOX
INSTANCE_HOME=$INSTANCE_HOME_DIR/$SANDBOX
INSTANCE_USER=user INSTANCE_USER=user
INSTANCE_HOME_MOUNT=/home/$INSTANCE_USER INSTANCE_HOME_MOUNT=/home/$INSTANCE_USER
INSTANCE_SCRIPT="$INSTANCE_CONFIG_DIR/bwrap/$SANDBOX.sh" INSTANCE_SCRIPT="$PACWRAP_CONFIG_DIR/bwrap/$INSTANCE.sh"
} }
@ -106,7 +83,7 @@ parse_args () {
;; ;;
*) *)
if [[ ! $sbdefined ]]; then if [[ ! $sbdefined ]]; then
SANDBOX=$var INSTANCE=$var
sbdefined=1 sbdefined=1
continue continue
fi fi
@ -134,16 +111,16 @@ execute_fakeroot() {
--ro-bind /usr/bin/fakechroot /usr/bin/fakechroot \ --ro-bind /usr/bin/fakechroot /usr/bin/fakechroot \
--ro-bind /usr/bin/faked /usr/bin/faked \ --ro-bind /usr/bin/faked /usr/bin/faked \
--ro-bind /etc/resolv.conf /etc/resolv.conf \ --ro-bind /etc/resolv.conf /etc/resolv.conf \
--ro-bind $INSTANCE_CONFIG_DIR/pacman/sync/pacman.$SANDBOX.conf /etc/pacman.conf \
--ro-bind /etc/localtime /etc/localtime \ --ro-bind /etc/localtime /etc/localtime \
--bind $INSTANCE_DATA_DIR/pacman/sync /var/lib/pacman/sync \ --bind $INSTANCE_PACMAN_SYNC /var/lib/pacman/sync \
--bind $INSTANCE_DATA_DIR/pacman/gnupg /etc/pacman.d/gnupg \ --bind $INSTANCE_PACMAN_GNUPG /etc/pacman.d/gnupg \
--bind $INSTANCE_CACHE_DIR /var/cache/pacman/pkg \ --bind $INSTANCE_PACMAN_CACHE /var/cache/pacman/pkg \
--dev /dev \ --dev /dev \
--proc /proc \ --proc /proc \
--tmpfs /tmp \ --tmpfs /tmp \
--ro-bind $INSTANCE_CONFIG_DIR/pacman/syncdb/pacman.$SANDBOX.conf /tmp/pacman.conf \ --ro-bind $(return_pacman_syncdb) /tmp/pacman.conf \
--ro-bind $INSTANCE_CONFIG_DIR/pacman.d/mirrorlist /etc/pacman.d/mirrorlist \ --ro-bind $(return_pacman_sync) /etc/pacman.conf \
--ro-bind $INSTANCE_PACMAN_MIRRORLIST /etc/pacman.d/mirrorlist \
--bind $LOG_FILE /tmp/pacman.log \ --bind $LOG_FILE /tmp/pacman.log \
--bind $INSTANCE_HOME $INSTANCE_HOME_MOUNT \ --bind $INSTANCE_HOME $INSTANCE_HOME_MOUNT \
--unshare-all \ --unshare-all \

View file

@ -18,17 +18,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
BOLD=$(tput bold) source pacwrap-common
RED=$(tput setaf 1)
GREEN=$(tput setaf 2) declare -A updated
CYAN=$(tput setaf 6) declare -A synced
YELLOW=$(tput setaf 11) declare -A syncreq
RESET=$(tput sgr0)
BAR="$RED$BOLD::$RESET$BOLD"
BAR_GREEN="$GREEN$BOLD::$RESET$BOLD"
ARROW="$CYAN$BOLD->$RESET"
ARROW_GREEN="$GREEN$BOLD->$RESET"
ARROW_RED="$RED$BOLD->$RESET"
EXEC_NAME="pacwrap-sync" EXEC_NAME="pacwrap-sync"
EXEC_SCRIPT="pacwrap-exec --root --exec" EXEC_SCRIPT="pacwrap-exec --root --exec"
@ -36,50 +30,58 @@ EXEC_SCRIPT="pacwrap-exec --root --exec"
LINKFILES=("bin" "lib" "lib32" "share") LINKFILES=("bin" "lib" "lib32" "share")
main () { main () {
parse_args $@ export PACWRAP_UTILS=1
trap abort INT
if [[ $SWITCH == v ]]; then
VER_DISPLAY=$EXEC_NAME pacwrap -v
exit
fi
local roots=() local roots=()
local rootdeps=() local rootdeps=()
local baserootdeps=() local baserootdeps=()
local syncroots=() local syncroots=()
local syncreq=
init_vars parse_args $@
init $PACWRAP_CREATE
[[ ! $SWITCH ]] && log_error "No switch provided." 1
log_to_file "Running '$RUNTIME_ARGS'"
if [[ $SWITCH == *g* ]]; then if [[ $SWITCH == *g* ]]; then
generate_pacman_conf ${ARGS[@]} log "$ARROW Configuring container..."
return local instance=${ARGS[0]}
check_root "pacman.conf generation"
sync_pacman_conf
exit
fi fi
[[ $SWITCH == *yyyy* ]] && SYNCREQ=1 if [[ $SYNC_UPGRADE ]] || ([[ $SYNC_UPDATE ]] && [[ ! $SYNC_FORCE ]]); then
log "$BAR_GREEN Starting container upgrade..."
if [[ $SWITCH == *u* ]] || ([[ $SWITCH == *y* ]] && [[ ! $SYNCREQ ]]); then [[ ! $SWITCH_NOCONFIRM ]] && echo -e $BAR "Update container \n\n${baserootdeps[@]} ${rootdeps[@]} ${roots[@]}\n"
log $BAR "Update container \n\n${baserootdeps[@]} ${rootdeps[@]} ${roots[@]}\n" [[ ! "$(query_confirm_Yn "Proceed with update?")" ]] && return
configure_containers ${baserootdeps[@]} ${rootdeps[@]}
[[ ! "$(query_confirm "Proceed with update?")" ]] && return
generate_pacman_conf ${baserootdeps[@]} ${rootdeps[@]}
update ${baserootdeps[@]} ${rootdeps[@]} update ${baserootdeps[@]} ${rootdeps[@]}
fi fi
if [[ $SWITCH == *yyy* ]] || [[ $SYNCREQ == 1 ]]; then [[ ${#roots[@]} > 0 ]] && [[ ! $SYNC_FORCE ]] &&
[[ $PACWRAP_CREATE ]] && syncroots=${roots[@]} log "$BAR_GREEN Starting contingent container upgrade..."
log $BAR_GREEN "Synchronizing container structures..."
generate_cache ${baserootdeps[@]}
update_links ${rootdeps[@]} ${syncroots[@]}
cleanup_cache ${rootdeps[@]} ${baserootdeps[@]}
[[ $SWITCH == *yyyy* ]] && return
fi
if [[ $SWITCH == *u* ]] || [[ $SWITCH == *y* ]]; then for b in ${baserootdeps[@]}; do
generate_pacman_conf ${roots[@]} [[ ${syncreq[$(return_base)]} ]] && syncreq=1 && break; done
if [[ $SYNC_OVERRIDE ]] || [[ $syncreq ]]; then
update_links ${syncroots[@]}
cleanup_cache ${rootdeps[@]} ${baserootdeps[@]}
[[ $SYNC_FORCE ]] && return
fi
[[ ${#roots[@]} -le 0 ]] && return
if [[ $SYNC_UPGRADE ]] || [[ $SYNC_UPDATE ]]; then
configure_containers ${roots[@]}
update ${roots[@]} update ${roots[@]}
if [[ -f /usr/bin/paccache ]]; then [[ -f /usr/bin/paccache ]] &&
paccache --cachedir $INSTANCE_CACHE_DIR -r -k 1 --min-mtime "14 days ago" paccache --cachedir $INSTANCE_PACMAN_CACHE -r -k 1 --min-mtime "14 days ago"
fi
fi fi
} }
@ -98,8 +100,8 @@ parse_args () {
--sync|-y) --sync|-y)
SWITCH=y$SWITCH SWITCH=y$SWITCH
;; ;;
-*) -S*)
SWITCH=$(echo $var | cut -c 2-) SWITCH=$(echo $var | cut -c 3-)
;; ;;
*) *)
ARGS+=("$var") ARGS+=("$var")
@ -108,60 +110,39 @@ parse_args () {
done done
} }
init_vars() { script_init () {
INSTANCE_DATA_DIR="$HOME/.local/share/pacwrap" [[ $SWITCH == *y* ]] && SYNC_UPDATE=1
INSTANCE_CACHE_DIR="$HOME/.cache/pacwrap/pkg" [[ $SWITCH == *u* ]] && SYNC_UPGRADE=1
INSTANCE_CONFIG_DIR="$HOME/.config/pacwrap" [[ $SWITCH == *yy* ]] && SYNC_FOREIGN=1
[[ $SWITCH == *yyy* ]] && SYNC_OVERRIDE=1
[[ $SWITCH == *yyyy* ]] && SYNC_FORCE=1
if [[ $PACWRAP_DEBUG ]]; then syncroots=(${roots[@]})
INSTANCE_DATA_DIR="$PACWRAP_DEBUG"
INSTANCE_CONFIG_DIR="$PACWRAP_DEBUG/cfg"
fi
VERBOSE="/dev/null"
[[ $SWITCH == *v* ]] && VERBOSE="/dev/stdout"
LOG_FILE="$INSTANCE_DATA_DIR/pacwrap.log"
OUTPUT_DEST=$LOG_FILE
INSTANCE_ROOT=$INSTANCE_DATA_DIR/root
INSTANCE_DB_ROOT=$INSTANCE_DATA_DIR/database
INSTANCE_CONFIG_ROOT=$INSTANCE_CONFIG_DIR/root
INSTANCE_PACMAN_SYNC=$INSTANCE_CONFIG_DIR/pacman/sync
INSTANCE_PACMAN_SYNCDB=$INSTANCE_CONFIG_DIR/pacman/syncdb
INSTANCE_PACMAN_TEMPLATE=$INSTANCE_CONFIG_DIR/pacman/template
if [[ ! -d $INSTANCE_ROOT ]]; then
echo $BOLD$RED"error:$RESET Sandbox root is either missing or an environmental variable is misconfigured.$RESET"
exit
fi
local rootlist=$(ls -U -1F $INSTANCE_ROOT | grep -i "/" | tr -d "/")
for sandbox in $rootlist; do
local type=$(return_type)
case $type in
BASE)
baserootdeps+=("$sandbox")
;;
DEP)
rootdeps+=("$sandbox")
;;
*)
roots+=("$sandbox")
;;
esac
done
syncroots=${roots[@]}
if [[ ${ARGS[@]} ]]; then if [[ ${ARGS[@]} ]]; then
roots=() roots=()
for sandbox in $ARGS; do for instance in $ARGS; do
check_root "container" check_root "container"
[[ $? == 1 ]] && continue [[ $? == 1 ]] && continue
roots+=("$sandbox") [[ $(return_type) != "ROOT" ]] &&
[[ ! $PACWRAP_CREATE ]] && continue
roots+=("$instance")
done done
fi fi
[[ $SYNC_FORCE ]] && syncroots+=(${rootdeps[@]})
if [[ $PACWRAP_CREATE ]]; then
local instance=${roots[0]}
syncroots=($instance)
for instance in $(return_dependencies); do
[[ $(return_type) == "BASE" ]] && continue
syncroots+=($instance)
done
fi
touch $LOCK_FILE
trap on_exit EXIT
} }
invoke_link_deletion() { invoke_link_deletion() {
@ -179,133 +160,161 @@ invoke_link_deletion() {
} }
invoke_update_link() { invoke_update_link() {
local sandbox=$1 local instance=$1
if [[ ${synced[$sandbox]} ]]; then
return
fi
if [[ $(return_type) == "BASE" ]]; then
return
fi
local linkfiles=${LINKFILES[@]} local linkfiles=${LINKFILES[@]}
local dep=$(return_dependency)
[[ ${synced[$instance]} ]] && return
if [[ $SWITCH != *f* ]]; then
for item in ${linkfiles[@]}; do if [[ $(return_type) == "BASE" ]]; then
local root=$INSTANCE_ROOT/$sandbox invoke_generate_cache
local link=$INSTANCE_DB_ROOT/$dep synced[$instance]=1
invoke_link_deletion return
# log_to_file "Deleted stray files from $dep/$item in $sandbox."
done
fi fi
local dep=$(return_dependency)
for item in ${linkfiles[@]}; do for item in ${linkfiles[@]}; do
local root=$INSTANCE_ROOT/$sandbox local root=$INSTANCE_ROOT_DIR/$instance
local source=$INSTANCE_ROOT/$dep local link=$INSTANCE_DB_DIR/$dep
invoke_link_deletion
#log_to_file "Deleted stray files from $dep/$item in $instance."
if [[ $amt ]]; then
((amt_done++))
progress_bar
fi
done
for item in ${linkfiles[@]}; do
local root=$INSTANCE_ROOT_DIR/$instance
local source=$INSTANCE_ROOT_DIR/$dep
local src=$source/usr/$item local src=$source/usr/$item
local dest=$root/usr/ local dest=$root/usr/
([[ ! -d $src ]] || [[ ! -d $dest ]]) && continue ([[ ! -d $src ]] || [[ ! -d $dest ]]) && ((amt_done++)) && continue
cp -flR $src $dest 2>>$OUTPUT_DEST 1>>$OUTPUT_DEST cp -flR $src $dest 2>>$OUTPUT_DEST 1>>$OUTPUT_DEST
log_to_file "Linked $sandbox/$item against $dep/$item." log_to_file "Linked $instance/$item against $dep/$item."
if [[ $amt ]]; then
((amt_done++))
progress_bar
fi
done done
if [[ $(return_type) == "DEP" ]]; then [[ $(return_type) == "DEP" ]] && invoke_generate_cache
invoke_generate_cache [[ $ABORT ]] && exit 1
fi
synced[$sandbox]=1 synced[$instance]=1
} }
update_links() { update_links() {
declare -A synced
local amt=${#@}
local amt_done=0 local amt_done=0
local amt=$((${#@} * 8))
local check_root="link synchronization"
SYNC=1
init_progress init_progress
set_progress_label " Linking structures" set_progress_label "Linking structures" $ARROW
for sandbox in "$@"; do for instance in "$@"; do check_root $check_root
((amt_done++))
progress_bar
check_root "link synchronization"
[[ $? == 1 ]] && continue [[ $? == 1 ]] && continue
for dep in $(return_dependencies); do local syncreq=$SYNC_OVERRIDE
check_root "link synchronization"
[[ $? == 1 ]] && continue for instance_dep in $(return_dependencies); do check_root $check_root
invoke_update_link $dep [[ $? == 1 ]] && break
[[ $syncreq ]] && invoke_update_link $instance_dep
[[ ${synced[$instance_dep]} ]] && syncreq=1
done done
invoke_update_link $sandbox
done; echo if [[ $syncreq ]]; then
invoke_update_link $instance
else
((amt_done+=8))
progress_bar
fi
done; SYNC= && echo
} }
cleanup_cache() { cleanup_cache() {
for sandbox in "$@"; do for instance in "$@"; do
check_root "cache file operation" check_root "cache file operation"
[[ $? == 1 ]] && continue [[ $? == 1 ]] && continue
local linkfiles=${LINKFILES[@]} local linkfiles=${LINKFILES[@]}
for item in ${linkfiles[@]}; do for item in ${linkfiles[@]}; do
[[ -f $INSTANCE_DB_ROOT/$sandbox/$item.old.zst ]] && [[ -f $INSTANCE_DB_DIR/$instance/$item.old.zst ]] &&
rm $INSTANCE_DB_ROOT/$sandbox/$item.old.zst 2>>$OUTPUT_DEST 1>>$OUTPUT_DEST rm $INSTANCE_DB_DIR/$instance/$item.old.zst 2>>$OUTPUT_DEST 1>>$OUTPUT_DEST
done done
done done
} }
invoke_generate_cache() { invoke_generate_cache() {
local dep=$sandbox local dep=$instance
local linkfiles=${LINKFILES[@]} local linkfiles=${LINKFILES[@]}
for item in ${linkfiles[@]}; do for item in ${linkfiles[@]}; do
local root=$INSTANCE_ROOT/$sandbox local root=$INSTANCE_ROOT_DIR/$instance
local link=$INSTANCE_DB_ROOT/$sandbox local link=$INSTANCE_DB_DIR/$instance
local rdir=$root/usr/ local rdir=$root/usr/
local tdir=$root/usr/$item local tdir=$root/usr/$item
if ([[ ! -d $root ]] || [[ ! -d $tdir ]]); then if ([[ ! -d $root ]] || [[ ! -d $tdir ]]); then
log $ARROW_RED$RESET "Root or link target for $BOLD$sandbox$RESET not found..." log_error "Root or link target for $BOLD$instance$RESET not found..."
log_to_file "Root or link target for $sandbox not found during synchronization operation."
return return
fi fi
[[ -f $link/$item.zst ]] && mv $link/$item.zst $link/$item.old.zst 2>>$OUTPUT_DEST 1>>$OUTPUT_DEST [[ -f $link/$item.zst ]] && mv $link/$item.zst $link/$item.old.zst 2>>$OUTPUT_DEST 1>>$OUTPUT_DEST
find $tdir -type f,d,l | sed -z "s,$rdir,,g" | zstd -fq -o $link/$item.zst find $tdir -type f,d,l | sed -z "s,$rdir,,g" | zstd -fq -o $link/$item.zst
done done
log_to_file "Generated link cache for $sandbox!" log_to_file "Generated link cache for $instance!"
}
generate_cache() {
for sandbox in "$@"; do
check_root "cache"
[[ $? == 1 ]] && continue
invoke_generate_cache
done
} }
check_root() { check_root() {
if [[ ! -d $INSTANCE_ROOT/$sandbox ]]; then if [[ ! -d $INSTANCE_ROOT_DIR/$instance ]]; then
log $ARROW_RED$RESET "Root for $BOLD$sandbox$RESET not found.$RESET\n Skipping $@..." log_error $ARROW_RED "Root for $BOLD$instance$RESET not found." "Skipping..."
return 1 return 1
fi fi
} }
invoke_vdb () { update_config() {
local root=$INSTANCE_ROOT/$sandbox declare -A skip
local dep=
local i=
local local_pkgs=$($EXEC_SCRIPT $instance pacman -Qqe)
local old_local_pkgs=$(return_packages)
local package_list=()
if [[ $(return_type) != "ROOT" ]]; then if [[ $(return_type) != "BASE" ]]; then
dep=$sandbox local pkgs=$($EXEC_SCRIPT $(return_dependency) pacman -Qqe)
else
dep=$(return_dependency) for pkg in $pkgs; do
skip[$pkg]=1
done
fi fi
declare -A skip for pkg in $local_pkgs; do
local template="cat $INSTANCE_PACMAN_TEMPLATE/pacman.$dep.conf" [[ ${skip[$pkg]} ]] && continue
package_list[i++]="$pkg"
done
local pacmanconf=$INSTANCE_PACMAN_SYNC/pacman.$sandbox.conf [[ "$(echo ${package_list[@]})" != "$(echo ${old_local_pkgs[@]})" ]] &&
generate_config ${package_list[@]}
((amt_done++))
progress_bar
}
sync_pacman_conf () {
local dep=$instance
[[ $(return_type) == "ROOT" ]] && dep=$(return_dependency)
declare -A skip
local template="cat $(return_pacman_template $dep)"
local pacmanconf=$(return_pacman_sync $instance)
local ignorepkg="## Start of automated configuration ##\n" local ignorepkg="## Start of automated configuration ##\n"
if [[ $(return_type) != "BASE" ]]; then if [[ $(return_type) != "BASE" ]]; then
@ -333,12 +342,18 @@ invoke_vdb () {
echo -e $header > $pacmanconf echo -e $header > $pacmanconf
$template | sed -z "s/###IGNOREPKG###/$ignorepkg/g" >> $pacmanconf $template | sed -z "s/###IGNOREPKG###/$ignorepkg/g" >> $pacmanconf
log_to_file "Generated $pacmanconf." log_to_file "Generated $pacmanconf."
invoke_vdb_update
if [[ $amt ]]; then
((amt_done++))
progress_bar
fi
syncdb_pacman_conf
} }
invoke_vdb_update() { syncdb_pacman_conf() {
local pacmanconf=$INSTANCE_PACMAN_SYNCDB/pacman.$sandbox.conf local pacmanconf=$(return_pacman_syncdb $instance)
local local_pkgs=$($EXEC_SCRIPT $sandbox pacman -Qq) local local_pkgs=$($EXEC_SCRIPT $instance pacman -Qq)
local ignorepkg="## Start of automated configuration ##\n" local ignorepkg="## Start of automated configuration ##\n"
if [[ $(return_type) != "BASE" ]]; then if [[ $(return_type) != "BASE" ]]; then
@ -352,145 +367,128 @@ invoke_vdb_update() {
echo -e $header > $pacmanconf echo -e $header > $pacmanconf
$template | sed -z "s/###IGNOREPKG###/$ignorepkg/g" >> $pacmanconf $template | sed -z "s/###IGNOREPKG###/$ignorepkg/g" >> $pacmanconf
log_to_file "Generated $pacmanconf." log_to_file "Generated $pacmanconf."
if [[ $amt ]]; then
((amt_done++))
progress_bar
fi
} }
invoke_update() { invoke_update() {
log_to_file "Checking $sandbox container for updates..." local instance=$1
log $BAR_GREEN "Checking $sandbox for updates..."
[[ ${updated[$instance]} ]] && return
updated[$instance]=1
log "$BAR_GREEN Checking $instance for updates..." \
"Checking $instance container for updates..."
local type=$(return_type); local type=$(return_type);
local syncdb=$(return_pacman_syncdb) local syncdb=$(return_sync_pacmandb)
if [[ $SWITCH == *y* ]] && ([[ $type == "BASE" ]] || [[ $type == "DEP" ]]); then if [[ $SYNC_UPDATE ]] && [[ $type != "ROOT" ]]; then
([[ ! $PMSYNCED ]] || ([[ $syncdb ]] && [[ $syncdb == 1 ]])) && if [[ ! $PMSYNCED ]] || [[ $syncdb == 1 ]]; then
$EXEC_SCRIPT $sandbox pacman --logfile /tmp/pacman.log -Sy $EXEC_SCRIPT $instance pacman --logfile /tmp/pacman.log -Sy
[[ $? != 0 ]] &&
log_error $ARROW_RED "Remote database synchronization failed." \
"Use pacwrap -Sv and examine stdout for details." 1
fi
PMSYNCED=1 PMSYNCED=1
fi fi
local result=$($EXEC_SCRIPT $sandbox pacman --color always -Qu | grep -v "ignored") local result=$($EXEC_SCRIPT $instance pacman --color always -Qu | grep -v "ignored")
if ([[ $SYNC_OVERRIDE ]] || [[ ${syncreq[$(return_base)]} ]]) &&
[[ $type != "ROOT" ]]; then
log "$ARROW Linking structures..."
invoke_update_link $instance
fi
if ([[ $SYNC_FOREIGN ]] || ([[ $result ]] && [[ $SYNC_UPGRADE ]])) &&
[[ $(return_type) != "BASE" ]]; then
log "$ARROW Synchronizing database against foreign packages..."
$EXEC_SCRIPT $instance pacman -Su \
--logfile /tmp/pacman.log \
--dbonly --noconfirm \
--config=/tmp/pacman.conf 2>/dev/null 1>$VERBOSE
[[ $? != 0 ]] &&
log_error $ARROW_RED "Foreign database synchronization failed." \
"Use pacwrap -Sv and examine stdout for details." 1
if ([[ $SWITCH == *yy* ]] || ([[ $result ]] && [[ $SWITCH == *u* ]])) && [[ $(return_type) != "BASE" ]]; then
log $ARROW "Synchronizing database against foreign packages..."
$EXEC_SCRIPT $sandbox pacman -Su --logfile /tmp/pacman.log --dbonly --noconfirm --config=/tmp/pacman.conf 2>/dev/null 1>$VERBOSE
log " Database synchronization complete!" log " Database synchronization complete!"
fi fi
if [[ ! $result ]]; then if [[ ! $result ]]; then
log "$ARROW Container $BOLD$sandbox$RESET is up to date!" log "$ARROW Container $BOLD$instance$RESET is up to date!" \
log_to_file "Database up-to-date for $sandbox." "Database up-to-date for $instance."
return return
fi fi
echo -e "$BAR Package changes: $RESET \n\n$result\n" echo -e "$BAR Package changes: $RESET \n\n$result\n"
[[ $SWITCH != *u* ]] && return
[[ ! $(query_confirm "Proceed with installation on $sandbox") ]] && return [[ ! $SYNC_UPGRADE ]] && return
log_to_file "Updating $sandbox container.." [[ ! $(query_confirm_Yn "Proceed with installation on $instance") ]] && return
SYNCREQ=1 log_to_file "Updating $instance container.."
$EXEC_SCRIPT $sandbox pacman -Su --logfile /tmp/pacman.log --noconfirm 2>/dev/null $EXEC_SCRIPT $instance pacman -Su \
log $ARROW_GREEN "Upgrade complete!" --logfile /tmp/pacman.log \
--noconfirm 2>/dev/null
[[ $? != 0 ]] &&
log_error $ARROW_RED "Upgrade for $instance failed." \
"Use pacwrap -Sv and examine stdout for details." 1
if [[ $(return_type) != "ROOT" ]]; then
log "$ARROW Generating link cache..."
invoke_generate_cache $instance
synced[$instance]=1
fi
syncreq[$(return_base)]=1
log "$ARROW_GREEN Upgrade complete!"
} }
update () { update () {
for sandbox in "$@"; do for instance in "$@"; do
check_root "update" check_root "update"
[[ $? == 1 ]] && continue [[ $? == 1 ]] && continue
invoke_update for dep in $(return_dependencies); do
invoke_update $dep
done
invoke_update $instance
done done
} }
print_progress_bar() {
printf "$PROGRESS_LABEL"
printf "%-*s" $(($2+1)) '[' | tr ' ' '#'
printf "%*s% 3d%%\r" $(($1-$2)) "]" "$3"
}
configure_containers () {
init_progress() { local amt=$((${#@}*3))
TTY_SIZE=($(stty size))
PROGRESS_LENGTH=$((${TTY_SIZE[1]}/2))
}
set_progress_label() {
PROGRESS_LABEL="$1"
local label_length=${#PROGRESS_LABEL}
local padding_length=$(($PROGRESS_LENGTH-$label_length-8))
for ((i=0; i<=padding_length; i+=1)); do
PROGRESS_LABEL+=" "
done
}
progress_bar() {
local bar_percent=$(($((PROGRESS_LENGTH*2))*$amt_done/$amt % 2 + $PROGRESS_LENGTH*$amt_done/$amt))
local percent=$((200*$amt_done/$amt % 2 + 100*$amt_done/$amt))
print_progress_bar $((PROGRESS_LENGTH+1)) $bar_percent $percent
}
generate_pacman_conf () {
log $BAR_GREEN "Generating pacman configuration..."
init_progress
set_progress_label " pacman.conf"
local amt=${#@}
local amt_done=0 local amt_done=0
for sandbox in "$@"; do init_progress
((amt_done++)) set_progress_label "Configuring containers" $ARROW
progress_bar
check_root "pacman.conf generation" for instance in "$@"; do check_root "pacman.conf generation"
[[ $? == 1 ]] && continue [[ $? == 1 ]] && continue
invoke_vdb sync_pacman_conf
done
for instance in "$@"; do check_root "configuration update"
[[ $? == 1 ]] && continue
update_config
done; echo done; echo
} }
return_pacman_syncdb() { abort () {
source $INSTANCE_CONFIG_ROOT/$sandbox echo
echo $SYNC_PACMANDB ABORT=1
[[ ! $SYNC ]] && exit
} }
return_type() { on_exit() {
source $INSTANCE_CONFIG_ROOT/$sandbox [[ ! $PACWRAP_CREATE ]] && [[ -f $LOCK_FILE ]] && rm $LOCK_FILE
echo $TYPE
}
return_dependencies() {
source $INSTANCE_CONFIG_ROOT/$sandbox
echo ${DEPS[@]}
}
return_base() {
source $INSTANCE_CONFIG_ROOT/$sandbox
echo ${DEPS[0]}
}
return_dependency() {
source $INSTANCE_CONFIG_ROOT/$sandbox
echo ${DEPS[$((${#DEPS[@]} - 1))]}
}
log () {
echo -e "$@ $RESET"
}
log_to_file() {
echo -e "[$(date '+%FT%H:%M:%S%z')] [$EXEC_NAME] $@" >>$LOG_FILE
}
query_confirm () {
if [[ $SWITCH == *n* ]]; then
echo 1
return
fi
read -rp "$BAR $@ [Y/n]$RESET " input
if [[ "$input" != "Y" ]] &&
[[ "$input" != "y" ]] &&
[[ "$input" != "" ]]; then
return
fi
echo 1
} }
main $@ main $@

View file

@ -18,45 +18,29 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
BOLD=$(tput bold) source pacwrap-common
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
CYAN=$(tput setaf 6)
YELLOW=$(tput setaf 11)
RESET=$(tput sgr0)
UNDERLINE=$(tput smul)
NUMFMT="numfmt --to=si --suffix=B --round=nearest --from-unit=1000 --format=%2f "
BAR="$RED$BOLD::$RESET$BOLD"
BAR_GREEN="$GREEN$BOLD::$RESET$BOLD"
ARROW="$CYAN$BOLD->$RESET"
ARROW_GREEN="$GREEN$BOLD->$RESET"
ARROW_RED="$RED$BOLD->$RESET"
EXEC_NAME="pacwrap-utils" EXEC_NAME="pacwrap-utils"
EXEC_SCRIPT="pacwrap-exec" NUMFMT="numfmt --to=si --suffix=B --round=nearest --from-unit=1000 --format=%2f "
main () { main () {
parse_args $@ export PACWRAP_UTILS=1
trap exit INT
if [[ $SWITCH == v ]]; then local roots=()
VER_DISPLAY=$EXEC_NAME pacwrap -v local rootdeps=()
exit local baserootdeps=()
fi local syncroots=()
local roots=;
local rootdeps=
local baserootdeps=
local links= local links=
local total=0
init_vars parse_args $@
init
if [[ ! -d $INSTANCE_ROOT ]] && [[ $SWITCH != i* ]]; then [[ $SWITCH != h* ]] &&
echo $BOLD$RED"error:$RESET Sandbox root is either missing or an environmental variable is misconfigured.$RESET" [[ $SWITCH != v ]] &&
exit [[ $SWITCH != ls* ]] &&
fi [[ $SWITCH != v* ]] &&
log_to_file "Running '$RUNTIME_ARGS'"
case $SWITCH in case $SWITCH in
c*) c*)
@ -64,10 +48,12 @@ main () {
paccache --cachedir $INSTANCE_CACHE_DIR ${ARGS[@]} paccache --cachedir $INSTANCE_CACHE_DIR ${ARGS[@]}
fi fi
;; ;;
i*) i*)
initialize_data_directory initialize_data_directory
;; ;;
r*)
replicate ${baserootdeps[@]} ${rootdeps[@]} ${roots[@]}
;;
v*) v*)
edit_file 1 edit_file 1
;; ;;
@ -81,8 +67,8 @@ main () {
link_root link_root
;; ;;
d*) d*)
[[ $SWITCH == *r* ]] && delete_root [[ $SWITCH == *r* ]] && delete "root" "$INSTANCE_ROOT_DIR"
[[ $SWITCH == *h* ]] && delete_home [[ $SWITCH == *h* ]] && delete "home" "$INSTANCE_HOME_DIR"
;; ;;
h*) h*)
pacwrap --help=utils pacwrap --help=utils
@ -95,12 +81,26 @@ main () {
fi fi
echo "Try '$EXEC_NAME -h' for more information on valid operational parameters." echo "Try '$EXEC_NAME -h' for more information on valid operational parameters."
;; ;;
esac esac
} }
summary() { script_init () {
[[ $REPLICATION_CONFIG_DIR ]] &&
INSTANCE_CONFIG_DIR=$REPLICATION_CONFIG_DIR
local linklist=$($list 2>/dev/null | grep -i "@" | tr -d "@")
for f in $linklist; do
links+=($f)
done
touch $LOCK_FILE
trap on_exit EXIT
}
summary() {
declare -A size declare -A size
local total=0
parse_du parse_du
@ -111,8 +111,7 @@ summary() {
rm $tmpfile rm $tmpfile
fi fi
if [[ $SWITCH == *d* ]]; then if [[ $SWITCH == *d* ]]; then
local actual_size=${size["root"]} local actual_size=${size["root"]}
local diff=$(($total - $actual_size)) local diff=$(($total - $actual_size))
@ -140,6 +139,9 @@ parse_args () {
-U*) -U*)
SWITCH=$(echo $var | cut -c 3-) SWITCH=$(echo $var | cut -c 3-)
;; ;;
--cfg-dir=*)
REPLICATION_CONFIG_DIR=$(echo $var | cut -c 11-)
;;
*) *)
ARGS+=("$var") ARGS+=("$var")
;; ;;
@ -147,148 +149,190 @@ parse_args () {
done done
} }
init_vars() { replicate() {
INSTANCE_DATA_DIR="$HOME/.local/share/pacwrap" local instances=()
INSTANCE_CACHE_DIR="$HOME/.cache/pacwrap/pkg" local baserootdeps=
INSTANCE_CONFIG_DIR="$HOME/.config/pacwrap" local rootdeps=
local roots=
if [[ $PACWRAP_DEBUG ]]; then if [[ $SWITCH == *t* ]]; then
INSTANCE_DATA_DIR="$PACWRAP_DEBUG" local default=$([[ ${#ARGS[@]} -le 1 ]] && echo 1)
INSTANCE_CONFIG_DIR="$PACWRAP_DEBUG/cfg" local tar_archive=${ARGS[0]}
fi
INSTANCE_ROOT=$INSTANCE_DATA_DIR/root [[ ! $tar_archive ]] && log_error $LOG_ERR_HELP "tar not specified." 1
INSTANCE_HOME=$INSTANCE_DATA_DIR/home [[ ! -f $tar_archive ]] && log_error "tar archive $tar_archive not found." 1
INSTANCE_DB_ROOT=$INSTANCE_DATA_DIR/database
INSTANCE_CONFIG_ROOT=$INSTANCE_CONFIG_DIR/root
INSTANCE_PACMAND=$INSTANCE_CONFIG_DIR/etc/pacman.d
local list="ls -U -1F $INSTANCE_ROOT"
local rootlist=$($list | grep -i "/" | tr -d "/")
local linklist=$($list | grep -i "@" | tr -d "@")
for instance in $rootlist; do
local type=$(return_type)
case $type in
BASE)
baserootdeps+=($instance)
;;
DEP)
rootdeps+=($instance)
;;
*)
roots+=($instance)
;;
esac
done
for f in $linklist; do
links+=($f)
done
}
delete_home() { local tar_list=($(tar tf ${ARGS[0]}))
local instances=${ARGS[@]};
if [[ ! $instances ]]; then
printf "%s\n%s\n" "$EXEC_NAME: instance not specified." \
"Try '$EXEC_NAME -h' for more information on valid operational parameters."
return
fi
printf "%s\n\n$BOLD%s\n\n$RESET" "$BAR Delete home" "$instances"
if [[ "$(query_confirm "Proceed with deletion?")" ]]; then
for instance in $instances; do for file in ${tar_list[@]}; do
if [[ ! -d $INSTANCE_HOME/$instance ]]; then local instance=${file##*/}
echo "$RED${BOLD}error:$RESET Home $instance does not exist." local this=$default
continue [[ ! $this ]] && for instance_list in ${ARGS[@]}; do
fi [[ "$instance" == "$instance_list" ]] && this=1 && break; done
[[ ! $instance ]] || [[ ! $this ]] && continue
rm -rf $INSTANCE_HOME/$instance eval $(tar axfO $tar_archive $file | grep -v "#")
printf "$ARROW_GREEN %s$RESET\n" "Deletion of $instance's home complete!" populate_configuration
populate_container_array
instances+=($instance)
done
else
instances=(${ARGS[@]})
[[ ${#ARGS[@]} -le 0 ]] && instances=$@
[[ ! $instances ]] && log_error $LOG_ERR_HELP "Instance not specified." 1
for instance in ${instances[@]}; do
[[ ! -f $INSTANCE_CONFIG_DIR/$instance ]] && continue
source_configuration
populate_configuration
populate_container_array
done done
fi fi
[[ ! $SWITCH_NOCONFIRM ]] && echo -e "$BAR Replicate \n\n${instances[@]}\n"
[[ ! "$(query_confirm_yN "Proceed with replication?")" ]] && return
invoke_replication ${baserootdeps[@]} ${rootdeps[@]} ${roots[@]}
} }
delete_root() { invoke_replication() {
local instances=${ARGS[@]}; if [[ $SWITCH = *d* ]]; then
printf "$BAR_GREEN %s$RESET\n" "Deleting root instances..."
if [[ ! $instances ]]; then for instance in $@; do
printf "%s\n%s\n" "$EXEC_NAME: instance not specified." \ [[ ! -d $INSTANCE_ROOT_DIR/$instance ]] && continue
"Try '$EXEC_NAME -h' for more information on valid operational parameters." rm -rf $INSTANCE_ROOT_DIR/$instance
return printf "$ARROW_GREEN %s$RESET\n" "Deletion of $instance's root complete!"
log_to_file "Deleted root $instance."
done
fi fi
printf "%s\n\n$BOLD%s\n\n$RESET" "$BAR Delete instance" "$instances" printf "$BAR_GREEN %s$RESET\n" "Replication of root filesystems from configuration..."
if [[ "$(query_confirm "Proceed with deletion?")" ]]; then declare -A completed
for instance in $instances; do for instance in $@; do
if [[ ! -d $INSTANCE_ROOT/$instance ]]; then local deps_return=$(return_dependencies)
echo "$RED${BOLD}error:$RESET $instance does not exist." local deps=()
continue
fi for dep in ${baserootdeps[@]}; do
rm -rf $INSTANCE_ROOT/$instance for depr in $deps_return; do
[[ $dep == $depr ]] && deps+=($dep)
printf "$ARROW_GREEN %s$RESET\n" "Deletion of $instance complete!" done
done
for dep in ${rootdeps[@]}; do
for depr in $deps_return; do
[[ $dep == $depr ]] && deps+=($dep)
done
done done
for dep in ${deps[@]}; do
replicate_instance $dep
done
for depr in $deps_return; do
if [[ ! -d $INSTANCE_ROOT_DIR/$depr ]]; then
log_error $LOG_ERR_HELP "Dependency $depr not fulfilled for $instance."
fi
done
replicate_instance $instance
done
}
replicate_instance() {
local instance=$1
[[ ${completed[$instance]} ]] && return
log_to_file "Beginning replication of $instance."
local type=$(return_type)
local params="-Cn"
[[ $SWITCH == *v* ]] && params+="v"
case $type in
BASE)
params+="b"
;;
DEP)
params+="d --dep=$(return_dependency)"
;;
ROOT)
params+=" --dep=$(return_dependency)"
;;
esac
$CREATE_SCRIPT $params $instance $(return_packages)
if [[ $? == 0 ]]; then
printf " %s$RESET\n" "Replication of $BOLD$instance$RESET complete!"
log_to_file "Replication of $instance complete!"
fi fi
completed[$instance]=1
}
delete() {
local instances=${ARGS[@]};
[[ ! $instances ]] && log_error $LOG_ERR_HELP "Instance not specified."
[[ ! $SWITCH_NOCONFIRM ]] && 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
} }
link_root() { link_root() {
local instance=${ARGS[1]}; local instance=${ARGS[0]};
local link=${ARGS[0]}; local link=${ARGS[1]};
if [[ ! $instance ]] || [[ ! $link ]]; then [[ ! $instance ]] && log_error $LOG_ERR_HELP "Instance not specified."
printf "%s\n%s\n" "$EXEC_NAME: instance or link not specified." \ [[ ! $link ]] && log_error $LOG_ERR_HELP "Link not specified."
"Try '$EXEC_NAME -h' for more information on valid operational parameters." [[ -d $INSTANCE_ROOT_DIR/$instance ]] && log_error "$instance already exists." 1
return [[ ! -d $INSTANCE_ROOT_DIR/$link ]] && log_error "Instance $link doesn't exist." 1
fi
if [[ -d $INSTANCE_ROOT/$instance ]]; then [[ ! $SWITCH_NOCONFIRM ]] && printf "$BAR%s\n\n$BOLD%s\n\n" "Create virtual instance" "$instance $ARROW $BOLD$link"
echo "$RED${BOLD}error:$RESET $instance already exists." [[ ! "$(query_confirm_Yn "Proceed with creation?")" ]] && return
return
fi
if [[ ! -d $INSTANCE_ROOT/$link ]]; then log "$BAR_GREEN Creating virtual instance $instance $ARROW $BOLD$link"
echo "$RED${BOLD}error:$RESET $link doesn't exist."
return
fi
printf "%s\n\n$BOLD%s\n\n" "$BAR Create virtual instance" "$instance $ARROW $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
if [[ "$(query_confirm "Proceed with creation?")" ]]; then log "$ARROW_GREEN Process complete!" \
ln -s $INSTANCE_ROOT/$link $INSTANCE_ROOT/$instance "Created $instance -> $link"
mkdir -p $INSTANCE_HOME/$instance
echo 'PS1="'$instance'> "' > $INSTANCE_HOME/$instance/.bashrc
printf "$BAR_GREEN %s$RESET\n" "Creation of virtual instance $instance complete!"
fi
} }
parse_du() { parse_du() {
for string in $(du -d 1 $INSTANCE_ROOT | tr ' ' '-'); do for string in $(du -d 1 $INSTANCE_ROOT_DIR | tr ' ' '-'); do
local bytes=${string%%-*} local bytes=${string%%-*}
local instance=${string##*/} local instance=${string##*/}
size["$instance"]=$bytes size["$instance"]=$bytes
if [[ $SWITCH == *dd* ]] && [[ $SWITCH != *dddd* ]]; then if [[ $SWITCH == *dd* ]] && [[ $SWITCH != *dddd* ]]; then
[[ $instance == "root" ]] && continue [[ $instance == "root" ]] && continue
local dh=$(du -d 1 $INSTANCE_ROOT/$instance | tail -n-1) local dh=$(du -d 1 $INSTANCE_ROOT_DIR/$instance | tail -n-1)
bytes=${dh% *} bytes=${dh% *}
size["$instance"]=$bytes size["$instance"]=$bytes
fi fi
[[ $instance == "root" ]] && continue [[ $instance == "root" ]] && continue
((total+=bytes)) ((total+=bytes))
@ -305,7 +349,7 @@ list_root() {
local bytes=0 local bytes=0
if [[ ! ${size["$instance"]} ]]; then if [[ ! ${size["$instance"]} ]]; then
dep="$(ls -l $INSTANCE_ROOT/$instance | sed -z "s,$INSTANCE_ROOT/,,g")" dep="$(ls -l $INSTANCE_ROOT_DIR/$instance | sed -z "s,$INSTANCE_ROOT_DIR/,,g")"
type="LINK" type="LINK"
else else
bytes=${size["$instance"]} bytes=${size["$instance"]}
@ -334,10 +378,8 @@ edit_file() {
EDITOR="vi" EDITOR="vi"
fi fi
if [[ ! -f $EDITOR ]]; then [[ ! -f $(type -P $EDITOR) ]] &&
printf "%s%s\n" "$RED$BOLD" "error:$RESET \$EDITOR was not found at $EDITOR." log_error "\$EDITOR was not found at $EDITOR." 1
exit
fi
[[ $1 ]] && dissolve=1 [[ $1 ]] && dissolve=1
@ -345,37 +387,37 @@ edit_file() {
case $SWITCH in case $SWITCH in
*c*) *c*)
file="$INSTANCE_CONFIG_ROOT/$instance" file="$INSTANCE_CONFIG_DIR/$instance"
error="config file" error="config file"
;; ;;
*b*) *b*)
file="$INSTANCE_CONFIG_DIR/bwrap/$instance.sh" file="$PACWRAP_CONFIG_DIR/bwrap/$instance.sh"
error="bubblewrap script $file" error="bubblewrap script $file"
create=1 create=1
;; ;;
*ips*) *ips*)
file="$INSTANCE_CONFIG_DIR/pacman/syncdb/pacman.$instance.conf" file=$(return_pacman_syncdb $instance)
error="instanced pacman $file" error="instanced pacman $file"
printf "##\n## %s\n" "For the purposes of overview or diagnostics only." > $tmpfile printf "##\n## %s\n" "For the purposes of overview or diagnostics only." > $tmpfile
dissolve=1 dissolve=1
;; ;;
*ip*) *ip*)
file="$INSTANCE_CONFIG_DIR/pacman/sync/pacman.$instance.conf" file=$(return_pacman_sync $instance)
error="instanced template $file" error="instanced template $file"
printf "##\n## %s\n" "For the purposes of overview or diagnostics only." > $tmpfile printf "##\n## %s\n" "For the purposes of overview or diagnostics only." > $tmpfile
dissolve=1 dissolve=1
;; ;;
*l*) *l*)
file="$INSTANCE_DATA_DIR/pacwrap.log" file="$PACWRAP_DATA_DIR/pacwrap.log"
error="$file" error="$file"
dissolve=1 dissolve=1
;; ;;
*pt*) *pt*)
file="$INSTANCE_CONFIG_DIR/pacman/template/pacman.$instance.conf" file=$(return_pacman_template $instance)
error="pacman template $file" error="pacman template $file"
;; ;;
*p*) *p*)
file="$INSTANCE_CONFIG_DIR/pacman/pacman.conf" file="$INSTANCE_PACMAN_CFG_DIR/pacman.conf"
error="default pacman.conf" error="default pacman.conf"
;; ;;
*) *)
@ -392,7 +434,6 @@ edit_file() {
return return
fi fi
#sed -r "s/(\x1B\^O|\x1B\[[0-9;]*[JKmsu])//g"
cat $file >> $tmpfile cat $file >> $tmpfile
$EDITOR $tmpfile $EDITOR $tmpfile
@ -404,65 +445,20 @@ edit_file() {
if [[ "${tmp_sum% *}" != "${config_sum% *}" ]]; then if [[ "${tmp_sum% *}" != "${config_sum% *}" ]]; then
cp $tmpfile $file cp $tmpfile $file
log "$ARROW_GREEN Changes written." \
"Changes written to $file."
else else
echo "$BAR_GREEN No changes made.$RESET" log "$BAR_GREEN No changes made"
fi fi
else else
echo "$BAR_GREEN No changes made.$RESET" log "$BAR_GREEN No changes made."
fi fi
rm $tmpfile rm $tmpfile
} }
initialize_data_directory() { on_exit () {
mkdir -v -p $INSTANCE_DATA_DIR/root \ [[ -f $LOCK_FILE ]] && rm $LOCK_FILE
$INSTANCE_DATA_DIR/home \
$INSTANCE_DATA_DIR/database \
$INSTANCE_DATA_DIR/pacman/sync \
$INSTANCE_DATA_DIR/pacman/gnupg \
$INSTANCE_CACHE_DIR \
$INSTANCE_CONFIG_DIR/root \
$INSTANCE_CONFIG_DIR/pacman.d \
$INSTANCE_CONFIG_DIR/pacman/sync \
$INSTANCE_CONFIG_DIR/pacman/syncdb \
$INSTANCE_CONFIG_DIR/pacman/template \
$INSTANCE_CONFIG_DIR/bwrap
local ins_string="\[options\]\n"
local template="\n###IGNOREPKG###\n\n"
local install="\nNoExtract = usr/lib/*\nNoExtract = usr/lib32/*\nNoExtract = usr/bin/*\nNoExtract = usr/share/*\n\n"
([[ ! -f $INSTANCE_CONFIG_DIR/pacman.d/mirrorlist ]] || [[ $SWITCH == *m* ]]) && cp /etc/pacman.d/mirrorlist $INSTANCE_CONFIG_DIR/pacman.d/mirrorlist
[[ ! -f $INSTANCE_CONFIG_DIR/pacman/pacman.conf ]] && cp /etc/pacman.conf $INSTANCE_CONFIG_DIR/pacman/pacman.conf
cat $INSTANCE_CONFIG_DIR/pacman/pacman.conf | sed -z "s,$ins_string,$ins_string$template,g" > $INSTANCE_CONFIG_DIR/pacman/pacman.template.conf
cat $INSTANCE_CONFIG_DIR/pacman/pacman.conf | sed -z "s,$ins_string,$ins_string$install,g" > $INSTANCE_CONFIG_DIR/pacman/pacman.install.conf
}
return_type() {
source $INSTANCE_CONFIG_ROOT/$instance
echo $TYPE
}
return_dependency() {
source $INSTANCE_CONFIG_ROOT/$instance
echo ${DEPS[$((${#DEPS[@]} - 1))]}
}
query_confirm () {
if [[ $SWITCH == *n* ]]; then
echo 1
return
fi
read -rp "$BAR $@ [y/N]$RESET " input
if [[ "$input" != "y" ]] &&
[[ "$input" != "Y" ]] &&
([[ "$input" == "" ]] ||
[[ "$input" != "" ]]); then
return
fi
echo 1
} }
main $@ main $@