toolbox/toolbox
Debarshi Ray 39806d9269 Drop the prefix from spinner messages
The prefixed spinner messages look odd because neither the download
confirmation prompts nor the hints on how to enter a container have
them. It's better to only prefix the debug and error messages so as to
disambiguate their origins.

https://github.com/debarshiray/toolbox/pull/164
2019-05-17 14:33:39 +02:00

1788 lines
54 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/sh
#
# Copyright © 2018 2019 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
exec 3>/dev/null
arguments=""
assume_yes=false
base_toolbox_command=$(basename "$0" 2>&3)
base_toolbox_image=""
# Based on the nameRegex value in:
# https://github.com/containers/libpod/blob/master/libpod/options.go
container_name_regexp="[a-zA-Z0-9][a-zA-Z0-9_.-]*"
environment=$(set)
environment_variables="COLORTERM \
DBUS_SESSION_BUS_ADDRESS \
DBUS_SYSTEM_BUS_ADDRESS \
DESKTOP_SESSION \
DISPLAY \
LANG \
SHELL \
SSH_AUTH_SOCK \
TERM \
TOOLBOX_PATH \
VTE_VERSION \
WAYLAND_DISPLAY \
XDG_CURRENT_DESKTOP \
XDG_DATA_DIRS \
XDG_MENU_PREFIX \
XDG_RUNTIME_DIR \
XDG_SEAT \
XDG_SESSION_DESKTOP \
XDG_SESSION_ID \
XDG_SESSION_TYPE \
XDG_VTNR"
fgc=""
prefix_sudo=""
registry="registry.fedoraproject.org"
registry_candidate="candidate-registry.fedoraproject.org"
release=""
release_default=""
spinner_animation="[>----] [=>---] [==>--] [===>-] [====>] [----<] [---<=] [--<==] [-<===] [<====]"
spinner_template="toolbox-spinner-XXXXXXXXXX"
tab="$(printf '\t')"
toolbox_command_path=""
toolbox_container=""
toolbox_container_default=""
toolbox_container_old_v1=""
toolbox_container_old_v2=""
toolbox_container_prefix_default=""
toolbox_image=""
user_id_real=$(id -ru 2>&3)
verbose=false
LGC='\033[1;32m' # Light Green Color
LBC='\033[1;34m' # Light Blue Color
NC='\033[0m' # No Color
has_prefix()
(
str="$1"
prefix="$2"
ret_val=1
case "$str" in
"$prefix"* )
ret_val=0
;;
* )
ret_val=1
;;
esac
return $ret_val
)
has_substring()
(
haystack="$1"
needle="$2"
ret_val=1
case "$haystack" in
*"$needle"* )
ret_val=0
;;
* )
ret_val=1
;;
esac
return $ret_val
)
is_integer()
{
[ "$1" != "" ] && [ "$1" -eq "$1" ] 2>&3
return $?
}
save_positional_parameters()
{
for i; do
printf "%s\\n" "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" 2>&3
done
echo " "
}
spinner_start()
(
directory="$1"
message="$2"
if $verbose; then
rm --force --recursive "$directory" 2>&3
return 0
fi
if ! touch "$directory/spinner-start" 2>&3; then
echo "$base_toolbox_command: unable to start spinner: spinner start file couldn't be created" >&2
return 1
fi
printf "%s" "$message"
tput civis 2>&3
exec 4>"$directory/spinner-start"
if ! flock 4 2>&3; then
echo "$base_toolbox_command: unable to start spinner: spinner lock couldn't be acquired" >&2
return 1
fi
(
while [ -f "$directory/spinner-start" ]; do
echo "$spinner_animation" | sed "s/ /\n/g" 2>&3 | while read -r frame; do
if ! [ -f "$directory/spinner-start" ] 2>&3; then
break
fi
printf "%s" "$frame"
frame_len=${#frame}
i=0
while [ "$i" -lt "$frame_len" ]; do
printf "\b"
i=$((i + 1))
done
sleep 1
done
done
printf "\033[2K" # delete entire line regardless of cursor position
printf "\r"
tput cnorm 2>&3
) &
return 0
)
spinner_stop()
(
$verbose && return
directory="$1"
exec 4>"$directory/spinner-start"
if ! rm "$directory/spinner-start" 2>&3; then
echo "$base_toolbox_command: unable to stop spinner: spinner start file couldn't be removed" >&2
return
fi
if ! flock 4 2>&3; then
echo "$base_toolbox_command: unable to stop spinner: spinner lock couldn't be acquired" >&2
return
fi
rm --force --recursive "$directory" 2>&3
)
ask_for_confirmation()
(
default_response="$1"
prompt="$2"
ret_val=0
while :; do
printf "%s " "$prompt"
read -r user_response
if [ "$user_response" = "" ] 2>&3; then
user_response="$default_response"
else
user_response=$(echo "$user_response" | tr "[:upper:]" "[:lower:]" 2>&3)
fi
if [ "$user_response" = "no" ] 2>&3 || [ "$user_response" = "n" ] 2>&3; then
ret_val=1
break
elif [ "$user_response" = "yes" ] 2>&3 || [ "$user_response" = "y" ] 2>&3; then
ret_val=0
break
fi
done
return "$ret_val"
)
container_name_is_valid()
(
name="$1"
echo "$name" | grep "^$container_name_regexp$" >/dev/null 2>&3
return $?
)
copy_etc_profile_d_toolbox_to_container()
(
container="$1"
if ! [ -f /etc/profile.d/toolbox.sh ] 2>&3; then
return 0
fi
# shellcheck disable=SC2174
if ! mkdir --mode 700 --parents "$XDG_RUNTIME_DIR"/toolbox 2>&3; then
echo "$base_toolbox_command: unable to copy /etc/profile.d/toolbox.sh: runtime directory not created" >&2
return 1
fi
exec 5>"$XDG_RUNTIME_DIR"/toolbox/profile.d-toolbox.lock
if ! flock 5 2>&3; then
echo "$base_toolbox_command: unable to copy /etc/profile.d/toolbox.sh: copy lock not acquired" >&2
return 1
fi
echo "$base_toolbox_command: looking for /etc/profile.d/toolbox.sh in container $toolbox_container" >&3
if $prefix_sudo podman exec \
--user "$USER" \
"$container" \
sh -c 'mount | grep /etc/profile.d/toolbox.sh >/dev/null 2>/dev/null' 2>&3; then
echo "$base_toolbox_command: /etc/profile.d/toolbox.sh already mounted in container $toolbox_container" >&3
return 0
fi
echo "$base_toolbox_command: copying /etc/profile.d/toolbox.sh to container $toolbox_container" >&3
if ! $prefix_sudo podman cp /etc/profile.d/toolbox.sh "$toolbox_container":/etc/profile.d 2>&3; then
echo "$base_toolbox_command: unable to copy /etc/profile.d/toolbox.sh to container $toolbox_container" >&2
return 1
fi
if ! $prefix_sudo podman exec \
--user root:root \
"$toolbox_container" \
chown root:root /etc/profile.d/toolbox.sh 2>&3; then
echo "$base_toolbox_command: unable to chown /etc/profile.d/toolbox.sh in container $toolbox_container" >&2
return 1
fi
return 0
)
create_enter_command()
(
container="$1"
if [ "$container" = "$toolbox_container_default" ] 2>&3; then
echo "$base_toolbox_command enter"
elif [ "$container" = "$toolbox_container_prefix_default-$release" ] 2>&3; then
echo "$base_toolbox_command enter --release $release"
else
echo "$base_toolbox_command enter --container $container"
fi
)
create_environment_options()
{
echo "$environment_variables" \
| sed "s/ \+/\n/g" 2>&3 \
| {
environment_options=""
echo "$base_toolbox_command: creating list of environment variables to forward" >&3
value=""
while read -r variable; do
if echo "$environment" | grep "^$variable" >/dev/null 2>&3; then
eval value="$""$variable"
echo "$base_toolbox_command: $variable=$value" >&3
environment_options="$environment_options --env=$variable=$value"
else
echo "$base_toolbox_command: $variable is unset" >&3
fi
done
environment_options=${environment_options#" "}
echo "$base_toolbox_command: created options for environment variables to forward" >&3
echo "$environment_options" >&3
echo "$environment_options"
}
}
create_toolbox_container_name()
(
image="$1"
basename=$(image_reference_get_basename "$image")
if [ "$basename" = "" ] 2>&3; then
return 100
fi
tag=$(image_reference_get_tag "$image")
if [ "$tag" = "" ] 2>&3; then
return 101
fi
echo "$basename-$tag"
return 0
)
create_toolbox_image_name()
(
# Based on the ResolveName function implemented in:
# https://github.com/containers/buildah/blob/master/util/util.go
if image_reference_can_be_id "$base_toolbox_image"; then
if base_toolbox_image_id=$($prefix_sudo podman inspect \
--format "{{.Id}}" \
--type image \
"$base_toolbox_image" 2>&3); then
if has_prefix "$base_toolbox_image_id" "$base_toolbox_image"; then
echo "$base_toolbox_image-$USER:latest"
return 0
fi
fi
fi
basename=$(image_reference_get_basename "$base_toolbox_image")
if [ "$basename" = "" ] 2>&3; then
return 100
fi
tag=$(image_reference_get_tag "$base_toolbox_image")
if [ "$tag" = "" ] 2>&3; then
echo "$basename-$USER:latest"
else
echo "$basename-$USER:$tag"
fi
return 0
)
enter_print_container_not_found()
(
container="$1"
echo "$base_toolbox_command: container $container not found" >&2
echo "Use the 'create' command to create a toolbox." >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
)
get_host_id()
(
# shellcheck disable=SC1091
. /etc/os-release
echo "$ID"
)
get_host_version_id()
(
# shellcheck disable=SC1091
. /etc/os-release
echo "$VERSION_ID"
)
image_reference_can_be_id()
(
image="$1"
echo "$image" | grep "^[a-f0-9]\{6,64\}$" >/dev/null 2>&3
return $?
)
image_reference_get_basename()
(
image="$1"
domain=$(image_reference_get_domain "$image")
remainder=${image#$domain}
path=${remainder%:*}
basename=${path##*/}
echo "$basename"
)
image_reference_get_domain()
(
image="$1"
image_reference_has_domain "$image" && domain=${image%%/*}
echo "$domain"
)
image_reference_get_tag()
(
image="$1"
domain=$(image_reference_get_domain "$image")
remainder=${image#$domain}
tag=""
if (echo "$remainder" | grep ":" >/dev/null 2>&3); then
tag=${remainder#*:}
fi
echo "$tag"
)
image_reference_has_domain()
(
# Based on the splitDockerDomain function implemented in:
# https://github.com/docker/distribution/blob/master/reference/normalize.go
image="$1"
if ! (echo "$image" | grep "/" >/dev/null 2>&3); then
return 1
fi
prefix=${image%%/*}
if ! (echo "$prefix" | grep "[.:]" >/dev/null 2>&3) && [ "$prefix" != "localhost" ] 2>&3; then
return 1
fi
return 0
)
images_get_details()
(
images="$1"
if ! echo "$images" | while read -r image; do
[ "$image" = "" ] 2>&3 && continue
if ! $prefix_sudo podman images \
--format "{{.ID}} {{.Repository}}:{{.Tag}} {{.Created}}" \
--noheading \
"$image" 2>&3; then
echo "$base_toolbox_command: failed to get details for image $image" >&2
return 1
fi
done; then
return 1
fi
return 0
)
list_container_names()
(
if ! containers_old=$($prefix_sudo podman ps \
--all \
--filter "label=com.redhat.component=fedora-toolbox" \
--format "{{.Names}}" 2>&3); then
echo "$base_toolbox_command: failed to list containers with com.redhat.component=fedora-toolbox" >&2
return 1
fi
if ! containers=$($prefix_sudo podman ps \
--all \
--filter "label=com.github.debarshiray.toolbox=true" \
--format "{{.Names}}" 2>&3); then
echo "$base_toolbox_command: failed to list containers with com.github.debarshiray.toolbox=true" >&2
return 1
fi
printf "%s\n%s\n" "$containers_old" "$containers" | sort 2>&3 | uniq 2>&3
return 0
)
pull_base_toolbox_image()
(
domain=""
has_domain=false
prompt_for_download=true
pull_image=false
if image_reference_can_be_id "$base_toolbox_image"; then
echo "$base_toolbox_command: looking for image $base_toolbox_image" >&3
if $prefix_sudo podman image exists "$base_toolbox_image" >/dev/null 2>&3; then
return 0
fi
fi
image_reference_has_domain "$base_toolbox_image" && has_domain=true
if ! $has_domain; then
echo "$base_toolbox_command: looking for image localhost/$base_toolbox_image" >&3
if $prefix_sudo podman image exists localhost/$base_toolbox_image >/dev/null 2>&3; then
return 0
fi
fi
if $has_domain; then
base_toolbox_image_full="$base_toolbox_image"
else
base_toolbox_image_full="$registry/$fgc/$base_toolbox_image"
fi
echo "$base_toolbox_command: looking for image $base_toolbox_image_full" >&3
if $prefix_sudo podman image exists "$base_toolbox_image_full" >/dev/null 2>&3; then
return 0
fi
domain=$(image_reference_get_domain "$base_toolbox_image_full")
if $assume_yes || [ "$domain" = "localhost" ] 2>&3; then
prompt_for_download=false
pull_image=true
fi
if $prompt_for_download; then
echo "Image required to create toolbox container."
prompt=$(printf "Download %s (500MB)? [y/N]:" "$base_toolbox_image_full")
if ask_for_confirmation "n" "$prompt"; then
pull_image=true
else
pull_image=false
fi
fi
if ! $pull_image; then
return 1
fi
echo "$base_toolbox_command: pulling image $base_toolbox_image_full" >&3
if spinner_directory=$(mktemp --directory --tmpdir $spinner_template 2>&3); then
spinner_message="Pulling $base_toolbox_image_full: "
if ! spinner_start "$spinner_directory" "$spinner_message"; then
spinner_directory=""
fi
else
echo "$base_toolbox_command: unable to start spinner: spinner directory not created" >&2
spinner_directory=""
fi
$prefix_sudo podman pull $base_toolbox_image_full >/dev/null 2>&3
ret_val=$?
if [ "$spinner_directory" != "" ]; then
spinner_stop "$spinner_directory"
fi
if [ "$ret_val" -ne 0 ] 2>&3; then
echo "$base_toolbox_command: failed to pull base image $base_toolbox_image" >&2
fi
return $ret_val
)
create()
(
enter_command_skip="$1"
dbus_system_bus_address="unix:path=/var/run/dbus/system_bus_socket"
dns_none=""
kcm_socket=""
kcm_socket_bind=""
monitor_host=""
no_hosts=""
tmpfs_size=$((64 * 1024 * 1024)) # 64 MiB
toolbox_profile_bind=""
# shellcheck disable=SC2153
if [ "$DBUS_SYSTEM_BUS_ADDRESS" != "" ]; then
dbus_system_bus_address=$DBUS_SYSTEM_BUS_ADDRESS
fi
dbus_system_bus_path=$(echo "$dbus_system_bus_address" | cut --delimiter = --fields 2 2>&3)
dbus_system_bus_path=$(readlink --canonicalize "$dbus_system_bus_path" 2>&3)
# Note that 'systemctl show ...' doesn't terminate with a non-zero exit
# code when used with an unknown unit. eg.:
# $ systemctl show --value --property Listen foo
# $ echo $?
# 0
if ! kcm_socket_listen=$(systemctl show --value --property Listen sssd-kcm.socket 2>&3); then
echo "$base_toolbox_command: failed to use 'systemctl show'" >&3
kcm_socket_listen=""
elif [ "$kcm_socket_listen" = "" ] 2>&3; then
echo "$base_toolbox_command: failed to read property Listen from sssd-kcm.socket" >&3
else
echo "$base_toolbox_command: checking value $kcm_socket_listen of property Listen in sssd-kcm.socket" >&3
if ! (echo "$kcm_socket_listen" | grep " (Stream)$" >/dev/null 2>&3); then
echo "$base_toolbox_command: unknown socket in sssd-kcm.socket" >&2
echo "$base_toolbox_command: expected SOCK_STREAM" >&2
kcm_socket_listen=""
elif ! (echo "$kcm_socket_listen" | grep "^/" >/dev/null 2>&3); then
echo "$base_toolbox_command: unknown socket in sssd-kcm.socket" >&2
echo "$base_toolbox_command: expected file system socket in the AF_UNIX family" >&2
kcm_socket_listen=""
fi
fi
echo "$base_toolbox_command: parsing value $kcm_socket_listen of property Listen in sssd-kcm.socket" >&3
if [ "$kcm_socket_listen" != "" ] 2>&3; then
kcm_socket=${kcm_socket_listen%" (Stream)"}
kcm_socket_bind="--volume $kcm_socket:$kcm_socket"
fi
echo "$base_toolbox_command: checking if 'podman create' supports --dns=none and --no-hosts" >&3
if $prefix_sudo podman create --help 2>&3 | grep "hosts" >/dev/null 2>&3; then
echo "$base_toolbox_command: 'podman create' supports --dns=none and --no-hosts" >&3
dns_none="--dns none"
no_hosts="--no-hosts"
monitor_host="--monitor-host"
fi
if ! pull_base_toolbox_image; then
return 1
fi
if image_reference_has_domain "$base_toolbox_image"; then
base_toolbox_image_full="$base_toolbox_image"
else
if ! base_toolbox_image_full=$($prefix_sudo podman inspect \
--format "{{index .RepoTags 0}}" \
--type image \
"$base_toolbox_image" 2>&3); then
echo "$base_toolbox_command: failed to get RepoTag for base image $base_toolbox_image" >&2
return 1
fi
echo "$base_toolbox_command: base image $base_toolbox_image resolved to $base_toolbox_image_full" >&3
fi
echo "$base_toolbox_command: checking if container $toolbox_container already exists" >&3
enter_command=$(create_enter_command "$toolbox_container")
if $prefix_sudo podman container exists $toolbox_container >/dev/null 2>&3; then
echo "$base_toolbox_command: container $toolbox_container already exists" >&2
echo "Enter with: $enter_command" >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
return 1
fi
total_ram=$(awk '( $1 == "MemTotal:" ) { print $2 }' /proc/meminfo 2>&3) # kibibytes
if is_integer "$total_ram"; then
tmpfs_size=$((total_ram * 1024 / 2)) # bytes
fi
toolbox_path_bind="--volume $TOOLBOX_PATH:/usr/bin/toolbox:ro"
toolbox_path_set="--env TOOLBOX_PATH=$TOOLBOX_PATH"
if [ -f /etc/profile.d/toolbox.sh ] 2>&3; then
toolbox_profile_bind="--volume /etc/profile.d/toolbox.sh:/etc/profile.d/toolbox.sh:ro"
fi
max_uid_count=65536
max_minus_uid=$((max_uid_count - user_id_real))
uid_plus_one=$((user_id_real + 1))
echo "$base_toolbox_command: trying to create container $toolbox_container" >&3
if spinner_directory=$(mktemp --directory --tmpdir $spinner_template 2>&3); then
spinner_message="Creating container $toolbox_container: "
if ! spinner_start "$spinner_directory" "$spinner_message"; then
spinner_directory=""
fi
else
echo "$base_toolbox_command: unable to start spinner: spinner directory not created" >&2
spinner_directory=""
fi
# shellcheck disable=SC2086
$prefix_sudo podman create \
$dns_none \
$toolbox_path_set \
--group-add wheel \
--hostname toolbox \
--label "com.github.debarshiray.toolbox=true" \
--name $toolbox_container \
--network host \
$no_hosts \
--pid host \
--privileged \
--security-opt label=disable \
--tmpfs /dev/shm:size="$tmpfs_size" \
--uidmap "$user_id_real":0:1 \
--uidmap 0:1:"$user_id_real" \
--uidmap "$uid_plus_one":"$uid_plus_one":"$max_minus_uid" \
--user root:root \
$kcm_socket_bind \
$toolbox_path_bind \
$toolbox_profile_bind \
--volume "$HOME":"$HOME":rslave \
--volume "$XDG_RUNTIME_DIR":"$XDG_RUNTIME_DIR" \
--volume "$dbus_system_bus_path":"$dbus_system_bus_path" \
--volume /etc:/run/host/etc \
--volume /dev/bus:/dev/bus \
--volume /dev/dri:/dev/dri \
--volume /dev/fuse:/dev/fuse \
--volume /media:/media:rslave \
--volume /mnt:/mnt:rslave \
--volume /run/media:/run/media:rslave \
--workdir "$HOME" \
"$base_toolbox_image_full" \
toolbox init-container \
--home "$HOME" \
$monitor_host \
--shell "$SHELL" \
--uid "$user_id_real" \
--user "$USER" >/dev/null 2>&3
ret_val=$?
if [ "$spinner_directory" != "" ]; then
spinner_stop "$spinner_directory"
fi
if [ $ret_val -ne 0 ]; then
echo "$base_toolbox_command: failed to create container $toolbox_container" >&2
return 1
fi
if ! $enter_command_skip; then
echo "Created container: $toolbox_container"
echo "Enter with: $enter_command"
fi
return 0
)
enter()
{
run true false "$SHELL" -l
}
init_container()
{
init_container_home="$1"
init_container_monitor_host="$2"
init_container_shell="$3"
init_container_uid="$4"
init_container_user="$5"
if $init_container_monitor_host; then
working_directory="$PWD"
if ! readlink /etc/hosts >/dev/null 2>&3; then
if ! cd /etc 2>&3 && unlink hosts 2>&3 && ln --symbolic /run/host/etc/hosts hosts 2>&3; then
echo "$base_toolbox_command: failed to redirect /etc/hosts to /run/host/etc/hosts" >&2
return 1
fi
fi
if ! readlink /etc/resolv.conf >/dev/null 2>&3; then
if ! cd /etc 2>&3 \
&& unlink resolv.conf 2>&3 \
&& ln --symbolic /run/host/etc/resolv.conf resolv.conf 2>&3; then
echo "$base_toolbox_command: failed to redirect /etc/resolv.conf to /run/host/etc/resolv.conf" >&2
return 1
fi
fi
if ! cd "$working_directory" 2>&3; then
echo "$base_toolbox_command: failed to restore working directory" >&2
fi
fi
if ! id -u "$init_container_user" >/dev/null 2>&3; then
if [ "$(readlink /home)" = var/home ] 2>&3; then
# shellcheck disable=SC2174
if ! rmdir /home 2>&3 \
&& mkdir --mode 0755 --parents /var/home 2>&3 \
&& ln --symbolic var/home /home 2>&3; then
echo "$base_toolbox_command: failed to make /home a symlink" >&2
return 1
fi
fi
if ! useradd \
--home-dir "$init_container_home" \
--no-create-home \
--shell "$init_container_shell" \
--uid "$init_container_uid" \
--groups wheel \
"$init_container_user" >/dev/null 2>&3; then
echo "$base_toolbox_command: failed to add user $init_container_user with UID $init_container_uid" >&2
return 1
fi
if ! passwd --delete "$init_container_user" >/dev/null 2>&3; then
echo "$base_toolbox_command: failed to remove password for user $init_container_user" >&2
return 1
fi
if ! passwd --delete root >/dev/null 2>&3; then
echo "$base_toolbox_command: failed to remove password for user root" >&2
return 1
fi
fi
if ! [ -f /etc/krb5.conf.d/kcm_default_ccache ] 2>&3; then
cat <<EOF >/etc/krb5.conf.d/kcm_default_ccache 2>&3
# Written by Toolbox
# https://github.com/debarshiray/toolbox
#
# # To disable the KCM credential cache, comment out the following lines.
[libdefaults]
default_ccache_name = KCM:
EOF
ret_val=$?
if [ "$ret_val" -ne 0 ] 2>&3; then
echo "$base_toolbox_command: failed to set KCM as the default Kerberos credential cache" >&2
return 1
fi
fi
exec sleep +Inf
}
run()
(
fallback_to_bash="$1"
pedantic="$2"
program="$3"
shift 3
create_toolbox_container=false
prompt_for_create=true
echo "$base_toolbox_command: checking if container $toolbox_container exists" >&3
if ! $prefix_sudo podman container exists "$toolbox_container" 2>&3; then
echo "$base_toolbox_command: container $toolbox_container not found" >&3
if $prefix_sudo podman container exists "$toolbox_container_old_v1" 2>&3; then
echo "$base_toolbox_command: container $toolbox_container_old_v1 found" >&3
# shellcheck disable=SC2030
toolbox_container="$toolbox_container_old_v1"
elif $prefix_sudo podman container exists "$toolbox_container_old_v2" 2>&3; then
echo "$base_toolbox_command: container $toolbox_container_old_v2 found" >&3
# shellcheck disable=SC2030
toolbox_container="$toolbox_container_old_v2"
else
if $pedantic; then
enter_print_container_not_found "$toolbox_container"
exit 1
fi
if ! containers=$(list_container_names); then
enter_print_container_not_found "$toolbox_container"
exit 1
fi
containers_count=$(echo "$containers" | grep --count . 2>&3)
if ! is_integer "$containers_count"; then
enter_print_container_not_found "$toolbox_container"
exit 1
fi
echo "$base_toolbox_command: found $containers_count containers" >&3
if [ "$containers_count" -eq 0 ] 2>&3; then
if $assume_yes; then
create_toolbox_container=true
prompt_for_create=false
fi
if $prompt_for_create; then
prompt="No toolbox containers found. Create now? [y/N]"
if ask_for_confirmation "n" "$prompt"; then
create_toolbox_container=true
else
create_toolbox_container=false
fi
fi
if ! $create_toolbox_container; then
echo "A container can be created later with the 'create' command." >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
exit 1
fi
if ! update_container_and_image_names; then
exit 1
fi
if ! create true; then
exit 1
fi
elif [ "$containers_count" -eq 1 ] 2>&3 \
&& [ "$toolbox_container" = "$toolbox_container_default" ] 2>&3; then
echo "$base_toolbox_command: container $toolbox_container not found" >&2
toolbox_container=$(echo "$containers" | grep . 2>&3 | head --lines 1 2>&3)
echo "Entering container $toolbox_container instead." >&2
echo "Use the 'create' command to create a different toolbox." >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
else
echo "$base_toolbox_command: container $toolbox_container not found" >&2
echo "Use the '--container' option to select a toolbox." >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
exit 1
fi
fi
fi
echo "$base_toolbox_command: trying to start container $toolbox_container" >&3
if ! $prefix_sudo podman start "$toolbox_container" >/dev/null 2>&3; then
echo "$base_toolbox_command: failed to start container $toolbox_container" >&2
exit 1
fi
if ! copy_etc_profile_d_toolbox_to_container "$toolbox_container"; then
exit 1
fi
if ! $prefix_sudo podman exec --user root:root "$toolbox_container" touch /run/.toolboxenv 2>&3; then
echo "$base_toolbox_command: failed to create /run/.toolboxenv in container $toolbox_container" >&2
exit 1
fi
set_environment=$(create_environment_options)
echo "$base_toolbox_command: looking for $program in container $toolbox_container" >&3
# shellcheck disable=SC2016
if ! $prefix_sudo podman exec \
--user "$USER" \
"$toolbox_container" \
sh -c 'command -v "$1"' sh "$program" >/dev/null 2>&3; then
if $fallback_to_bash; then
echo "$base_toolbox_command: $program not found in $toolbox_container; using /bin/bash instead" >&3
program=/bin/bash
else
echo "$base_toolbox_command: command '$program' not found in container $toolbox_container" >&2
exit 127
fi
fi
echo "$base_toolbox_command: running in container $toolbox_container:" >&3
echo "$base_toolbox_command: $program" >&3
for i in "$@"; do
echo "$base_toolbox_command: $i" >&3
done
# shellcheck disable=SC2016
# for the command passed to capsh
# shellcheck disable=SC2086
$prefix_sudo podman exec \
--interactive \
--tty \
--user "$USER" \
$set_environment \
"$toolbox_container" \
capsh --caps="" -- -c 'cd "$1"; shift; exec "$@"' /bin/sh "$PWD" "$program" "$@" 2>&3
)
list_images()
(
if ! images_old=$($prefix_sudo podman images \
--filter "label=com.redhat.component=fedora-toolbox" \
--format "{{.Repository}}:{{.Tag}}" 2>&3); then
echo "$base_toolbox_command: failed to list images with com.redhat.component=fedora-toolbox" >&2
return 1
fi
if ! images=$($prefix_sudo podman images \
--filter "label=com.github.debarshiray.toolbox=true" \
--format "{{.Repository}}:{{.Tag}}" 2>&3); then
echo "$base_toolbox_command: failed to list images with com.github.debarshiray.toolbox=true" >&2
return 1
fi
images=$(printf "%s\n%s\n" "$images_old" "$images" | sort 2>&3 | uniq 2>&3)
if ! details=$(images_get_details "$images"); then
return 1
fi
if ! output=$(echo "$details" \
| sed "s/ \{2,\}/\t/g" 2>&3 \
| column --separator "$tab" --table --table-columns "IMAGE ID,IMAGE NAME,CREATED" 2>&3); then
echo "$base_toolbox_command: failed to parse list of images" >&2
return 1
fi
if [ "$output" != "" ]; then
# shellcheck disable=SC2059
printf "${LBC}Images created by toolbox${NC}\n"
echo "$output"
fi
return 0
)
containers_get_details()
(
containers="$1"
if ! echo "$containers" | while read -r container; do
[ "$container" = "" ] 2>&3 && continue
if ! $prefix_sudo podman ps --all \
--filter "name=$container" \
--format "{{.ID}} {{.Names}} {{.Created}} {{.Status}} {{.Image}}" 2>&3; then
echo "$base_toolbox_command: failed to get details for container $container" >&2
return 1
fi
done; then
return 1
fi
return 0
)
list_containers()
(
if ! containers=$(list_container_names); then
return 1
fi
if ! details=$(containers_get_details "$containers"); then
return 1
fi
if ! output=$(echo "$details" \
| sed "s/ \{2,\}/\t/g" 2>&3 \
| column \
--separator "$tab" \
--table \
--table-columns "CONTAINER ID,CONTAINER NAME,CREATED,STATUS,IMAGE NAME" 2>&3); then
echo "$base_toolbox_command: failed to parse list of containers" >&2
return 1
fi
if [ "$output" != "" ]; then
# shellcheck disable=SC2059
printf "${LBC}Containers created by toolbox${NC}\n"
echo "$output" | head --lines 1 2>&3
echo "$output" | tail --lines +2 2>&3 \
| (
while read -r container; do
id=$(echo "$container" | cut --delimiter " " --fields 1 2>&3)
is_running=$($prefix_sudo podman inspect "$id" --format "{{.State.Running}}" 2>&3)
if $is_running; then
# shellcheck disable=SC2059
printf "${LGC}$container${NC}\n"
else
echo "$container"
fi
done
)
fi
return 0
)
remove_containers()
(
ids=$1
all=$2
force=$3
ret_val=0
$force && force_option="--force"
if $all; then
if ! ids_old=$($prefix_sudo podman ps \
--all \
--filter "label=com.redhat.component=fedora-toolbox" \
--format "{{.ID}}" 2>&3); then
echo "$base_toolbox_command: failed to list containers with com.redhat.component=fedora-toolbox" >&2
return 1
fi
if ! ids=$($prefix_sudo podman ps \
--all \
--filter "label=com.github.debarshiray.toolbox=true" \
--format "{{.ID}}" 2>&3); then
echo "$base_toolbox_command: failed to list containers with com.github.debarshiray.toolbox=true" >&2
return 1
fi
ids=$(printf "%s\n%s\n" "$ids_old" "$ids" | sort 2>&3 | uniq 2>&3)
if [ "$ids" != "" ]; then
ret_val=$(echo "$ids" \
| (
while read -r id; do
if ! $prefix_sudo podman rm $force_option "$id" >/dev/null 2>&3; then
echo "$base_toolbox_command: failed to remove container $id" >&2
ret_val=1
fi
done
echo "$ret_val"
)
)
fi
else
ret_val=$(echo "$ids" \
| sed "s/ \+/\n/g" 2>&3 \
| (
while read -r id; do
if ! labels=$($prefix_sudo podman inspect \
--format "{{.Config.Labels}}" \
--type container \
"$id" 2>&3); then
echo "$base_toolbox_command: failed to inspect $id" >&2
ret_val=1
continue
fi
if ! has_substring "$labels" "com.github.debarshiray.toolbox" \
&& ! has_substring "$labels" "com.redhat.component:fedora-toolbox"; then
echo "$base_toolbox_command: $id is not a toolbox container" >&2
ret_val=1
continue
fi
if ! $prefix_sudo podman rm $force_option "$id" >/dev/null 2>&3; then
echo "$base_toolbox_command: failed to remove container $id" >&2
ret_val=1
fi
done
echo "$ret_val"
)
)
fi
return "$ret_val"
)
remove_images()
(
ids=$1
all=$2
force=$3
ret_val=0
$force && force_option="--force"
if $all; then
if ! ids_old=$($prefix_sudo podman images \
--filter "label=com.redhat.component=fedora-toolbox" \
--format "{{.ID}}" 2>&3); then
echo "$0: failed to list images with com.redhat.component=fedora-toolbox" >&2
return 1
fi
if ! ids=$($prefix_sudo podman images \
--all \
--filter "label=com.github.debarshiray.toolbox=true" \
--format "{{.ID}}" 2>&3); then
echo "$0: failed to list images with com.github.debarshiray.toolbox=true" >&2
return 1
fi
ids=$(printf "%s\n%s\n" "$ids_old" "$ids" | sort 2>&3 | uniq 2>&3)
if [ "$ids" != "" ]; then
ret_val=$(echo "$ids" \
| (
while read -r id; do
if ! $prefix_sudo podman rmi $force_option "$id" >/dev/null 2>&3; then
echo "$base_toolbox_command: failed to remove image $id" >&2
ret_val=1
fi
done
echo "$ret_val"
)
)
fi
else
ret_val=$(echo "$ids" \
| sed "s/ \+/\n/g" 2>&3 \
| (
while read -r id; do
if ! labels=$($prefix_sudo podman inspect \
--format "{{.Labels}}" \
--type image \
"$id" 2>&3); then
echo "$base_toolbox_command: failed to inspect $id" >&2
ret_val=1
continue
fi
if ! has_substring "$labels" "com.github.debarshiray.toolbox" \
&& ! has_substring "$labels" "com.redhat.component:fedora-toolbox"; then
echo "$base_toolbox_command: $id is not a toolbox image" >&2
ret_val=1
continue
fi
if ! $prefix_sudo podman rmi $force_option "$id" >/dev/null 2>&3; then
echo "$base_toolbox_command: failed to remove image $id" >&2
ret_val=1
fi
done
echo "$ret_val"
)
)
fi
return "$ret_val"
)
exit_if_extra_operand()
{
if [ "$1" != "" ]; then
echo "$base_toolbox_command: extra operand '$1'" >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
exit 1
fi
}
exit_if_missing_argument()
{
if [ "$2" = "" ]; then
echo "$base_toolbox_command: missing argument for '$1'" >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
exit 1
fi
}
exit_if_non_positive_argument()
{
if ! is_integer "$2"; then
echo "$base_toolbox_command: invalid argument for '$1'" >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
exit 1
fi
if [ "$2" -le 0 ] 2>&3; then
echo "$base_toolbox_command: invalid argument for '$1'" >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
exit 1
fi
}
exit_if_unrecognized_option()
{
echo "$base_toolbox_command: unrecognized option '$1'" >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
exit 1
}
# shellcheck disable=SC2120
forward_to_host()
(
if ! command -v flatpak-spawn >/dev/null 2>&3; then
echo "$base_toolbox_command: flatpak-spawn not found" >&2
return 1
fi
eval "set -- $arguments"
set_environment=$(create_environment_options)
echo "$base_toolbox_command: forwarding to host:" >&3
echo "$base_toolbox_command: $TOOLBOX_PATH" >&3
for i in "$@"; do
echo "$base_toolbox_command: $i" >&3
done
# shellcheck disable=SC2086
flatpak-spawn $set_environment --host "$TOOLBOX_PATH" "$@" 2>&3
ret_val="$?"
return "$ret_val"
)
update_container_and_image_names()
{
[ "$release" = "" ] 2>&3 && release="$release_default"
if [ "$base_toolbox_image" = "" ] 2>&3; then
base_toolbox_image="fedora-toolbox:$release"
else
release=$(image_reference_get_tag "$base_toolbox_image")
[ "$release" = "" ] 2>&3 && release="$release_default"
fi
fgc="f$release"
echo "$base_toolbox_command: Fedora generational core is $fgc" >&3
echo "$base_toolbox_command: base image is $base_toolbox_image" >&3
toolbox_image=$(create_toolbox_image_name)
if ! (
ret_val=$?
if [ "$ret_val" -ne 0 ] 2>&3; then
if [ "$ret_val" -eq 100 ] 2>&3; then
echo "$base_toolbox_command: failed to get the basename of base image $base_toolbox_image" >&2
else
echo "$base_toolbox_command: failed to create an ID for the customized user-specific image" >&2
fi
exit 1
fi
exit 0
); then
return 1
fi
# shellcheck disable=SC2031
if [ "$toolbox_container" = "" ]; then
toolbox_container=$(create_toolbox_container_name "$base_toolbox_image")
if ! (
ret_val=$?
if [ "$ret_val" -ne 0 ] 2>&3; then
if [ "$ret_val" -eq 100 ] 2>&3; then
echo "$base_toolbox_command: failed to get the basename of image $base_toolbox_image" >&2
elif [ "$ret_val" -eq 101 ] 2>&3; then
echo "$base_toolbox_command: failed to get the tag of image $base_toolbox_image" >&2
else
echo "$base_toolbox_command: failed to create a name for the toolbox container" >&2
fi
exit 1
fi
exit 0
); then
return 1
fi
if ! container_name_is_valid "$toolbox_container"; then
echo "$base_toolbox_command: generated container name $toolbox_container is invalid" >&2
echo "Container names must match '$container_name_regexp'." >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
return 1
fi
toolbox_container_old_v1=$(create_toolbox_container_name "$toolbox_image")
if ! (
ret_val=$?
if [ "$ret_val" -ne 0 ] 2>&3; then
if [ "$ret_val" -eq 100 ] 2>&3; then
echo "$base_toolbox_command: failed to get the basename of image $toolbox_image" >&2
elif [ "$ret_val" -eq 101 ] 2>&3; then
echo "$base_toolbox_command: failed to get the tag of image $toolbox_image" >&2
else
echo "$base_toolbox_command: failed to create a name for the toolbox container" >&2
fi
exit 1
fi
exit 0
); then
return 1
fi
toolbox_container_old_v2="$toolbox_image"
fi
echo "$base_toolbox_command: container is $toolbox_container" >&3
return 0
}
usage()
{
echo "Usage: toolbox [-v | --verbose]"
echo " [-y | --assumeyes]"
echo " create [--candidate-registry]"
echo " [-c | --container <name>]"
echo " [-i | --image <name>]"
echo " [-r | --release <release>]"
echo " or: toolbox [-v | --verbose]"
echo " [-y | --assumeyes]"
echo " enter [-c | --container <name>]"
echo " [-r | --release <release>]"
echo " or: toolbox [-v | --verbose]"
echo " [-y | --assumeyes]"
echo " init-container --home"
echo " --monitor-host"
echo " --shell"
echo " --uid"
echo " --user"
echo " or: toolbox [-v | --verbose]"
echo " [-y | --assumeyes]"
echo " list [-c | --containers]"
echo " [-i | --images]"
echo " or: toolbox [-y | --assumeyes]"
echo " rm [-a | --all]"
echo " [-f | --force] [<container> ...]"
echo " or: toolbox [-y | --assumeyes]"
echo " rmi [-a | --all]"
echo " [-f | --force] [<image> ...]"
echo " or: toolbox [-v | --verbose]"
echo " [-y | --assumeyes]"
echo " run [-c | --container <name>]"
echo " [-r | --release <release>] <command>"
echo " or: toolbox --help"
}
arguments=$(save_positional_parameters "$@")
host_id=$(get_host_id)
if [ "$host_id" = "fedora" ] 2>&3; then
release_default=$(get_host_version_id)
else
release_default="29"
fi
toolbox_container_prefix_default="fedora-toolbox"
toolbox_container_default="$toolbox_container_prefix_default-$release_default"
while has_prefix "$1" -; do
case $1 in
--assumeyes | -y )
assume_yes=true
;;
-h | --help )
usage
exit
;;
--sudo )
prefix_sudo="sudo"
;;
-v | --verbose )
exec 3>&2
verbose=true
;;
* )
exit_if_unrecognized_option "$1"
esac
shift
done
if ! toolbox_command_path=$(realpath "$0" 2>&3); then
echo "$base_toolbox_command: failed to resolve absolute path to $0" >&2
exit 1
fi
echo "$base_toolbox_command: resolved absolute path for $0 to $toolbox_command_path" >&3
if [ -f /run/.containerenv ] 2>&3; then
if [ "$TOOLBOX_PATH" = "" ] 2>&3; then
echo "$base_toolbox_command: TOOLBOX_PATH not set" >&2
exit 1
fi
else
if [ "$TOOLBOX_PATH" = "" ] 2>&3; then
TOOLBOX_PATH="$toolbox_command_path"
fi
fi
echo "$base_toolbox_command: TOOLBOX_PATH is $TOOLBOX_PATH" >&3
if [ "$1" = "" ]; then
echo "$base_toolbox_command: missing command" >&2
echo >&2
echo "These are some common commands:" >&2
echo "create Create a new toolbox container" >&2
echo "enter Enter an existing toolbox container" >&2
echo "list List all existing toolbox containers and images" >&2
echo >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
exit 1
fi
op=$1
shift
if [ -f /run/.containerenv ] 2>&3; then
case $op in
create | enter | list | rm | rmi | run )
if ! [ -f /run/.toolboxenv ] 2>&3; then
echo "$base_toolbox_command: this is not a toolbox container" >&2
exit 1
fi
# shellcheck disable=SC2119
forward_to_host
exit "$?"
;;
init-container )
init_container_monitor_host=false
while has_prefix "$1" -; do
case $1 in
--home )
shift
exit_if_missing_argument --home "$1"
init_container_home="$1"
;;
--monitor-host )
init_container_monitor_host=true
;;
--shell )
shift
exit_if_missing_argument --shell "$1"
init_container_shell="$1"
;;
--uid )
shift
exit_if_missing_argument --uid "$1"
init_container_uid="$1"
;;
--user )
shift
exit_if_missing_argument --user "$1"
init_container_user="$1"
;;
* )
exit_if_unrecognized_option "$1"
esac
shift
done
init_container \
"$init_container_home" \
"$init_container_monitor_host" \
"$init_container_shell" \
"$init_container_uid" \
"$init_container_user"
exit "$?"
;;
* )
echo "$base_toolbox_command: unrecognized command '$op'" >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
exit 1
;;
esac
fi
case $op in
create )
while has_prefix "$1" -; do
case $1 in
--candidate-registry )
registry=$registry_candidate
;;
-c | --container )
shift
exit_if_missing_argument --container "$1"
arg=$1
if ! container_name_is_valid "$arg"; then
echo "$base_toolbox_command: invalid argument for '--container'" >&2
echo "Container names must match '$container_name_regexp'." >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
exit 1
fi
toolbox_container="$arg"
;;
-i | --image )
shift
exit_if_missing_argument --image "$1"
base_toolbox_image=$1
;;
-r | --release )
shift
exit_if_missing_argument --release "$1"
arg=$(echo "$1" | sed "s/^F\|^f//" 2>&3)
exit_if_non_positive_argument --release "$arg"
release=$arg
;;
* )
exit_if_unrecognized_option "$1"
esac
shift
done
exit_if_extra_operand "$1"
if ! update_container_and_image_names; then
exit 1
fi
if ! create false; then
exit 1
fi
exit
;;
enter )
while has_prefix "$1" -; do
case $1 in
-c | --container )
shift
exit_if_missing_argument --container "$1"
toolbox_container=$1
;;
-r | --release )
shift
exit_if_missing_argument --release "$1"
arg=$(echo "$1" | sed "s/^F\|^f//" 2>&3)
exit_if_non_positive_argument --release "$arg"
release=$arg
;;
* )
exit_if_unrecognized_option "$1"
esac
shift
done
exit_if_extra_operand "$1"
if ! update_container_and_image_names; then
exit 1
fi
enter
exit
;;
init-container )
echo "$base_toolbox_command: The 'init-container' command can only be used inside containers" >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
exit 1
;;
list )
ls_images=false
ls_containers=false
while has_prefix "$1" -; do
case $1 in
-c | --containers )
ls_containers=true
;;
-i | --images )
ls_images=true
;;
* )
exit_if_unrecognized_option "$1"
esac
shift
done
exit_if_extra_operand "$1"
if ! $ls_containers && ! $ls_images; then
ls_containers=true
ls_images=true
fi
if $ls_images; then
if ! images=$(list_images); then
exit 1
fi
fi
if $ls_containers; then
if ! containers=$(list_containers); then
exit 1
fi
fi
$ls_images && [ "$images" != "" ] && echo "$images"
$ls_containers && [ "$containers" != "" ] && echo "$containers"
exit
;;
rm | rmi )
rm_all=false
rm_force=false
while has_prefix "$1" -; do
case $1 in
-a | --all )
rm_all=true
;;
-f | --force )
rm_force=true
;;
* )
exit_if_unrecognized_option "$1"
esac
shift
done
rm_ids=""
if $rm_all; then
exit_if_extra_operand "$1"
else
exit_if_missing_argument "$op" "$1"
while [ "$1" != "" ]; do
rm_ids="$rm_ids $1"
shift
done
fi
rm_ids=$(echo "$rm_ids" | sed "s/^ \+//" 2>&3)
if [ "$op" = "rm" ]; then
remove_containers "$rm_ids" "$rm_all" "$rm_force"
else
remove_images "$rm_ids" "$rm_all" "$rm_force"
fi
exit
;;
run )
while has_prefix "$1" -; do
case $1 in
-c | --container )
shift
exit_if_missing_argument --container "$1"
toolbox_container=$1
;;
-r | --release )
shift
exit_if_missing_argument --release "$1"
arg=$(echo "$1" | sed "s/^F\|^f//" 2>&3)
exit_if_non_positive_argument --release "$arg"
release=$arg
;;
* )
exit_if_unrecognized_option "$1"
esac
shift
done
if ! update_container_and_image_names; then
exit 1
fi
run false true "$@"
exit
;;
* )
echo "$base_toolbox_command: unrecognized command '$op'" >&2
echo "Try '$base_toolbox_command --help' for more information." >&2
exit 1
esac