Created
July 7, 2024 07:17
-
-
Save nelsonaloysio/c92066d770db499301bf642313cf0fa2 to your computer and use it in GitHub Desktop.
Export toolbox applications the same way as with distrobox.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env sh | |
# SPDX-License-Identifier: GPL-3.0-only | |
# | |
# This file is part of the distrobox project: | |
# https://github.com/89luca89/distrobox | |
# | |
# Just modified to work with toolbox: | |
# https://github.com/containers/toolbox | |
# | |
# Copyright (C) 2021 distrobox contributors | |
# | |
# distrobox is free software; you can redistribute it and/or modify it | |
# under the terms of the GNU General Public License version 3 | |
# as published by the Free Software Foundation. | |
# | |
# distrobox is distributed in the hope that it will be useful, but | |
# WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
# General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with distrobox; if not, see <http://www.gnu.org/licenses/>. | |
# POSIX | |
# Expected env variables: | |
# HOME | |
# USER | |
trap '[ "$?" -ne 0 ] && printf "\nAn error occurred\n"' EXIT | |
# Defaults | |
export_action="" | |
exported_app="" | |
exported_app_label="" | |
exported_bin="" | |
exported_delete=0 | |
exported_service="" | |
extra_flags="" | |
host_home="${HOME}" | |
is_sudo="" | |
verbose=0 | |
version="1.2.15" | |
# 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 | |
# We can assume this as we set it the same as container name during creation. | |
container_cmd="" | |
container_name="toolbox" # $(uname -n | cut -d'.' -f1) | |
# Prefix to add to an existing command to work throught the container | |
container_command_prefix="toolbox run ${container_cmd} ${is_sudo} " | |
if [ -z "${exported_app_label}" ]; then | |
exported_app_label="(on ${container_name})" | |
fi | |
# Print usage to stdout. | |
# Arguments: | |
# None | |
# Outputs: | |
# print usage with examples. | |
show_help() { | |
cat << EOF | |
$(toolbox --version) (distrobox version ${version}) | |
Usage: | |
toolbox-export --app mpv [--extra-flags "flags"] [--delete] [--sudo] | |
toolbox-export --service syncthing.service [--extra-flags "flags"] [--delete] [--sudo] | |
toolbox-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 | |
--container/-c: container name instead of default | |
--home/-h: home path instead of default | |
--delete/-d: delete exported application or service | |
--export-label/-el: label to add to exported application name. | |
Defaults to (on <container>) | |
--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 "$(toolbox --version) (distrobox version %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 | |
;; | |
-c | --container) | |
if [ -n "$2" ]; then | |
container_name="toolbox-$2" | |
container_cmd="-c $container_name" | |
shift | |
shift | |
fi | |
;; | |
-h | --home) | |
if [ -n "$2" ]; then | |
host_home="$2" | |
shift | |
shift | |
fi | |
;; | |
-S | --sudo) | |
is_sudo="sudo" | |
shift | |
;; | |
-el | --export-label) | |
if [ -n "$2" ]; then | |
exported_app_label="$2" | |
shift | |
shift | |
fi | |
;; | |
-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 | |
# We're working with HOME, so we must run as USER, not as root. | |
if [ "$(id -u)" -eq 0 ]; then | |
printf >&2 "You must not run %s as root!\n" "$(basename "$0")" | |
exit 1 | |
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 "\ttoolbox-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 | |
# 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 | |
toolbox run ${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/$host_home/.local/share/icons | |
# needed desktop files in /run/host/$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/* \ | |
/var/lib/flatpak/exports/share/applications/* 2> /dev/null || :) | |
icon_files=$(find \ | |
/usr/share/icons \ | |
/usr/share/pixmaps \ | |
/var/lib/flatpak/exports/share/icons -iname "*${exported_app}*" 2> /dev/null || :) | |
# 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/${host_home}/.local/share/applications" ]; then | |
mkdir -p "/run/host/${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\/${host_home}/.local/share/|g" | | |
sed "s|/var/lib/flatpak/exports/share|\/run\/host\/${host_home}/.local/share/|g" | | |
sed "s|pixmaps|icons|g")" | |
# check if we're exporting or deleting | |
if [ "${exported_delete}" -ne 0 ]; then | |
# we need to remove, not export | |
rm -rf "${icon_home_directory:?}"/"$(basename "${icon_file:?}")" | |
else | |
# we wanto to export the application's icons | |
mkdir -p "${icon_home_directory}" | |
cp -r "${icon_file}" "${icon_home_directory}" | |
fi | |
done | |
# create desktop files for the distrobox | |
for desktop_file in ${desktop_files}; do | |
desktop_original_file="$(basename "${desktop_file}")" | |
desktop_home_file="${container_name}-$(basename "${desktop_file}")" | |
# check if we're exporting or deleting | |
if [ "${exported_delete}" -ne 0 ]; then | |
if [ ! -f "/run/host/${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/${host_home}/.local/share/applications/${desktop_original_file}" | |
rm -f "/run/host/${host_home}/.local/share/applications/${desktop_home_file}" | |
else | |
# Add commmand_prefix | |
# Add extra flags | |
# 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|^TryExec=.*|TryExec=true|g" | | |
sed "s|Name.*|& ${exported_app_label}|g" > \ | |
"/run/host/${host_home}/.local/share/applications/${desktop_home_file}" | |
if ! grep -q "StartupWMClass" "/run/host/${host_home}/.local/share/applications/${desktop_home_file}"; then | |
printf "StartupWMClass=%s\n" "${exported_app}" >> \ | |
"/run/host/${host_home}/.local/share/applications/${desktop_home_file}" | |
fi | |
fi | |
done | |
if [ "${exported_delete}" -ne 0 ]; then | |
printf "Application %s successfully un-exported.\nOK!\n" "${exported_app}" | |
printf "%s will disappear from your applications list in a few seconds.\n" "${exported_app}" | |
else | |
printf "Application %s successfully exported.\nOK!\n" "${exported_app}" | |
printf "%s will appear in your applications list in a few seconds.\n" "${exported_app}" | |
fi | |
} | |
# 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/$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/ \ | |
"${host_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/${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/${host_home}/.config/systemd/user/" ]; then | |
mkdir -p "/run/host/${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 | |
sed "s|^${exec_cmd}=|${exec_cmd}=${container_command_prefix}|g" "${temp_file}" | | |
sed "s|^${exec_cmd}=.*|& ${extra_flags}|g" > \ | |
"${exported_service_fullpath}" | |
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment