From 8b84b5e4604921fad818ede5267591de7f0993e6 Mon Sep 17 00:00:00 2001 From: Debarshi Ray Date: Fri, 10 May 2019 20:38:46 +0200 Subject: [PATCH] Drop the Buildah dependency and the user-specific customized image This works by configuring the toolbox container after it has been created, instead of before. The toolbox script itself is mentioned as the entry point of the container, which does 'exec sleep +Inf' once the initialization is done. A new command 'init-container' was added to perform the initialization. It is primarily meant to be used as the entry point for all toolbox containers, and must be run inside the container that's to be initialized. It is not expected to be directly invoked by humans, and cannot be used on the host. As a result, the default name for the toolbox containers is now fedora-toolbox-, not fedora-toolbox--. For backwards compatibility, 'toolbox enter' and 'toolbox run' will continue to work with containers using the old naming scheme. https://github.com/debarshiray/toolbox/pull/160 --- README.md | 12 +- completion/bash/toolbox | 3 +- doc/meson.build | 1 + doc/toolbox-init-container.1.md | 48 ++++ doc/toolbox.1.md | 4 + toolbox | 444 +++++++++++++++----------------- 6 files changed, 263 insertions(+), 249 deletions(-) create mode 100644 doc/toolbox-init-container.1.md diff --git a/README.md b/README.md index c4a7320..9d3f866 100644 --- a/README.md +++ b/README.md @@ -19,20 +19,20 @@ works equally well if you're running e.g. existing Fedora Workstation or Server, and that's a useful way to incrementally adopt containerization. The toolbox environment is based on an [OCI](https://www.opencontainers.org/) -image. On Fedora this is the `fedora-toolbox` image. This image is then -customized for the current user to create a toolbox container that seamlessly -integrates with the rest of the operating system. +image. On Fedora this is the `fedora-toolbox` image. This image is used to +create a toolbox container that seamlessly integrates with the rest of the +operating system. ## Usage ### Create your toolbox container: ``` [user@hostname ~]$ toolbox create +Created container: fedora-toolbox-30 +Enter with: toolbox enter [user@hostname ~]$ ``` -This will create a container, and an image, called -`fedora-toolbox-:` that's specifically customised -for your host user. +This will create a container called `fedora-toolbox-`. ### Enter the toolbox: ``` diff --git a/completion/bash/toolbox b/completion/bash/toolbox index 9fb47ff..540b8df 100644 --- a/completion/bash/toolbox +++ b/completion/bash/toolbox @@ -13,12 +13,13 @@ __toolbox() { local MIN_VERSION=29 local RAWHIDE_VERSION=31 - local verbose_commands="create enter list run" + local verbose_commands="create enter init-container list run" local commands="$verbose_commands rm rmi" declare -A options local options=([create]="--candidate-registry --container --image --release" \ [enter]="--container --release" \ + [init-container]="--home --monitor-host --shell --uid --user" \ [list]="--containers --images" \ [rm]="--all --force" \ [rmi]="--all --force" \ diff --git a/doc/meson.build b/doc/meson.build index e5f0a96..1183687 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -8,6 +8,7 @@ manuals = [ 'toolbox.1', 'toolbox-create.1', 'toolbox-enter.1', + 'toolbox-init-container.1', 'toolbox-list.1', 'toolbox-rm.1', 'toolbox-rmi.1', diff --git a/doc/toolbox-init-container.1.md b/doc/toolbox-init-container.1.md new file mode 100644 index 0000000..9486c56 --- /dev/null +++ b/doc/toolbox-init-container.1.md @@ -0,0 +1,48 @@ +% toolbox-init-container(1) + +## NAME +toolbox\-init\-container - Initialize a running container + +## SYNOPSIS +**toolbox init-container** *--home HOME* + *--monitor-host* + *--shell SHELL* + *--uid UID* + *--user USER* + +## DESCRIPTION + +Initializes a newly created container that's running. It is primarily meant to +be used as the entry point for all toolbox containers, and must be run inside +the container that's to be initialized. It is not expected to be directly +invoked by humans, and cannot be used on the host. + +## OPTIONS ## + +The following options are understood: + +**--home** HOME + +Create a user inside the toolbox container whose login directory is HOME. + +**--monitor-host** + +Ensure that certain configuration files inside the toolbox container are kept +synchronized with their counterparts on the host. Currently, these files are +`/etc/hosts` and `/etc/resolv.conf`. + +**--shell** SHELL + +Create a user inside the toolbox container whose login shell is SHELL. + +**--uid** UID + +Create a user inside the toolbox container whose numerical user ID is UID. + +**--user** USER + +Create a user inside the toolbox container whose login name is LOGIN. + +## SEE ALSO + +`podman(1)`, `podman-create(1)`, `podman-start(1)` diff --git a/doc/toolbox.1.md b/doc/toolbox.1.md index 969b681..ff8bdf4 100644 --- a/doc/toolbox.1.md +++ b/doc/toolbox.1.md @@ -57,6 +57,10 @@ Create a new toolbox container. Enter a toolbox container for interactive use. +**toolbox-init-container(1)** + +Initialize a running container. + **toolbox-list(1)** List existing toolbox containers and images. diff --git a/toolbox b/toolbox index 6c04a30..d45e6c4 100755 --- a/toolbox +++ b/toolbox @@ -62,7 +62,8 @@ tab="$(printf '\t')" toolbox_command_path="" toolbox_container="" toolbox_container_default="" -toolbox_container_old="" +toolbox_container_old_v1="" +toolbox_container_old_v2="" toolbox_container_prefix_default="" toolbox_image="" user_id_real=$(id -ru 2>&3) @@ -233,123 +234,6 @@ ask_for_confirmation() ) -check_image_for_volumes() -( - image="$1" - - echo "$base_toolbox_command: checking if image $image has volumes for host bind mounts" >&3 - - if volumes=$($prefix_sudo podman inspect \ - --format "{{.Config.Volumes}}" \ - --type image \ - "$image" 2>&3); then - if echo "$volumes" | grep "/dev/dri\|/dev/fuse" >/dev/null 2>&3; then - echo "$base_toolbox_command: image $image has volumes for host bind mounts:" >&3 - echo "$base_toolbox_command: $volumes" >&3 - echo "$base_toolbox_command: consider re-creating image $image" >&3 - fi - fi -) - - -configure_working_container() -( - working_container_name="$1" - podman_create_supports_dns_none_no_hosts="$2" - - buildah_unshare_supports_sh_c=false - - echo "$base_toolbox_command: checking if 'buildah unshare' supports sh -c" >&3 - - if $prefix_sudo buildah unshare -- sh -c 'echo "hello world"' >/dev/null 2>&3; then - echo "$base_toolbox_command: 'buildah unshare' supports sh -c" >&3 - buildah_unshare_supports_sh_c=true - fi - - if [ "$(readlink /home)" = var/home ] ; then - need_home_link=true - else - need_home_link=false - fi - - if $need_home_link ; then - if ! $prefix_sudo buildah run "$working_container_name" -- \ - sh -c 'rmdir /home && mkdir -m 0755 -p /var/home && ln -s var/home /home' 2>&3; then - $prefix_sudo buildah rm "$working_container_name" >/dev/null 2>&3 - echo "$base_toolbox_command: failed to make /home a symlink" >&2 - return 1 - fi - fi - - if ! $prefix_sudo buildah copy "$working_container_name" \ - /etc/krb5.conf.d/kcm_default_ccache \ - /etc/krb5.conf.d >/dev/null 2>&3; then - $prefix_sudo buildah rm "$working_container_name" >/dev/null 2>&3 - echo "$base_toolbox_command: failed to set KCM as the default Kerberos credential cache" >&2 - return 1 - fi - - if ! $prefix_sudo buildah run "$working_container_name" -- useradd \ - --home-dir "$HOME" \ - --no-create-home \ - --shell "$SHELL" \ - --uid "$user_id_real" \ - --groups wheel \ - "$USER" \ - >/dev/null 2>&3; then - echo "$base_toolbox_command: failed to create user $USER with UID $user_id_real" >&2 - return 1 - fi - - if ! $prefix_sudo buildah run "$working_container_name" -- passwd -d "$USER" >/dev/null 2>&3; then - echo "$base_toolbox_command: failed to remove password for user $USER" >&2 - return 1 - fi - - if ! $prefix_sudo buildah run "$working_container_name" -- passwd -d root >/dev/null 2>&3; then - echo "$base_toolbox_command: failed to remove password for user root" >&2 - return 1 - fi - - if ! $prefix_sudo buildah config \ - --label "com.github.debarshiray.toolbox=true" \ - "$working_container_name" >/dev/null 2>&3; then - echo "$base_toolbox_command: failed to add com.github.debarshiray.toolbox=true" >&2 - return 1 - fi - - if $buildah_unshare_supports_sh_c && $podman_create_supports_dns_none_no_hosts; then - # shellcheck disable=SC2016 - if ! $prefix_sudo buildah unshare --\ - sh -c 'working_container_root=$(buildah mount "$1") '\ -' && cd "$working_container_root/etc" '\ -' && unlink hosts '\ -' && ln --symbolic /run/host/etc/hosts hosts '\ -' && buildah umount "$1"' \ - "/bin/sh" \ - "$working_container_name" >/dev/null 2>&3; then - echo "$base_toolbox_command: failed to redirect /etc/hosts to /run/host/etc/hosts" >&2 - return 1 - fi - - # shellcheck disable=SC2016 - if ! $prefix_sudo buildah unshare -- \ - sh -c 'working_container_root=$(buildah mount "$1") '\ -' && cd "$working_container_root/etc" '\ -' && unlink resolv.conf '\ -' && ln --symbolic /run/host/etc/resolv.conf resolv.conf '\ -' && buildah umount "$1"' \ - "/bin/sh" \ - "$working_container_name" >/dev/null 2>&3; then - echo "$base_toolbox_command: failed to redirect /etc/resolv.conf to /run/host/etc/resolv.conf" >&2 - return 1 - fi - fi - - return 0 -) - - container_name_is_valid() ( name="$1" @@ -730,11 +614,10 @@ create() dns_none="" kcm_socket="" kcm_socket_bind="" + monitor_host="" no_hosts="" - podman_create_supports_dns_none_no_hosts=false tmpfs_size=$((64 * 1024 * 1024)) # 64 MiB toolbox_profile_bind="" - working_container_name="toolbox-working-container-$(uuidgen --time)" # shellcheck disable=SC2153 if [ "$DBUS_SYSTEM_BUS_ADDRESS" != "" ]; then @@ -778,111 +661,30 @@ create() 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 - podman_create_supports_dns_none_no_hosts=true + dns_none="--dns none" no_hosts="--no-hosts" + + monitor_host="--monitor-host" fi - echo "$base_toolbox_command: checking if image $toolbox_image already exists" >&3 - - if ! $prefix_sudo podman image exists $toolbox_image >/dev/null 2>&3; then - 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: trying to create working container $working_container_name" >&3 - - if spinner_directory=$(mktemp --directory --tmpdir $spinner_template 2>&3); then - spinner_message="$base_toolbox_command: creating working 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 - - $prefix_sudo buildah from --name "$working_container_name" "$base_toolbox_image_full" >/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 working container" >&2 - return 1 - fi - - echo "$base_toolbox_command: trying to configure working container $working_container_name" >&3 - - if spinner_directory=$(mktemp --directory --tmpdir $spinner_template 2>&3); then - spinner_message="$base_toolbox_command: configuring working 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 - - configure_working_container \ - "$working_container_name" \ - "$podman_create_supports_dns_none_no_hosts" - ret_val=$? - - if [ "$spinner_directory" != "" ]; then - spinner_stop "$spinner_directory" - fi - - if [ $ret_val -ne 0 ]; then - $prefix_sudo buildah rm "$working_container_name" >/dev/null 2>&3 - return 1 - fi - - echo "$base_toolbox_command: trying to create image $toolbox_image" >&3 - - if spinner_directory=$(mktemp --directory --tmpdir $spinner_template 2>&3); then - spinner_message="$base_toolbox_command: creating image $toolbox_image: " - 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 buildah commit --rm "$working_container_name" "$toolbox_image" >/dev/null 2>&3 - ret_val=$? - - if [ "$spinner_directory" != "" ]; then - spinner_stop "$spinner_directory" - fi - - if [ $ret_val -ne 0 ]; then - $prefix_sudo buildah rm "$working_container_name" >/dev/null 2>&3 - echo "$base_toolbox_command: failed to create image $toolbox_image" >&2 - return 1 - fi - - echo "$base_toolbox_command: created image $toolbox_image" >&3 + if ! pull_base_toolbox_image; then + return 1 fi - check_image_for_volumes "$toolbox_image" + 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 @@ -954,8 +756,13 @@ create() --volume /mnt:/mnt:rslave \ --volume /run/media:/run/media:rslave \ --workdir "$HOME" \ - "$toolbox_image" \ - sleep +Inf >/dev/null 2>&3 + "$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 @@ -982,6 +789,94 @@ enter() } +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 </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" @@ -994,20 +889,19 @@ run() echo "$base_toolbox_command: checking if container $toolbox_container exists" >&3 - if ! toolbox_image=$($prefix_sudo podman inspect \ - --format "{{.ImageName}}" \ - --type container \ - $toolbox_container 2>&3); then + if ! $prefix_sudo podman container exists "$toolbox_container" 2>&3; then echo "$base_toolbox_command: container $toolbox_container not found" >&3 - if toolbox_image=$($prefix_sudo podman inspect \ - --format "{{.ImageName}}" \ - --type container \ - "$toolbox_container_old" 2>&3); then - echo "$base_toolbox_command: container $toolbox_container_old 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" + 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" @@ -1072,10 +966,6 @@ run() fi fi - echo "$base_toolbox_command: container $toolbox_container was created from image $toolbox_image" >&3 - - [ "$toolbox_image" != "" ] 2>&3 && check_image_for_volumes "$toolbox_image" - echo "$base_toolbox_command: trying to start container $toolbox_container" >&3 if ! $prefix_sudo podman start "$toolbox_container" >/dev/null 2>&3; then @@ -1490,18 +1380,16 @@ update_container_and_image_names() return 1 fi - echo "$base_toolbox_command: customized user-specific image is $toolbox_image" >&3 - # shellcheck disable=SC2031 if [ "$toolbox_container" = "" ]; then - toolbox_container=$(create_toolbox_container_name "$toolbox_image") + 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 $toolbox_image" >&2 + 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 $toolbox_image" >&2 + 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 @@ -1521,7 +1409,27 @@ update_container_and_image_names() return 1 fi - toolbox_container_old="$toolbox_image" + 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 @@ -1543,6 +1451,13 @@ usage() echo " [-r | --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]" @@ -1567,7 +1482,7 @@ if [ "$host_id" = "fedora" ] 2>&3; then else release_default="29" fi -toolbox_container_prefix_default="fedora-toolbox-$USER" +toolbox_container_prefix_default="fedora-toolbox" toolbox_container_default="$toolbox_container_prefix_default-$release_default" while has_prefix "$1" -; do @@ -1639,6 +1554,46 @@ if [ -f /run/.containerenv ] 2>&3; then 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 @@ -1719,6 +1674,11 @@ case $op in 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