mirror of
https://github.com/89luca89/distrobox.git
synced 2026-08-19 01:14:49 -05:00
460 lines
14 KiB
Bash
Executable File
460 lines
14 KiB
Bash
Executable File
#!/bin/sh
|
|
# POSIX
|
|
# Expected env variables:
|
|
# HOME
|
|
# USER
|
|
# DISTROBOX_ENTER_PATH
|
|
|
|
trap '[ "$?" -ne 0 ] && printf "\nAn error occurred\n"' EXIT
|
|
|
|
# Defaults
|
|
export_action=""
|
|
exported_app=""
|
|
exported_bin=""
|
|
exported_delete=0
|
|
exported_service=""
|
|
extra_flags=""
|
|
is_sudo=""
|
|
verbose=0
|
|
version="1.2.2"
|
|
|
|
# We depend on some commands, let's be sure we have them
|
|
base_dependencies="basename grep sed find"
|
|
for dep in ${base_dependencies}; do
|
|
if ! command -v "${dep}" >/dev/null; then
|
|
printf >&2 "Missing dependency: %s\n" "${dep}"
|
|
exit 127
|
|
fi
|
|
done
|
|
|
|
# Print usage to stdout.
|
|
# Arguments:
|
|
# None
|
|
# Outputs:
|
|
# print usage with examples.
|
|
show_help() {
|
|
cat <<EOF
|
|
|
|
distrobox version: ${version}
|
|
|
|
distrobox-export takes care of exporting an app a binary or a service from the container
|
|
to the host.
|
|
|
|
The exported app will be easily available in your normal launcher and it will
|
|
automatically be launched from the container it is exported from.
|
|
|
|
The exported services will be available in the host's user's systemd session, so
|
|
|
|
systemctl --user status exported_service_name
|
|
|
|
will show the status of the service exported.
|
|
|
|
The exported binaries will be exported in the "--export-path" of choice as a wrapper
|
|
script that acts naturally both on the host and in the container.
|
|
Note that "--export-path" is NOT OPTIONAL, you have to explicitly set it.
|
|
|
|
You can specify additional flags to add to the command, for example if you want
|
|
to export an electron app, you could add the "--foreground" flag to the command:
|
|
|
|
distrobox-export --app atom --extra-flags "--foreground"
|
|
distrobox-export --bin /usr/bin/vim --export-path ~/.local/bin --extra-flags "-p"
|
|
distrobox-export --service syncthing --extra-flags "-allow-newer-config"
|
|
|
|
This works for services, binaries, and apps.
|
|
Extra flags are only used then the exported app, binary, or service is used from
|
|
the host, using them inside the container will not include them.
|
|
|
|
The option "--delete" will un-export an app, binary, or service.
|
|
|
|
distrobox-export --app atom --delete
|
|
distrobox-export --bin /usr/bin/vim --export-path ~/.local/bin --delete
|
|
distrobox-export --service syncthing --delete
|
|
distrobox-export --service nginx --delete
|
|
|
|
The option "--sudo" will launch the exported item as root inside the distrobox.
|
|
|
|
Note you can use --app OR --bin OR --service but not together.
|
|
|
|
distrobox-export --service nginx --sudo
|
|
|
|
Usage:
|
|
|
|
distrobox-export --app mpv [--extra-flags "flags"] [--delete] [--sudo]
|
|
distrobox-export --service syncthing [--extra-flags "flags"] [--delete] [--sudo]
|
|
distrobox-export --bin /path/to/bin --export-path ~/.local/bin [--extra-flags "flags"] [--delete] [--sudo]
|
|
|
|
|
|
Options:
|
|
|
|
--app/-a: name of the application to export
|
|
--bin/-b: absolute path of the binary to export
|
|
--service/-s: name of the service to export
|
|
--delete/-d: delete exported application or service
|
|
--export-path/-ep: path where to export the binary
|
|
--extra-flags/-ef: extra flags to add to the command
|
|
--sudo/-S: specify if the exported item should be ran as sudo
|
|
--help/-h: show this message
|
|
--verbose/-v: show more verbosity
|
|
--version/-V: show version
|
|
EOF
|
|
}
|
|
|
|
# Parse arguments
|
|
while :; do
|
|
case $1 in
|
|
-h | --help)
|
|
# Call a "show_help" function to display a synopsis, then exit.
|
|
show_help
|
|
exit 0
|
|
;;
|
|
-v | --verbose)
|
|
shift
|
|
verbose=1
|
|
;;
|
|
-V | --version)
|
|
printf "distrobox: %s\n" "${version}"
|
|
exit 0
|
|
;;
|
|
-a | --app)
|
|
if [ -n "$2" ]; then
|
|
export_action="app"
|
|
exported_app="$2"
|
|
shift
|
|
shift
|
|
fi
|
|
;;
|
|
-b | --bin)
|
|
if [ -n "$2" ]; then
|
|
export_action="bin"
|
|
exported_bin="$2"
|
|
shift
|
|
shift
|
|
fi
|
|
;;
|
|
-s | --service)
|
|
if [ -n "$2" ]; then
|
|
export_action="service"
|
|
exported_service="$2"
|
|
shift
|
|
shift
|
|
fi
|
|
;;
|
|
-S | --sudo)
|
|
is_sudo="sudo"
|
|
shift
|
|
;;
|
|
-ep | --export-path)
|
|
if [ -n "$2" ]; then
|
|
dest_path="$2"
|
|
shift
|
|
shift
|
|
fi
|
|
;;
|
|
-ef | --extra-flags)
|
|
if [ -n "$2" ]; then
|
|
extra_flags="$2"
|
|
shift
|
|
shift
|
|
fi
|
|
;;
|
|
-d | --delete)
|
|
exported_delete=1
|
|
shift
|
|
;;
|
|
*) # Default case: If no more options then break out of the loop.
|
|
break ;;
|
|
esac
|
|
done
|
|
|
|
set -o errexit
|
|
set -o nounset
|
|
# set verbosity
|
|
if [ "${verbose}" -ne 0 ]; then
|
|
set -o xtrace
|
|
fi
|
|
|
|
# Check we're running inside a container and not on the host
|
|
if [ ! -f /run/.containerenv ] && [ ! -f /.dockerenv ]; then
|
|
printf >&2 "You must run %s inside a container!\n" " $(basename "$0")"
|
|
exit 126
|
|
fi
|
|
|
|
# Ensure the foundamental variables are set and not empty, we will not proceed
|
|
# if they are not all set.
|
|
if [ -z "${exported_app}" ] &&
|
|
[ -z "${exported_bin}" ] &&
|
|
[ -z "${exported_service}" ]; then
|
|
printf >&2 "Error: Invalid arguments.\n"
|
|
printf >&2 "Error: missing export target. Run\n"
|
|
printf >&2 "\tdistrobox-export --help\n"
|
|
printf >&2 "for more informations.\n"
|
|
exit 2
|
|
fi
|
|
# Ensure we're not receiving more than one action at time.
|
|
if [ -n "${exported_app}" ] && [ -n "${exported_bin}" ] ||
|
|
[ -n "${exported_app}" ] && [ -n "${exported_service}" ] ||
|
|
[ -n "${exported_bin}" ] && [ -n "${exported_service}" ]; then
|
|
printf >&2 "Error: Invalid arguments, choose only one action below.\n"
|
|
printf >&2 "Error: You can only export one thing at time.\n"
|
|
exit 2
|
|
fi
|
|
# Ensure we have the export-path set when exporting a binary.
|
|
if [ -n "${exported_bin}" ] && [ -z "${dest_path}" ]; then
|
|
printf >&2 "Error: Missing argument export-path.\n"
|
|
exit 2
|
|
fi
|
|
|
|
# We can assume this as we set it the same as container name during creation.
|
|
container_name=$(cat /etc/hostname)
|
|
# Prefix to add to an existing command to work throught the container
|
|
container_command_prefix="${DISTROBOX_ENTER_PATH:-"distrobox-enter"} -H -n ${container_name} -- \"${is_sudo} "
|
|
|
|
# Print generated script from template
|
|
# Arguments:
|
|
# none it will use the ones set up globally
|
|
# Outputs:
|
|
# print generated script.
|
|
generate_script() {
|
|
cat <<EOF
|
|
#!/bin/sh
|
|
# distrobox_binary
|
|
# name: ${container_name}
|
|
if [ ! -f /run/.containerenv ]; then
|
|
${DISTROBOX_ENTER_PATH:-"distrobox-enter"} -n ${container_name} -- ${is_sudo} ${exported_bin} ${extra_flags} \$@
|
|
else
|
|
${exported_bin} \$@
|
|
fi
|
|
EOF
|
|
return $?
|
|
}
|
|
|
|
# Export binary to destination directory.
|
|
# the following function will use generate_script to create a shell script in
|
|
# dest_path that will execute the exported binary in the selected distrobox.
|
|
#
|
|
# Arguments:
|
|
# none it will use the ones set up globally
|
|
# Outputs:
|
|
# a generated_script in dest_path
|
|
# or error code.
|
|
export_binary() {
|
|
# Ensure the binary we're exporting is installed
|
|
if [ ! -f "${exported_bin}" ]; then
|
|
printf >&2 "Error: cannot find %s.\n" "${exported_bin}"
|
|
return 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 ]; then
|
|
if [ ! -f "${dest_file}" ]; then
|
|
printf >&2 "Error: cannot find %s in %s.\nWas it exported?.\n" "$(basename "${exported_bin}")" "${dest_path}"
|
|
return 1
|
|
fi
|
|
if grep -q "distrobox_binary" "${dest_file}"; then
|
|
if rm -f "${dest_file}"; then
|
|
printf "%s from %s removed successfully from %s.\nOK!\n" "${exported_bin}" "${container_name}" "${dest_path}"
|
|
return 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}"
|
|
return 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}"
|
|
return 1
|
|
fi
|
|
|
|
# create the script from template and write to file
|
|
if generate_script >"${dest_file}"; then
|
|
chmod +x "${dest_file}"
|
|
printf "%s from %s exported successfully in %s.\nOK!\n" "${exported_bin}" "${container_name}" "${dest_path}"
|
|
return 0
|
|
fi
|
|
# Unknown error.
|
|
printf >&2 "A problem occurred.\n"
|
|
return 3
|
|
}
|
|
|
|
# Export graphical application to the host.
|
|
# the following function will scan the distrobox for desktop and icon files for
|
|
# the selected application. It will then put the needed icons in the host's icons
|
|
# directory and create a new .desktop file that will execute the selected application
|
|
# in the distrobox.
|
|
#
|
|
# Arguments:
|
|
# none it will use the ones set up globally
|
|
# Outputs:
|
|
# needed icons in /run/host/$HOME/.local/share/icons
|
|
# needed desktop files in /run/host/$HOME/.local/share/applications
|
|
# or error code.
|
|
export_application() {
|
|
# 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"
|
|
return 127
|
|
fi
|
|
|
|
# Find desktop file for the application to export
|
|
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"
|
|
return 127
|
|
fi
|
|
|
|
# create applications dir if not existing
|
|
if [ ! -d "/run/host/${HOME}/.local/share/applications" ]; then
|
|
mkdir -p "/run/host/${HOME}/.local/share/applications"
|
|
fi
|
|
|
|
# copy icons in home directory
|
|
for icon_file in ${icon_files}; do
|
|
icon_home_directory="$(dirname "${icon_file}" | sed "s|/usr/share/|\/run\/host\/${HOME}/.local/share/|g")"
|
|
|
|
# check if we're exporting or deleting
|
|
if [ "${exported_delete}" -ne 0 ]; then
|
|
# we need to remove, not export
|
|
rm -f "${icon_home_directory}"/"$(basename "${icon_file}")"
|
|
else
|
|
# we wanto to export the application's icons
|
|
mkdir -p "${icon_home_directory}"
|
|
cp "${icon_file}" "${icon_home_directory}"
|
|
fi
|
|
done
|
|
|
|
# create desktop files for the distrobox
|
|
for desktop_file in ${desktop_files}; do
|
|
desktop_home_file="$(basename "${desktop_file}")"
|
|
# 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"
|
|
return 1
|
|
fi
|
|
rm -f "/run/host/${HOME}/.local/share/applications/${desktop_home_file}"
|
|
else
|
|
# Add commmand_prefix
|
|
# Add extra flags
|
|
# Add closing quote
|
|
# If a TryExec is present, we have to fake it as it will not work
|
|
# throught the container separation
|
|
sed "s|^Exec=|Exec=${container_command_prefix} |g" "${desktop_file}" |
|
|
sed "s|\(%.*\)|${extra_flags} \1|g" |
|
|
sed "s|\(%.*\)|\" \1|g" |
|
|
sed "s|^TryExec=.*|TryExec=true|g" \
|
|
>"/run/host/${HOME}/.local/share/applications/${desktop_home_file}"
|
|
# in the end we add the final quote we've opened in the "container_command_prefix"
|
|
fi
|
|
done
|
|
|
|
printf "Application %s successfully exported.\nOK!\n" "${exported_app}"
|
|
printf "%s will appear in your applications list in a few seconds.\n" "${exported_app}"
|
|
|
|
}
|
|
|
|
# Export systemd service to the host.
|
|
# the following function will export a selected systemd unit from the distrobox
|
|
# to the host. It will modify the original unit to include the container_command_prefix.
|
|
#
|
|
# Arguments:
|
|
# none it will use the ones set up globally
|
|
# Outputs:
|
|
# new systemd unit in /run/host/$HOME/.config/systemd/user/
|
|
# or error code.
|
|
export_service() {
|
|
|
|
# find the service file in the common
|
|
service_file=$(find \
|
|
/etc/systemd/system/ /lib/systemd/system/ /usr/lib/systemd/system/ "${HOME}"/.config/systemd/user/ \
|
|
-type f -name "${exported_service}*" 2>/dev/null | tail -1)
|
|
# Check that we found some service files first.
|
|
if [ -z "${service_file}" ]; then
|
|
printf >&2 "Error: cannot find any service file for %s.\n" "${exported_service}"
|
|
printf >&2 "Error: trying to export a non-installed service.\n"
|
|
return 127
|
|
fi
|
|
|
|
# this is the output file we will produce.
|
|
exported_service_file="${container_name}-$(basename "${service_file}")"
|
|
exported_service_fullpath="/run/host/${HOME}/.config/systemd/user/${exported_service_file}"
|
|
|
|
# If we're deleting it, just do it and exit
|
|
if [ "${exported_delete}" -ne 0 ]; then
|
|
if [ ! -f "${exported_service_fullpath}" ]; then
|
|
printf >&2 "Error: cannot find service %s.\nWas it exported?.\n" "${exported_service_file}"
|
|
return 1
|
|
fi
|
|
rm -f "${exported_service_fullpath}"
|
|
printf "Service %s successfully removed.\nOK!\n" "${exported_service_file}"
|
|
return 0
|
|
fi
|
|
# Check if it is already exported
|
|
if [ -f "${exported_service_fullpath}" ] &&
|
|
grep -q "${container_command_prefix}" "${exported_service_fullpath}"; then
|
|
|
|
printf "Service %s is already exported.\n\n" "${exported_service_file}"
|
|
printf "\nTo check the status, run:\n\tsystemctl --user status %s \n" "${exported_service_file}"
|
|
printf "\nTo start it, run:\n\tsystemctl --user start %s \n" "${exported_service_file}"
|
|
printf "\nTo start it at login, run:\n\tsystemctl --user enable %s \n" "${exported_service_file}"
|
|
return 0
|
|
fi
|
|
|
|
# Create temp file with random name
|
|
temp_file="$(mktemp -u)"
|
|
# Replace all Exec occurrencies
|
|
if [ ! -d "/run/host/${HOME}/.config/systemd/user/" ]; then
|
|
mkdir -p "/run/host/${HOME}/.config/systemd/user/"
|
|
fi
|
|
cat "${service_file}" >"${exported_service_fullpath}" 2>/dev/null
|
|
for exec_cmd in ExecStart ExecStartPre ExecStartPost ExecReload ExecStop ExecStopPost; do
|
|
# Save to temp file
|
|
cat "${exported_service_fullpath}" >"${temp_file}" 2>/dev/null
|
|
# Add prefix only if not present
|
|
if ! grep "${exec_cmd}" "${temp_file}" | grep -q "${container_command_prefix}"; then
|
|
# Add commmand_prefix
|
|
# Add extra flags
|
|
# Add closing quote
|
|
sed "s|^${exec_cmd}=|${exec_cmd}=${container_command_prefix}|g" "${temp_file}" |
|
|
sed "s|^${exec_cmd}=.*|& ${extra_flags}|g" |
|
|
sed "s|^${exec_cmd}=.*|&\"|g" >"${exported_service_fullpath}"
|
|
# in the end we add the final quote we've opened in the "container_command_prefix"
|
|
fi
|
|
done
|
|
# Cleanup
|
|
rm -f "${temp_file}"
|
|
|
|
printf "Service %s successfully exported.\nOK\n" "${exported_service_file}"
|
|
printf "%s will appear in your services list in a few seconds.\n\n" "${exported_service_file}"
|
|
printf "\nTo check the status, run:\n\tsystemctl --user status %s \n" "${exported_service_file}"
|
|
printf "\nTo start it, run:\n\tsystemctl --user start %s \n" "${exported_service_file}"
|
|
printf "\nTo start it at login, run:\n\tsystemctl --user enable %s \n" "${exported_service_file}"
|
|
|
|
return 0
|
|
}
|
|
|
|
# Main routine
|
|
case "${export_action}" in
|
|
app)
|
|
export_application
|
|
;;
|
|
bin)
|
|
export_binary
|
|
;;
|
|
service)
|
|
export_service
|
|
;;
|
|
*)
|
|
printf >&2 "Invalid arguments, choose an action below.\n"
|
|
show_help
|
|
exit 2
|
|
;;
|
|
esac
|