Skip to content

Commit

Permalink
create: improve output and messages. Check if the named distrobox alr…
Browse files Browse the repository at this point in the history
…eady exists.

create: mount host's root filesystem instead of single folders
export: improve output and messages. Check if exports already exists and
are valid. Export service to named services.
Fix #27
  • Loading branch information
89luca89 committed Dec 12, 2021
1 parent fccead6 commit 3089e20
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 42 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

# Distrobox

Use any linux distribution inside your terminal.
Distrobox uses `podman` to create containers using the linux distribution of your choice.
Use any linux distribution inside your terminal.
Distrobox uses `podman` to create containers using the linux distribution of your choice.
Created container will be tightly integrated with the host, allowing to share
the HOME directory of the user, external storage, external usb devices and
graphical apps (X11/Wayland) and audio.
Expand Down Expand Up @@ -42,7 +42,7 @@ graphical apps (X11/Wayland) and audio.

Simply put it's a fancy `podman` wrapper to create and start containers highly integrated with the hosts.

The distrobox environment is based on an OCI image.
The distrobox environment is based on an OCI image.
This image is used to create a container that seamlessly integrates with the rest of the operating system by providing access to the user's home directory,
the Wayland and X11 sockets, networking, removable devices (like USB sticks), systemd journal, SSH agent, D-Bus,
ulimits, /dev and the udev database, etc..
Expand Down Expand Up @@ -136,28 +136,28 @@ Distrobox guests tested successfully with the following container images:
| Distro | Version | Images |
| --- | --- | --- |
| AlmaLinux | 8 | docker.io/library/almalinux:8 |
| Alpine Linux | 3.14, 3.15 | docker.io/library/alpine:latest |
| Alpine Linux | 3.14 </br> 3.15 | docker.io/library/alpine:latest |
| AmazonLinux | 2 | docker.io/library/amazonlinux:2.0.20211005.0 |
| Archlinux | | docker.io/library/archlinux:latest |
| Centos | 7 | quay.io/centos/centos:7 |
| Centos | 8 | quay.io/centos/centos:8 |
| Debian | 11 | docker.io/library/debian:stable, docker.io/library/debian:stable-backports |
| Debian | Testing | docker.io/library/debian:testing, docker.io/library/debian:testing-backports |
| Debian | 11 | docker.io/library/debian:stable </br> docker.io/library/debian:stable-backports |
| Debian | Testing | docker.io/library/debian:testing </br> docker.io/library/debian:testing-backports |
| Debian | Unstable | docker.io/library/debian:unstable |
| Neurodebian | nd100 | docker.io/library/neurodebian:nd100 |
| Fedora | 34 | registry.fedoraproject.org/fedora-toolbox:34, docker.io/library/fedora:34 |
| Fedora | 35 | registry.fedoraproject.org/fedora-toolbox:35, docker.io/library/fedora:35 |
| Fedora | 34 | registry.fedoraproject.org/fedora-toolbox:34 </br> docker.io/library/fedora:34 |
| Fedora | 35 | registry.fedoraproject.org/fedora-toolbox:35 </br> docker.io/library/fedora:35 |
| Mageia | 8 | docker.io/library/mageia |
| Opensuse | Leap | registry.opensuse.org/opensuse/leap:latest |
| Opensuse | Tumbleweed | registry.opensuse.org/opensuse/tumbleweed:latest, registry.opensuse.org/opensuse/toolbox:latest |
| Opensuse | Tumbleweed | registry.opensuse.org/opensuse/tumbleweed:latest </br> registry.opensuse.org/opensuse/toolbox:latest |
| Oracle Linux | 7 | container-registry.oracle.com/os/oraclelinux:7 |
| Oracle Linux | 8 | container-registry.oracle.com/os/oraclelinux:8 |
| Rocky Linux | 8 | docker.io/rockylinux/rockylinux:8 |
| Scientific Linux | 7 | docker.io/library/sl:7 |
| Ubuntu | 20.04 | docker.io/library/ubuntu:20.04 |
| Ubuntu | 21.10 | docker.io/library/ubuntu:21.10 |
| Kali Linux | rolling | docker.io/kalilinux/kali-rolling:latest |
| Void Linux | | ghcr.io/void-linux/void-linux:latest-thin-bb-x86_64, ghcr.io/void-linux/void-linux:latest-thin-bb-x86_64-musl, ghcr.io/void-linux/void-linux:latest-full-x86_64, ghcr.io/void-linux/void-linux:latest-full-x86_64-musl |
| Void Linux | | ghcr.io/void-linux/void-linux:latest-thin-bb-x86_64 </br> ghcr.io/void-linux/void-linux:latest-thin-bb-x86_64-musl </br> ghcr.io/void-linux/void-linux:latest-full-x86_64 </br> ghcr.io/void-linux/void-linux:latest-full-x86_64-musl |


Note however that if you use a non-toolbox pre configured image (e.g. images pre-baked to work with https://github.com/containers/toolbox), the **first** `distrobox-enter` you'll perform
Expand Down
36 changes: 21 additions & 15 deletions distrobox-create
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ generate_command() {
--pid host
--privileged
--security-opt label=disable
--ulimit host
--user root:root
--userns keep-id
--ulimit host
--volume ${distrobox_entrypoint_path}:/usr/bin/entrypoint:ro"

# let's check if we can include distrobox-export or not
Expand All @@ -140,15 +140,8 @@ generate_command() {
# mount dev inside container. This grants access to external devices
# like usb webcams, disks and so on.
result_command="${result_command} --volume /dev:/dev:rslave"

# useful mounts from host to the container using /run/host as a base
host_directories="/ /etc /mnt /run /tmp /usr /var"
for host_directory in ${host_directories}; do
# Check if the directory exists first
if [ -d "${host_directory}" ]; then
result_command="${result_command} --volume ${host_directory}:/run/host${host_directory}:rslave"
fi
done
result_command="${result_command} --volume /:/run/host:rslave"
result_command="${result_command} --mount type=devpts,destination=/dev/pts"

# those are dynamic configs needed by the container to function properly
# and integrate with the host
Expand All @@ -165,15 +158,14 @@ generate_command() {
if [ -d "/run/user/${container_user_uid}" ]; then
result_command="${result_command} --volume /run/user/${container_user_uid}:/run/user/${container_user_uid}"
fi
result_command="${result_command} --mount type=devpts,destination=/dev/pts"

# find all the user's socket and mount them inside the container
# this will allow for continuity of functionality between host and container
# for example using `podman --remote` to control the host's podman from inside
# the container
host_sockets="$(find /run -iname "*socket" ! -path "/run/user/*" 2>/dev/null || :)"
for socket in ${host_sockets}; do
result_command="${result_command} --volume ${socket}:${socket}"
for host_socket in ${host_sockets}; do
result_command="${result_command} --volume ${host_socket}:${host_socket}"
done

# now execute the entrypoint, refer to `distrobox-init -h` for instructions
Expand Down Expand Up @@ -207,13 +199,27 @@ if [ -z "$(podman images -q "${container_image}")" ]; then
exit 0
;;
*) # Default case: If no more options then break out of the loop.
printf >&2 "The available choices are: y,Y,Yes,yes,n,N,No,no. Exiting.\n"
printf >&2 "Invalid input.\n"
printf >&2 "The available choices are: y,Y,Yes,yes or n,N,No,no.\nExiting.\n"
exit 1
;;
esac
fi

# Check if the container already exists.
if podman ps -a | grep -q "${container_name}\$"; then
printf "Distrobox named '%s' already exists." "${container_name}"
printf "To enter, run:\n"
printf "\tdistrobox-enter --name %s\n" "${container_name}"
exit 0
fi

# Generate the command and execute
cmd="$(generate_command)"
# Eval the generated command. If successful display an helpful message.
# shellcheck disable=SC2086
eval ${cmd}
if eval ${cmd}; then
printf "Distrobox '%s' successfully created." "${container_name}"
printf "To enter, run:\n"
printf "\tdistrobox-enter --name %s\n" "${container_name}"
fi
84 changes: 67 additions & 17 deletions distrobox-export
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ else
${exported_bin} \$@
fi
EOF
return $?
}

# Parse arguments
Expand Down Expand Up @@ -181,19 +182,19 @@ fi
if [ -z "${exported_app}" ] &&
[ -z "${exported_bin}" ] &&
[ -z "${exported_service}" ]; then
printf >&2 "Invalid arguments, choose an action below\n"
printf >&2 "Invalid arguments, choose an action below.\n"
show_help
exit 2
fi
if [ -n "${exported_app}" ] &&
[ -n "${exported_bin}" ] &&
[ -n "${exported_service}" ]; then
printf >&2 "Invalid arguments, choose only one action below\n"
printf >&2 "Invalid arguments, choose only one action below.\n"
show_help
exit 2
fi
if [ -n "${exported_bin}" ] && [ -z "${dest_path}" ]; then
printf >&2 "Missing argument export-path\n"
printf >&2 "Missing argument export-path.\n"
exit 2
fi

Expand All @@ -214,41 +215,63 @@ if [ -n "${exported_bin}" ]; then

# Ensure the binary we're exporting is installed
if [ ! -f "${exported_bin}" ]; then
printf >&2 "Error: cannot find %s\n" "${exported_bin}"
printf >&2 "Error: cannot find %s.\n" "${exported_bin}"
exit 127
fi
# generate dest_file path
dest_file="${dest_path}/$(basename "${exported_bin}")"

# If we're deleting it, just do it and exit
if [ "${exported_delete}" -ne 0 ] && grep -q "distrobox_binary" "${dest_file}"; then
rm -f "${dest_file}"
exit 0
if [ "${exported_delete}" -ne 0 ]; then
if [ ! -f "${dest_file}" ]; then
printf >&2 "Error: cannot find %s in %s.\nWas it exported?.\n" "${dest_file}" "${dest_path}"
exit 1
fi
if grep -q "distrobox_binary" "${dest_file}"; then
if rm -f "${dest_file}"; then
printf "OK!\n%s exported from %s removed successfully from %s.\n" "${exported_bin}" "${container_name}" "${dest_path}"
exit 0
fi
else
printf >&2 "Error: %s exists but it's not a distrobox exported file.\n" "${dest_file}"
printf >&2 "Error: cannot delete: %s.\n" "${dest_file}"
exit 2
fi
fi

# test if we have writing rights on the file
if ! touch "${dest_file}"; then
printf >&2 "Error: cannot create destination file %s\n" "${dest_file}"
printf >&2 "Error: cannot create destination file %s.\n" "${dest_file}"
exit 1
fi

# create the script from template and write to file
generate_script "${container_name}" "${exported_bin}" >"${dest_file}"
chmod +x "${dest_file}"
if generate_script "${container_name}" "${exported_bin}" >"${dest_file}"; then
chmod +x "${dest_file}"
printf "OK!\n%s from %s exported successfully in %s.\n" "${exported_bin}" "${container_name}" "${dest_path}"
exit 0
fi

exit 0
printf >&2 "A problem occurred.\n"
exit 3

elif [ -n "${exported_app}" ]; then
# Work on a desktop app export

# Ensure the app we're exporting is installed
if ! command -v "${exported_app}" >/dev/null; then
printf >&2 "Error: trying to export a non-installed application\n"
printf >&2 "Error: trying to export a non-installed application.\n"
exit 127
fi
# Find desktop file for the application to export
desktop_files=$(grep -ril "${exported_app}" /usr/share/applications/*)
desktop_files=$(grep -ril "${exported_app}" /usr/share/applications/* || :)
icon_files=$(find /usr/share/icons -iname "*${exported_app}*")
# Check that we found some desktop files first.
if [ -z "${desktop_files}" ]; then
printf >&2 "Error: cannot find any desktop files.\n"
printf >&2 "Error: trying to export a non-installed application.\n"
exit 127
fi

# copy icons in home directory
for icon_file in ${icon_files}; do
Expand All @@ -275,6 +298,10 @@ elif [ -n "${exported_app}" ]; then
fi
# check if we're exporting or deleting
if [ "${exported_delete}" -ne 0 ]; then
if [ ! -f "/run/host/${HOME}/.local/share/applications/${desktop_home_file}" ]; then
printf >&2 "Error: trying to remove a non-exported application.\n"
exit 1
fi
rm -f "/run/host/${HOME}/.local/share/applications/${desktop_home_file}"
else
# If a TryExec is present, we have to fake it as it will not work throught the
Expand All @@ -286,7 +313,8 @@ elif [ -n "${exported_app}" ]; then
fi
done

exit 0
printf "OK!\nApplication %s successfully exported.\n" "${exported_app}"
printf "%s will appear in your applications list in a few seconds.\n" "${exported_app}"

elif [ -n "${exported_service}" ]; then
# Work on a service export
Expand All @@ -299,21 +327,37 @@ elif [ -n "${exported_service}" ]; then
# Ensure we're working with fresh data
systemctl --user daemon-reload
# Fetch original service file
service_file="/run/host/${HOME}/.config/systemd/user/${exported_service}.service"
service_file="/run/host/${HOME}/.config/systemd/user/${exported_service}-${container_name}.service"

# If we're deleting it, just do it and exit
if [ "${exported_delete}" -ne 0 ]; then
rm -f "/run/host/${HOME}/.config/systemd/user/${exported_service}.service"
if [ ! -f "${service_file}" ]; then
printf >&2 "Error: cannot find service %s.\nWas it exported?.\n" "${exported_service}-${container_name}"
exit 1
fi
rm -f "${service_file}"
systemctl --user daemon-reload
printf "OK!\nService %s successfully removed.\n" "${exported_service}-${container_name}"
exit 0
fi
# Check if it is already exported
if [ -f "${service_file}" ] &&
grep -q "${container_command_prefix}" "${service_file}"; then

printf "Service %s is already exported.\n\n" "${exported_service}-${container_name}"
printf "To check the status, run:\n\tsystemctl --user status %s \n" "${exported_service}-${container_name}.service"
printf "To start it, run:\n\tsystemctl --user start %s \n" "${exported_service}-${container_name}.service"
printf "To start it at login, run:\n\tsystemctl --user enable %s \n" "${exported_service}-${container_name}.service"
exit 0
fi

# Create temp file with random name
temp_file="$(mktemp -u)"
# Replace all Exec occurrencies
systemctl --user cat "${exported_service}.service" >"${service_file}" 2>/dev/null
for exec_cmd in ExecStart ExecStartPre ExecStartPost ExecReload ExecStop ExecStopPost; do
# Save to temp file
systemctl --user cat "${exported_service}.service" >"${temp_file}" 2>/dev/null
systemctl --user cat "${exported_service}-${container_name}.service" >"${temp_file}" 2>/dev/null
# Add prefix only if not present
if ! grep "${exec_cmd}" "${temp_file}" | grep -q "${container_command_prefix}"; then
tail -n+2 "${temp_file}" |
Expand All @@ -326,5 +370,11 @@ elif [ -n "${exported_service}" ]; then
# Reload
systemctl --user daemon-reload

printf "OK!\nService %s successfully exported.\n" "${exported_service}-${container_name}"
printf "%s will appear in your services list in a few seconds.\n\n" "${exported_service}-${container_name}"
printf "To check the status, run:\n\tsystemctl --user status %s \n" "${exported_service}-${container_name}.service"
printf "To start it, run:\n\tsystemctl --user start %s \n" "${exported_service}-${container_name}.service"
printf "To start it at login, run:\n\tsystemctl --user enable %s \n" "${exported_service}-${container_name}.service"

exit 0
fi

0 comments on commit 3089e20

Please sign in to comment.