#!/bin/sh
# SPDX-License-Identifier: GPL-3.0-only
#
# This file is part of the distrobox project:
#    https://github.com/89luca89/distrobox
#
# 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
#	DISTROBOX_ENTER_PATH
#	DISTROBOX_HOST_HOME

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=""
# Use DBX_HOST_HOME if defined, else fallback to HOME
#	DBX_HOST_HOME is set in case container is created
#	with custom --home directory
host_home="${DISTROBOX_HOST_HOME:-"${HOME}"}"
is_sudo=""
verbose=0
version="1.2.13"

# 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}

Usage:

	distrobox-export --app mpv [--extra-flags "flags"] [--delete] [--sudo]
	distrobox-export --service syncthing.service [--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-label/-el:	label to add to exported application name.
				Defaults to (on \$container_name)
	--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
		;;
	-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 "\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=$(uname -n | cut -d'.' -f1)
# Prefix to add to an existing command to work throught the container
container_command_prefix="${DISTROBOX_ENTER_PATH:-"distrobox-enter"} -T -n ${container_name} -- \"${is_sudo} "
if [ -z "${exported_app_label}" ]; then
	exported_app_label=" (on ${container_name})"
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
    ${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/$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/* || :)
	icon_files=$(find /usr/share/icons /usr/share/pixmaps -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|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
			# 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|^Exec=.*|&"|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
			# in the end we add the final quote we've opened in the "container_command_prefix"
		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
			# 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
