#!/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
#	SHELL

trap '[ "$?" -ne 0 ] && printf "Error: An error occurred\n"' EXIT

# Redirect stderr to stdout as podman by default logs stderr as priority 3 journald errors.
# Github issue: https://github.com/containers/podman/issues/20728
exec 2>&1

# We'll also bind mount READ-WRITE useful mountpoints to pass external drives and libvirt from
# the host to the container
HOST_MOUNTS="
	/etc/host.conf
	/etc/machine-id
	/media
	/mnt
	/run/libvirt
	/run/media
	/run/netconfig/
	/run/systemd/journal
	/run/systemd/resolve/
	/run/systemd/seats
	/run/systemd/sessions
	/run/systemd/users
	/run/udev
	/var/lib/libvirt
	/var/mnt"

# We'll also bind mount in READ-ONLY useful directories from the host
HOST_MOUNTS_RO="
	/etc/localtime
	/var/lib/systemd/coredump
	/var/log/journal"

HOST_MOUNTS_RO_INIT="
	/etc/localtime
	/run/systemd/journal
	/run/systemd/resolve
	/run/systemd/seats
	/run/systemd/sessions
	/run/systemd/users
	/var/lib/systemd/coredump
	/var/log/journal"

# Defaults
container_additional_packages=""
init=0
init_hook=""
nvidia=0
pre_init_hook=""
rootful=0
upgrade=0
verbose=0
version="1.8.2.5"

# show_help will print usage to stdout.
# Arguments:
#   None
# Expected global variables:
#   version: distrobox version
# Expected env variables:
#   USER
#   HOME
# Outputs:
#   print usage with examples.
show_help()
{
	cat << EOF
distrobox version: ${version}

Usage:

	distrobox-init --name ${USER} --user $(id -ru) --group $(id -rg) --home ${HOME}

Options:

	--name/-n:		user name
	--user/-u:		uid of the user
	--group/-g:		gid of the user
	--home/-d:		path/to/home of the user
	--help/-h:		show this message
	--additional-packages:	packages to install in addition
	--init/-I:		whether to use or not init
	--pre-init-hooks:	commands to execute prior to init
	--nvidia:		try to integrate host's nVidia drivers in the guest
	--upgrade/-U:		run init in upgrade mode
	--verbose/-v:		show more verbosity
	--version/-V:		show version
	--:			end arguments execute the rest as command to execute during init
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
			;;
		-U | --upgrade)
			shift
			upgrade=1
			;;
		-n | --name)
			if [ -n "$2" ]; then
				container_user_name="$2"
				shift
				shift
			fi
			;;
		-i | --init)
			if [ -n "$2" ]; then
				init="$2"
				shift
				shift
			fi
			;;
		-d | --home)
			if [ -n "$2" ]; then
				container_user_home="$2"
				shift
				shift
			fi
			;;
		-u | --user)
			if [ -n "$2" ]; then
				container_user_uid="$2"
				shift
				shift
			fi
			;;
		-g | --group)
			if [ -n "$2" ]; then
				container_user_gid="$2"
				shift
				shift
			fi
			;;
		--pre-init-hooks)
			if [ -n "$2" ]; then
				pre_init_hook="$2"
			fi
			shift
			shift
			;;
		--additional-packages)
			if [ -n "$2" ]; then
				container_additional_packages="$2"
			fi
			shift
			shift
			;;
		--nvidia)
			if [ -n "$2" ]; then
				nvidia="$2"
				shift
				shift
			fi
			;;
		--)
			shift
			init_hook=$*
			break
			;;
		-*) # Invalid options.
			printf >&2 "Error: Invalid flag '%s'\n\n" "$1"
			show_help
			exit 1
			;;
		*) # Default case: If no more options then break out of the loop.
			break ;;
	esac
done

# Check we're running inside a container and not on the host
if [ ! -f /run/.containerenv ] && [ ! -f /.dockerenv ] && [ -z "${container:-}" ]; then
	printf >&2 "You must run %s inside a container!\n" " $(basename "$0")"
	printf >&2 "distrobox-init should only be used as an entrypoint for a distrobox!\n\n"
	printf >&2 "This is not intended to be used manually, but instead used by distrobox-enter\n"
	printf >&2 "to set up the container's entrypoint.\n"
	exit 126
fi

# Ensure the foundamental variables are set and not empty, we will not proceed if
# they are not all set.
if [ "${upgrade}" -eq 0 ]; then
	[ -z "${container_user_gid}" ] && printf "Error: Invalid arguments, missing user gid\n" && exit 2
	[ -z "${container_user_home}" ] && printf "Error: Invalid argument, missing user home\n" && exit 2
	[ -z "${container_user_name}" ] && printf "Error: Invalid arguments, missing username\n" && exit 2
	[ -z "${container_user_uid}" ] && printf "Error: Invalid arguments, missing user uid\n" && exit 2
fi
set -o errexit
set -o nounset
# set verbosity
if [ "${verbose}" -ne 0 ]; then
	set -o xtrace
fi

# Determine if we're in a rootful container, generally if we're able to read
# host's /etc/shadow, it means we're really root!
#
# if /run/.nopasswd is present, let's treat the init as rootless, this is not
# a good thing, users behold!
#
# if /run/.distrobox.rootless is present, the container was explicitly created
# without --root (distrobox-create mounts this marker for rootless containers).
# Trust it over the shadow heuristic, which gives false positives on Docker
# Desktop (macOS) where the container always has root access to the VM's fs.
if [ ! -e /run/.distrobox.rootless ] &&
	stat /run/host/etc/shadow > /dev/null &&
	{
		[ "$(stat -c "%u" /run/host/etc/shadow)" = "0" ] || [ "$(stat -f "%u" /run/host/etc/shadow 2> /dev/null)" = "0" ]
	} &&
	[ ! -e /run/.nopasswd ]; then
	rootful=1
fi

# Get host $LANG
if [ -f "/run/host/etc/locale.conf" ]; then
	HOST_LOCALE=$(grep -e '^LANG=' /run/host/etc/locale.conf | sed 's/LANG=//' | sed 's/"//g' | sed "s/'//g")
	HOST_LOCALE_ENCODING=$(echo "${HOST_LOCALE}" | sed -n 's/^[^.]*\.\(.*\)$/\1/p')
	HOST_LOCALE_LANG=$(echo "${HOST_LOCALE}" | sed -n 's/^\([^.]*\)\..*$/\1/p')
elif [ -f "/run/host/etc/default/locale" ]; then
	HOST_LOCALE=$(grep -e '^LANG=' /run/host/etc/default/locale | sed 's/LANG=//' | sed 's/"//g')
	HOST_LOCALE_ENCODING=$(echo "${HOST_LOCALE}" | sed -n 's/^[^.]*\.\(.*\)$/\1/p')
	HOST_LOCALE_LANG=$(echo "${HOST_LOCALE}" | sed -n 's/^\([^.]*\)\..*$/\1/p')
fi
# Add fallback values in case host's locale is not set correctly
if [ -z "${HOST_LOCALE:-}" ] || [ "${HOST_LOCALE:-}" = "C.UTF-8" ]; then
	HOST_LOCALE="en_US.UTF-8"
	HOST_LOCALE_ENCODING="UTF-8"
	HOST_LOCALE_LANG="en_US"
fi

# get_locked_mount_flags will print mount flags considered "locked".
# Arguments:
# 	src: path to the file/directory
# Expected env variables:
#	None
# Expected global variables:
#	None
# Outputs:
# 	Comma-separated list of locked mount flags
get_locked_mount_flags()
{
	src="$1"
	prev=""
	locked_flags=""

	# If findmnt does not exist, exit
	if ! command -v findmnt 2> /dev/null > /dev/null; then
		return 0
	fi

	# If we can't read the file/directory, exit
	if ! ls "${src}" 2> /dev/null > /dev/null; then
		return 0
	fi

	# Get mount flags of given file/directory, using nearest mountpoint.
	# Earlier versions of findmnt did not check parents until it found a mountpoint,
	# so we use a workaround with dirname.
	while true; do
		flags="$(findmnt --noheadings --output OPTIONS --target "${src}" || :)"
		# shellcheck disable=SC2181
		if [ -n "${flags}" ]; then
			break
		fi
		prev="${src}"
		src="$(dirname "${src}")"
		[ "${src}" = "${prev}" ] && return 1
	done

	for flag in nodev noexec nosuid; do
		if printf "%s" "${flags}" | grep -q "${flag}"; then
			# Locked flag found, append to list while avoiding leading/trailing commas
			locked_flags="${locked_flags:+${locked_flags},}${flag}"
		fi
	done

	printf "%s" "${locked_flags}"
}

# init_readlink is a simplistic implementation for
# readlink -fm
# we use this as readlink -fm does not work on
# busybox systems, and we need the path even for broken links.
# Arguments:
#	source file
# Expected env variables:
#	None
# Expected global variables:
#	None
# Outputs:
#	original path the link is pointing
init_readlink()
{
	# shellcheck disable=SC2010
	ls -l "${1}" | grep -Eo '\->.*' | cut -d' ' -f2- | sed 's|\.\./|/|g'
}

# mount_bind will perform a bind mount for inputs or error
# Arguments:
#   source_dir: string what to mount
#	target_dir: string where to mount
#	mount_flags: list of mount flags -> optional
# Expected env variables:
#	None
# Expected global variables:
#	None
# Outputs:
#   No output if all ok
#	Error if not
mount_bind()
{
	source_dir="$1"
	target_dir="$2"
	mount_flags=""
	if [ "$#" -gt 2 ]; then
		mount_flags="$3"
	fi

	# Adjust source_dir in order to point to /run/host if it's a symlink
	if [ -L "${source_dir}" ]; then
		source_dir="$(init_readlink "${source_dir}")"
		if ! printf "%s" "${source_dir}" | grep -q "/run/host"; then
			source_dir="/run/host${source_dir}"
		fi
	fi

	# if source dir doesn't exist, just exit
	if [ ! -d "${source_dir}" ] && [ ! -f "${source_dir}" ]; then
		return 0
	fi

	# if target_dir exists, check if it is a mountpoint and umount it.
	if [ -e "${target_dir}" ] && findmnt "${target_dir}" > /dev/null; then
		umount "${target_dir}"
	fi

	# if target_dir exists, and is a symlink, remove it
	if [ -L "${target_dir}" ]; then
		rm -f "${target_dir}"
	fi

	# if the source_dir exists, then create the target_dir
	if [ -d "${source_dir}" ]; then
		if ! mkdir -p "${target_dir}"; then
			printf "Warning: cannot create mount target directory: %s\n" "${target_dir}"
			return 1
		fi
	# if instead it's a file, create it with touch
	elif [ -f "${source_dir}" ]; then
		if [ ! -d "$(dirname "${target_dir}")" ]; then
			mkdir -p "$(dirname "${target_dir}")"
		fi
		# if we encounter a broken link, and we touch it
		# then remove the broken link, the next touch
		# will cover it.
		if ! touch "${target_dir}"; then
			printf "Warning: cannot create mount target file: %s\n" "${target_dir}"
			return 1
		fi
	fi

	# Add mountflags if needed, if no are specified, use rslave as default.
	# bind mount source_dir to target_dir, return error if not successful
	if [ "${mount_flags}" = "" ]; then
		if ! mount --rbind "${source_dir}" "${target_dir}"; then
			printf "Warning: failed to bind mount %s to %s\n" "${source_dir}" "${target_dir}"
			return 1
		fi
		if ! mount --make-rslave "${target_dir}"; then
			printf "Warning: failed to make rslave to %s\n" "${target_dir}"
			return 1
		fi
	elif ! mount --rbind -o "${mount_flags}" "${source_dir}" "${target_dir}"; then
		printf "Warning: failed to bind mount %s to %s using option %s\n" "${source_dir}" "${target_dir}" "${mount_flags}"
		return 1
	fi

	return 0
}

if [ -n "${pre_init_hook}" ]; then
	printf "distrobox: Executing pre-init hooks...\n"
	# execute pre-init hooks if specified
	# shellcheck disable=SC2086
	eval ${pre_init_hook}
fi

###############################################################################
printf "distrobox: Installing basic packages...\n"
# Extract shell name from the $SHELL environment variable
# If not present as package in the container, we want to install it.
shell_pkg="$(basename "${SHELL:-"bash"}")"
# Ash shell is an exception, it is not a standalone package, but part of busybox.
# for this reason, use this quirk to adjust the package name to standard bash.
if [ "${shell_pkg}" = "ash" ]; then
	shell_pkg="bash"
fi

# setup_pkg_manager_hooks will create umount/remount hooks script for a package
# manager. Mainly used by apt and pacman.
# Arguments:
#   None
# Expected global variables:
#   init: if this is an initful container
#   HOST_MOUNTS_RO_INIT: list of mountpoints of an initful system, to avoid
# Expected env variables:
#   None
# Outputs:
#   None
setup_pkg_manager_hooks()
{
	if {
		[ -d "/etc/dpkg/dpkg.cfg.d/" ] || [ -d "/usr/share/libalpm/scripts" ]
	} && [ "${init}" -eq 0 ]; then
		cat << EOF > /etc/distrobox-pre-hook.sh
#!/bin/sh
mounts="${HOST_MOUNTS_RO_INIT}"
for mount in \$mounts; do
	if findmnt \$mount >/dev/null; then
		umount -l \$mount
	fi
done
EOF
		cat << EOF > /etc/distrobox-post-hook.sh
#!/bin/sh
mounts="${HOST_MOUNTS_RO_INIT}"
for mount in \$mounts; do
	if [ -e /run/host/\$mount ] || [ -e /run/host/\$(readlink -fm /run/host/\$mount) ]; then
		if [ ! -d /run/host/\$mount ]; then
			rm -f \$mount && touch \$mount
		fi
		if ! mount --rbind \$(readlink -fm /run/host/\$mount) \$mount; then
			mount --rbind /run/host/\$(readlink -fm /run/host/\$mount) \$mount
		fi
	fi
done
EOF
		chmod +x /etc/distrobox-pre-hook.sh /etc/distrobox-post-hook.sh
	fi
}

# setup_tmpfiles_exceptions masks the systemd-tmpfiles configs that chown paths
# bind mounted from the host.
# Arguments:
#   None
# Expected global variables:
#   None
# Expected env variables:
#   None
# Outputs:
#   None
setup_tmpfiles_exceptions()
{
	mkdir -p /etc/tmpfiles.d
	for tmpfiles_conf in \
		tmp.conf x11.conf systemd-tmp.conf openssh-client.conf \
		tpm-udev.conf tpm2-tss-fapi.conf; do
		ln -sf /dev/null "/etc/tmpfiles.d/${tmpfiles_conf}"
	done
}

# setup_deb_exceptions will create path-excludes for host mounts, dpkg/apt only.
# Arguments:
#   None
# Expected global variables:
#   init: if this is an initful container
#   HOST_MOUNTS_RO: list of readonly mountpoints, to avoid
#   HOST_MOUNTS: list of readwrite mountpoints, to avoid
# Expected env variables:
#   None
# Outputs:
#   None
setup_deb_exceptions()
{
	setup_pkg_manager_hooks

	# In case of an DEB distro, we can specify that our bind_mount directories
	# have to be ignored. This prevents conflicts during package installations.
	if [ "${init}" -eq 0 ]; then
		# Loop through all the environment vars
		# and export them to the container.
		mkdir -p /etc/dpkg/dpkg.cfg.d/
		printf "" > /etc/dpkg/dpkg.cfg.d/00_distrobox
		for net_mount in ${HOST_MOUNTS_RO} ${HOST_MOUNTS}; do
			printf "path-exclude %s/*\n" "${net_mount}" >> /etc/dpkg/dpkg.cfg.d/00_distrobox
		done

		# Also we put a hook to clear some critical paths that do not play well
		# with read only filesystems, like Systemd.
		if [ -d "/etc/apt/apt.conf.d/" ]; then
			printf 'DPkg::Pre-Invoke {/etc/distrobox-pre-hook.sh};\n' > /etc/apt/apt.conf.d/00_distrobox
			printf 'DPkg::Post-Invoke {/etc/distrobox-post-hook.sh};\n' >> /etc/apt/apt.conf.d/00_distrobox
		fi
	fi
}

# setup_pacman_exceptions will set pre/post transaction hooks to avoid host's mounts, pacman only.
# Arguments:
#   None
# Expected global variables:
#   init: if this is an initful container
# Expected env variables:
#   None
# Outputs:
#   None
setup_pacman_exceptions()
{
	setup_pkg_manager_hooks

	# Workarounds for pacman. We need to exclude the paths by using a pre-hook to umount them and a
	# post-hook to remount them. Additionally we neutralize the systemd-post-hooks as they do not
	# work on a rootless container system.
	if [ -d "/usr/share/libalpm/scripts" ] && [ "${init}" -eq 0 ]; then
		# in case we're not using an init image, neutralize systemd post installation hooks
		# so that we do not encounter problems along the way.
		# This will be removed if we're using --init.
		cat << EOF > /usr/share/libalpm/scripts/distrobox_post_hook.sh
#!/bin/sh
printf '#!/bin/sh\nexit 0\n' > /usr/share/libalpm/scripts/systemd-hook;
EOF
		chmod +x /usr/share/libalpm/scripts/distrobox_post_hook.sh

		# create hooks files for them
		find /usr/share/libalpm/hooks/*distrobox* -delete || :
		for hook in /etc/distrobox-pre-hook.sh /etc/distrobox-post-hook.sh /usr/share/libalpm/scripts/distrobox_post_hook.sh; do
			when="PostTransaction"
			[ -z "${hook##*pre*}" ] && when="PreTransaction"
			cat << EOF > "/usr/share/libalpm/hooks/$(basename "${hook}").hook"
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = *
[Action]
Description = Distrobox hook ${hook}...
When = ${when}
Exec = ${hook}
EOF
		done
	fi

}

# setup_rpm_exceptions will create path-excludes for host mounts, rpm only (dnf, yum, zypper, microdnf).
# Arguments:
#   None
# Expected global variables:
#   init: if this is an initful container
#   HOST_MOUNTS_RO: list of readonly mountpoints, to avoid
#   HOST_MOUNTS: list of readwrite mountpoints, to avoid
# Expected env variables:
#   None
# Outputs:
#   None
setup_rpm_exceptions()
{
	# In case of an RPM distro, we can specify that our bind_mount directories
	# are in fact net shares. This prevents conflicts during package installations.
	if [ "${init}" -eq 0 ]; then
		mkdir -p /usr/lib/rpm/macros.d/
		# Loop through all the environment vars
		# and export them to the container.
		net_mounts=""
		for net_mount in \
			${HOST_MOUNTS_RO} ${HOST_MOUNTS} \
			'/dev' '/proc' '/sys' '/tmp' \
			'/etc/hosts' '/etc/resolv.conf' '/etc/passwd' '/etc/shadow'; do

			net_mounts="${net_mount}:${net_mounts}"
		done
		net_mounts=${net_mounts%?}
		cat << EOF > /usr/lib/rpm/macros.d/macros.distrobox
%_netsharedpath ${net_mounts}
EOF
	fi
}

# setup_xbps_exceptions will create path-excludes for host mounts, xbps only.
# Arguments:
#   None
# Expected global variables:
#   None
# Expected env variables:
#   None
# Outputs:
#   None
setup_xbps_exceptions()
{
	# We have to lock this paths from xbps extraction, as it's incompatible with distrobox's
	# mount process.
	cat << EOF > /etc/xbps.d/distrobox-ignore.conf
noextract=/etc/passwd
noextract=/etc/hosts
noextract=/etc/host.conf
noextract=/etc/hostname
noextract=/etc/localtime
noextract=/etc/machine-id
noextract=/etc/resolv.conf
EOF
}

# setup_apk will upgrade or setup all packages for apk based systems.
# Arguments:
#   None
# Expected global variables:
#   upgrade: if we need to upgrade or not
#   container_additional_packages: additional packages to install during this phase
# Expected env variables:
#   None
# Outputs:
#   None
setup_apk()
{
	# If we need to upgrade, do it and exit, no further action required.
	if [ "${upgrade}" -ne 0 ]; then
		apk update
		apk upgrade
		exit
	fi
	# Check if shell_pkg is available in distro's repo. If not we
	# fall back to bash, and we set the SHELL variable to bash so
	# that it is set up correctly for the user.
	if ! apk add "${shell_pkg}"; then
		shell_pkg="bash"
	fi
	if apk add wolfi-base; then
		deps="
			busybox
			gnutar
			man-db
			mesa
			openssh-client
			pinentry
			posix-libc-utils
			script
		"
	elif apk add alpine-base; then
		apk upgrade -Ua
		deps="
			bash-completion
			docs
			gcompat
			libc-utils
			lsof
			man-pages
			mandoc
			musl-utils
			openssh-client-default
			pinentry
			tar
			vte3
			which
			$(apk search -q mesa-dri)
			$(apk search -q mesa-vulkan)
		"
	elif apk add base-bootstrap; then
		# Prevent "ADB schema error" while installing tzdb in rootful container on currently old image
		apk upgrade -Ua
		# Setup rest of packages while also allowing install of extras from user repo
		apk add chimera-repo-user
		apk update
		deps="
			base-full-man
			bash-completion
			bc-gh
			gtar
			libarchive-progs
			libcap-progs
			lsof
			mesa-dri
			ncurses-term
			opendoas
			openssh
			util-linux-mount
			vte
			wget2
		"
	fi
	deps="${deps:-}
		${shell_pkg}
		bash
		bc
		bzip2
		coreutils
		curl
		diffutils
		findmnt
		findutils
		gnupg
		gpg
		iproute2
		iputils
		keyutils
		less
		libcap
		mount
		ncurses
		ncurses-terminfo
		net-tools
		pigz
		rsync
		shadow
		sudo
		tcpdump
		tree
		tzdata
		umount
		unzip
		util-linux
		util-linux-login
		util-linux-misc
		vulkan-loader
		wget
		xauth
		xz
		zip
		$(apk search -qe procps)
	"
	# shellcheck disable=SC2086
	found_deps="$(apk search -qe ${deps} | tr '\n' ' ')"
	install_pkg=""
	for dep in ${deps}; do
		# shellcheck disable=SC2249
		case " ${found_deps} " in
			*" ${dep} "*)
				install_pkg="${install_pkg} ${dep}"
				;;
		esac
	done
	# shellcheck disable=SC2086
	apk add --force-overwrite ${install_pkg}

	# Ensure we have tzdata installed and populated, sometimes container
	# images blank the zoneinfo directory, so we reinstall the package to
	# ensure population
	if [ ! -e /usr/share/zoneinfo/UTC ]; then
		apk del tzdata
		apk add tzdata
	fi

	# Install additional packages passed at distrbox-create time
	if [ -n "${container_additional_packages}" ]; then
		# shellcheck disable=SC2086
		apk add --force-overwrite ${container_additional_packages}
	fi
}

# setup_apt will upgrade or setup all packages for apt based systems.
# Arguments:
#   None
# Expected global variables:
#   upgrade: if we need to upgrade or not
#   container_additional_packages: additional packages to install during this phase
# Expected env variables:
#   None
# Outputs:
#   None
setup_apt()
{
	export DEBIAN_FRONTEND=noninteractive

	# If we need to upgrade, do it and exit, no further action required.
	if [ "${upgrade}" -ne 0 ]; then
		apt-get update
		apt-get upgrade -o Dpkg::Options::="--force-confold" -y
		exit
	fi
	# In Ubuntu official images, dpkg is configured to ignore locale and docs
	# This however, results in a rather poor out-of-the-box experience
	# So, let's enable them.
	rm -f /etc/dpkg/dpkg.cfg.d/excludes

	apt-get update
	# Check if shell_pkg is available in distro's repo. If not we
	# fall back to bash, and we set the SHELL variable to bash so
	# that it is set up correctly for the user.
	if ! apt-get install -y "${shell_pkg}"; then
		shell_pkg="bash"
	fi
	deps="
		${shell_pkg}
		apt-utils
		bash-completion
		bc
		bzip2
		curl
		dialog
		diffutils
		findutils
		gnupg
		gnupg2
		gpgsm
		hostname
		iproute2
		iputils-ping
		keyutils
		language-pack-en
		less
		libcap2-bin
		libkrb5-3
		libnss-mdns
		libnss-myhostname
		libvte-2.9*-common
		libvte-common
		locales
		lsof
		man-db
		manpages
		mtr
		ncurses-base
		openssh-client
		passwd
		pigz
		pinentry-curses
		procps
		rsync
		sudo
		tcpdump
		time
		traceroute
		tree
		tzdata
		unzip
		util-linux
		wget
		xauth
		xz-utils
		zip
		libgl1
		libegl-mesa0
		libegl1-mesa
		libgl1-mesa-glx
		libegl1
		libglx-mesa0
		libvulkan1
		mesa-vulkan-drivers
	"
	# shellcheck disable=SC2086,2046
	apt-get install -y $(apt-cache show ${deps} 2> /dev/null | grep "Package:" | sort -u | cut -d' ' -f2-)

	# In case the locale is not available, install it
	# will ensure we don't fallback to C.UTF-8
	if [ -e /etc/locale.gen ] && {
		! locale -a | grep -qi en_us.utf8 || ! locale -a | grep -qi "$(echo "${HOST_LOCALE}" | tr -d '-')"
	}; then
		sed -i "s|#.*en_US.UTF-8|en_US.UTF-8|g" /etc/locale.gen
		sed -i "s|#.*${HOST_LOCALE}|${HOST_LOCALE}|g" /etc/locale.gen
		locale-gen
		update-locale LC_ALL="${HOST_LOCALE}" LANG="${HOST_LOCALE}"
		dpkg-reconfigure locales
	fi

	# Ensure we have tzdata installed and populated, sometimes container
	# images blank the zoneinfo directory, so we reinstall the package to
	# ensure population
	if [ ! -e /usr/share/zoneinfo/UTC ]; then
		apt-get --reinstall install tzdata
	fi

	# Install additional packages passed at distrbox-create time
	if [ -n "${container_additional_packages}" ]; then
		# shellcheck disable=SC2086
		apt-get install -y ${container_additional_packages}
	fi
}

# setup_aptrpm will upgrade or setup all packages for apt-get and rpm based systems.
# Arguments:
#   None
# Expected global variables:
#   upgrade: if we need to upgrade or not
#   container_additional_packages: additional packages to install during this phase
# Expected env variables:
#   None
# Outputs:
#   None
setup_aptrpm()
{
	# If we need to upgrade, do it and exit, no further action required.
	if [ "${upgrade}" -ne 0 ]; then
		apt-get update
		apt-get dist-upgrade -y
		exit
	fi

	apt-get update
	# Check if shell_pkg is available in distro's repo. If not we
	# fall back to bash, and we set the SHELL variable to bash so
	# that it is set up correctly for the user.
	if ! apt-get install -y "${shell_pkg}"; then
		shell_pkg="bash"
	fi
	deps="
		${shell_pkg}
		apt-repo-tools
		apt-rsync
		bash-completion
		bc
		bzip2
		curl
		cracklib
		dialog
		diffutils
		findutils
		fontconfig
		gettext
		glibc
		glibc-i18ndata
		gnupg
		gnupg2
		iconv
		iproute2
		iputils
		keyutils
		less
		libcap
		libEGL
		libGL
		libkrb5
		libnss-mdns
		libnss-myhostname
		vte3
		libvulkan1
		lsof
		man-db
		mount
		mtr
		ncurses
		openssh-clients
		pam
		passwd
		pigz
		pinentry-common
		procps
		su
		sudo
		tcpdump
		time
		traceroute
		tree
		tzdata
		unzip
		util-linux
		wget
		xauth
		xorg-dri-intel
		xorg-dri-radeon
		xorg-utils
		xz
		zip
	"
	# shellcheck disable=SC2086,2046
	apt-get install -y ${deps}

	# Altlinux hooks. Without them the commands su, sudo, sudoreplay and passwd do not work
	if command -v control; then
		control passwd traditional
		control sudo public
		control sudoreplay public
		control su wheel
		control pam_mktemp disabled
		mkdir -p /etc/tcb/"${container_user_name}"
		echo "${container_user_name}::::::::" > /etc/tcb/"${container_user_name}"/shadow
		sed -i 's/*//g' /etc/passwd

		# ALT Linux ships its own su incompatible with util-linux su flags.
		# distrobox-enter passes flags like -m, --pty, -s, -c which ALT su rejects.
		# runuser (from util-linux, always present) accepts the same flags.
		# Place the wrapper in /usr/local/bin so it is found before /bin/su.
		mkdir -p /usr/local/bin
		cat << 'EOF' > /usr/local/bin/su
#!/bin/sh
exec /usr/sbin/runuser "$@"
EOF
		chmod +x /usr/local/bin/su
	fi

	# In case the locale is not available, install it
	# will ensure we don't fallback to C.UTF-8
	if [ ! -e /usr/share/i18n/charmaps ]; then
		apt-get --reinstall install -y glibc-i18ndata iconv
	fi
	if ! locale -a | grep -qi en_us.utf8 || ! locale -a | grep -qi "${HOST_LOCALE}"; then
		LANG="${HOST_LOCALE}" localedef -i "${HOST_LOCALE_LANG}" -f "${HOST_LOCALE_ENCODING}" "${HOST_LOCALE}"
	fi

	# Ensure we have tzdata installed and populated, sometimes container
	# images blank the zoneinfo directory, so we reinstall the package to
	# ensure population
	if [ ! -e /usr/share/zoneinfo/UTC ]; then
		apt-get --reinstall install -y tzdata
	fi

	# Install additional packages passed at distrbox-create time
	if [ -n "${container_additional_packages}" ]; then
		# shellcheck disable=SC2086
		apt-get install -y ${container_additional_packages}
	fi
}

# setup_dnf will upgrade or setup all packages for dnf/yum based systems.
# Arguments:
#   manager: yum or dnf
# Expected global variables:
#   upgrade: if we need to upgrade or not
#   container_additional_packages: additional packages to install during this phase
# Expected env variables:
#   None
# Outputs:
#   None
setup_dnf()
{
	manager=$1

	# If we need to upgrade, do it and exit, no further action required.
	if [ "${upgrade}" -ne 0 ]; then
		${manager} upgrade -y
		exit
	fi
	# In dnf family official images, dnf is configured to ignore locale and docs
	# This however, results in a rather poor out-of-the-box experience
	# So, let's enable them.
	[ -e /etc/dnf/dnf.conf ] && sed -i '/tsflags=nodocs/d' /etc/dnf/dnf.conf
	[ -e /etc/yum.conf ] && sed -i '/tsflags=nodocs/d' /etc/yum.conf

	# Check if shell_pkg is available in distro's repo. If not we
	# fall back to bash, and we set the SHELL variable to bash so
	# that it is set up correctly for the user.
	if ! ${manager} install -y "${shell_pkg}" 2> /dev/null; then
		shell_pkg="bash"
	fi
	flags=""
	if [ "${manager}" = "dnf" ]; then
		flags="--allowerasing"
	fi
	deps="
		${shell_pkg}
		bash-completion
		bc
		bzip2
		cracklib-dicts
		curl
		diffutils
		dnf-plugins-core
		findutils
		glibc-all-langpacks
		glibc-common
		glibc-locale-source
		gnupg2
		gnupg2-smime
		hostname
		iproute
		iputils
		keyutils
		krb5-libs
		less
		lsof
		man-db
		man-pages
		mtr
		ncurses
		nss-mdns
		openssh-clients
		pam
		passwd
		pigz
		pinentry
		procps-ng
		rsync
		shadow-utils
		sudo
		tcpdump
		time
		traceroute
		tree
		tzdata
		unzip
		util-linux
		util-linux-script
		vte-profile
		wget
		wget2-wget
		which
		whois
		words
		xorg-x11-xauth
		xz
		zip
		mesa-dri-drivers
		mesa-vulkan-drivers
		vulkan
	"
	# shellcheck disable=SC2086,2046,2248
	${manager} install ${flags} -y $(${manager} list -q ${deps} |
		grep -v "Packages" |
		grep "$(uname -m)" |
		cut -d' ' -f1)

	# In case the locale is not available, install it
	# will ensure we don't fallback to C.UTF-8
	if [ ! -e /usr/share/i18n/charmaps ]; then
		${manager} reinstall -y glibc-common
	fi
	if ! locale -a | grep -qi en_us.utf8 || ! locale -a | grep -qi "$(echo "${HOST_LOCALE}" | tr -d '-')"; then
		LANG="${HOST_LOCALE}" localedef -i "${HOST_LOCALE_LANG}" -f "${HOST_LOCALE_ENCODING}" "${HOST_LOCALE}"
	fi

	# Ensure we have tzdata installed and populated, sometimes container
	# images blank the zoneinfo directory, so we reinstall the package to
	# ensure population
	if [ ! -e /usr/share/zoneinfo/UTC ]; then
		${manager} reinstall -y tzdata
	fi

	# Install additional packages passed at distrbox-create time
	if [ -n "${container_additional_packages}" ]; then
		# shellcheck disable=SC2086
		${manager} install -y ${container_additional_packages}
	fi
}

# setup_emerge will upgrade or setup all packages for gentoo based systems.
# Arguments:
#   None
# Expected global variables:
#   upgrade: if we need to upgrade or not
#   container_additional_packages: additional packages to install during this phase
# Expected env variables:
#   None
# Outputs:
#   None
setup_emerge()
{
	# Check if the container we are using has a ::gentoo repo defined,
	# if it is defined and it is empty, then synchroznize it.
	gentoo_repo="$(portageq get_repo_path / gentoo)"
	if [ -n "${gentoo_repo}" ] && [ ! -e "${gentoo_repo}" ]; then
		emerge-webrsync
	fi
	# If we need to upgrade, do it and exit, no further action required.
	if [ "${upgrade}" -ne 0 ]; then
		emerge --sync
		exit
	fi
	# Check if shell_pkg is available in distro's repo. If not we
	# fall back to bash, and we set the SHELL variable to bash so
	# that it is set up correctly for the user.
	if ! emerge --ask=n --autounmask-continue --noreplace --quiet-build "${shell_pkg}"; then
		shell_pkg="bash"
	fi
	deps="
		app-shells/${shell_pkg}
		app-crypt/gnupg
		app-shells/bash-completion
		sys-apps/diffutils
		sys-apps/findutils
		sys-apps/less
		sys-libs/ncurses
		net-misc/curl
		app-crypt/pinentry
		sys-process/procps
		sys-apps/shadow
		app-admin/sudo
		sys-devel/bc
		sys-process/lsof
		sys-apps/util-linux
		net-misc/wget
	"
	install_pkg=""
	for dep in ${deps}; do
		if [ "$(emerge --ask=n --search "${dep}" | grep "Applications found" | grep -Eo "[0-9]")" -gt 0 ]; then
			# shellcheck disable=SC2086
			install_pkg="${install_pkg} ${dep}"
		fi
	done
	# shellcheck disable=SC2086
	emerge --ask=n --autounmask-continue --noreplace --quiet-build ${install_pkg}

	# In case the locale is not available, install it
	# will ensure we don't fallback to C.UTF-8
	if ! locale -a | grep -qi en_us.utf8 || ! locale -a | grep -qi "$(echo "${HOST_LOCALE}" | tr -d '-')"; then
		sed -i "s|#.*en_US.UTF-8|en_US.UTF-8|g" /etc/locale.gen
		sed -i "s|#.*${HOST_LOCALE}|${HOST_LOCALE}|g" /etc/locale.gen
		locale-gen
		cat << EOF > /etc/env.d/02locale
LANG=${HOST_LOCALE}
LC_CTYPE=${HOST_LOCALE}
EOF
	fi

	# Install additional packages passed at distrbox-create time
	if [ -n "${container_additional_packages}" ]; then
		# shellcheck disable=SC2086
		emerge --ask=n --autounmask-continue --noreplace --quiet-build \
			${container_additional_packages}
	fi
}

# setup_microdnf will upgrade or setup all packages for microdnf based systems.
# Arguments:
#   None
# Expected global variables:
#   upgrade: if we need to upgrade or not
#   container_additional_packages: additional packages to install during this phase
# Expected env variables:
#   None
# Outputs:
#   None
setup_microdnf()
{
	# If we need to upgrade, do it and exit, no further action required.
	if [ "${upgrade}" -ne 0 ]; then
		microdnf upgrade -y
		exit
	fi
	# Check if shell_pkg is available in distro's repo. If not we
	# fall back to bash, and we set the SHELL variable to bash so
	# that it is set up correctly for the user.
	if ! microdnf install -y "${shell_pkg}"; then
		shell_pkg="bash"
	fi
	deps="
		${shell_pkg}
		bash-completion
		bc
		bzip2
		cracklib-dicts
		diffutils
		dnf-plugins-core
		findutils
		glibc-all-langpacks
		glibc-common
		glibc-locale-source
		gnupg2
		gnupg2-smime
		hostname
		iproute
		iputils
		keyutils
		krb5-libs
		less
		lsof
		man-db
		man-pages
		mtr
		ncurses
		nss-mdns
		openssh-clients
		pam
		passwd
		pigz
		pinentry
		procps-ng
		rsync
		shadow-utils
		sudo
		tcpdump
		time
		traceroute
		tree
		tzdata
		unzip
		util-linux
		vte-profile
		wget
		which
		whois
		words
		xorg-x11-xauth
		xz
		zip
		mesa-dri-drivers
		mesa-vulkan-drivers
		vulkan
	"
	install_pkg=""
	for dep in ${deps}; do
		if [ "$(microdnf repoquery "${dep}" | wc -l)" -gt 0 ]; then
			install_pkg="${install_pkg} ${dep}"
		fi
	done
	# shellcheck disable=SC2086,SC2046
	microdnf install -y ${install_pkg}

	# In case the locale is not available, install it
	# will ensure we don't fallback to C.UTF-8
	if [ ! -e /usr/share/zoneinfo/UTC ]; then
		microdnf reinstall -y tzdata || microdnf install -y glibc-common
	fi
	if ! locale -a | grep -qi en_us.utf8 || ! locale -a | grep -qi "$(echo "${HOST_LOCALE}" | tr -d '-')"; then
		LANG="${HOST_LOCALE}" localedef -i "${HOST_LOCALE_LANG}" -f "${HOST_LOCALE_ENCODING}" "${HOST_LOCALE}"
	fi

	# Ensure we have tzdata installed and populated, sometimes container
	# images blank the zoneinfo directory, so we reinstall the package to
	# ensure population
	if [ ! -e /usr/share/zoneinfo/UTC ]; then
		microdnf reinstall -y tzdata || microdnf install -y tzdata
	fi

	# Install additional packages passed at distrbox-create time
	if [ -n "${container_additional_packages}" ]; then
		# shellcheck disable=SC2086
		microdnf install -y ${container_additional_packages}
	fi
}

# setup_pacman will upgrade or setup all packages for pacman based systems.
# Arguments:
#   None
# Expected global variables:
#   upgrade: if we need to upgrade or not
#   container_additional_packages: additional packages to install during this phase
# Expected env variables:
#   None
# Outputs:
#   None
setup_pacman()
{
	# Update the package repository cache exactly once before installing packages.
	pacman -S -y -y

	# If we need to upgrade, do it and exit, no further action required.
	if [ "${upgrade}" -ne 0 ]; then
		pacman -S -u --noconfirm
		exit
	fi
	# In archlinux official images, pacman is configured to ignore locale and docs
	# This however, results in a rather poor out-of-the-box experience
	# So, let's enable them.
	sed -i "s|NoExtract.*||g" /etc/pacman.conf
	sed -i "s|NoProgressBar.*||g" /etc/pacman.conf

	pacman -S -u --noconfirm
	# Check if shell_pkg is available in distro's repo. If not we
	# fall back to bash, and we set the SHELL variable to bash so
	# that it is set up correctly for the user.
	if ! pacman -S --needed --noconfirm "${shell_pkg}"; then
		shell_pkg="bash"
	fi
	deps="
		${shell_pkg}
		bash-completion
		bc
		curl
		diffutils
		findutils
		glibc
		gnupg
		iputils
		inetutils
		keyutils
		less
		lsof
		man-db
		man-pages
		mlocate
		mtr
		ncurses
		nss-mdns
		openssh
		pigz
		pinentry
		procps-ng
		rsync
		shadow
		sudo
		tcpdump
		time
		traceroute
		tree
		tzdata
		unzip
		util-linux
		util-linux-libs
		vte-common
		wget
		words
		xorg-xauth
		zip
		mesa
		vulkan-intel
		vulkan-radeon
	"
	# shellcheck disable=SC2086,2046
	pacman -S --needed --noconfirm $(pacman -Ssq | grep -E "^($(echo ${deps} | tr ' ' '|'))$")

	if [ ! -e "/usr/share/i18n/locales${HOST_LOCALE}" ]; then
		pacman -S --noconfirm glibc glibc-locales
	fi

	# Ensure we have tzdata installed and populated, sometimes container
	# images blank the zoneinfo directory, so we reinstall the package to
	# ensure population
	if [ ! -e /usr/share/zoneinfo/UTC ]; then
		pacman -S --noconfirm tzdata
	fi

	# In case the locale is not available, install it
	# will ensure we don't fallback to C.UTF-8
	if ! locale -a | grep -qi en_us.utf8 || ! locale -a | grep -qi "$(echo "${HOST_LOCALE}" | tr -d '-')"; then
		sed -i "s|#.*en_US.UTF-8|en_US.UTF-8|g" /etc/locale.gen
		sed -i "s|#.*${HOST_LOCALE}|${HOST_LOCALE}|g" /etc/locale.gen
		locale-gen -a
	fi

	# Install additional packages passed at distrbox-create time
	if [ -n "${container_additional_packages}" ]; then
		# shellcheck disable=SC2086
		pacman -S --needed --noconfirm ${container_additional_packages}
	fi
}

# setup_slackpkg will upgrade or setup all packages for slackware based systems.
# Arguments:
#   None
# Expected global variables:
#   upgrade: if we need to upgrade or not
#   container_additional_packages: additional packages to install during this phase
# Expected env variables:
#   None
# Outputs:
#   None
setup_slackpkg()
{
	# If we need to upgrade, do it and exit, no further action required.
	if [ "${upgrade}" -ne 0 ]; then
		yes | slackpkg upgrade-all -default_answer=yes -batch=yes
		exit
	fi
	slackpkg update
	# Check if shell_pkg is available in distro's repo. If not we
	# fall back to bash, and we set the SHELL variable to bash so
	# that it is set up correctly for the user.
	if ! yes | slackpkg install -default_answer=yes -batch=yes "${shell_pkg}"; then
		shell_pkg="bash"
	fi
	deps="
		${shell_pkg}
		bash-completion
		bc
		curl
		diffutils
		findutils
		glew
		glibc
		glu
		gnupg2
		iputils
		less
		libX11
		libXau
		libXdamage
		libXdmcp
		libXext
		libXfixes
		libXxf86vm
		libdrm
		libvte-2
		libxcb
		libxcb-dri2
		libxcb-dri3
		libxcb-glx
		libxcb-present
		libxcb-randr
		libxcb-render
		libxcb-shape
		libxcb-sync
		libxcb-xfixes
		libxshmfence
		lsof
		man
		mesa
		ncurses
		openssh
		pinentry
		procps
		rsync
		shadow
		ssh
		sudo
		time
		wget
		xauth
	"
	install_pkg=""
	dep=""
	for dep in ${deps}; do
		if ! slackpkg search "${dep}" | grep -q "No package name matches the pattern"; then
			install_pkg="${install_pkg} ${dep}"
		fi
	done

	rm -f /var/lock/slackpkg.*

	# shellcheck disable=SC2086
	yes | slackpkg install -default_answer=yes -batch=yes ${install_pkg}

	# Install additional packages passed at distrbox-create time
	if [ -n "${container_additional_packages}" ]; then
		# shellcheck disable=SC2086
		yes | slackpkg install -default_answer=yes -batch=yes \
			${container_additional_packages}
	fi
}

# setup_xbps will upgrade or setup all packages for xbps based systems.
# Arguments:
#   None
# Expected global variables:
#   upgrade: if we need to upgrade or not
#   container_additional_packages: additional packages to install during this phase
# Expected env variables:
#   None
# Outputs:
#   None
setup_xbps()
{
	# If we need to upgrade, do it and exit, no further action required.
	if [ "${upgrade}" -ne 0 ]; then
		xbps-install -Syu
		exit
	fi
	# Ensure we avoid errors by keeping xbps updated
	xbps-install -Syu xbps

	# Check if shell_pkg is available in distro's repo. If not we
	# fall back to bash, and we set the SHELL variable to bash so
	# that it is set up correctly for the user.
	if ! xbps-install -Sy "${shell_pkg}"; then
		shell_pkg="bash"
	fi
	deps="
		${shell_pkg}
		bash-completion
		bc
		bzip2
		curl
		diffutils
		findutils
		gnupg2
		inetutils-ping
		iproute2
		less
		lsof
		man-db
		mit-krb5-client
		mit-krb5-libs
		mtr
		ncurses-base
		nss
		openssh
		pigz
		pinentry-tty
		procps-ng
		rsync
		shadow
		sudo
		time
		traceroute
		tree
		tzdata
		unzip
		util-linux
		xauth
		xz
		zip
		wget
		vte3
		mesa-dri
		vulkan-loader
		mesa-vulkan-intel
		mesa-vulkan-radeon
	"
	# shellcheck disable=SC2086,2046
	xbps-install -Sy $(xbps-query -Rs '*' | awk '{print $2}' | sed 's/-[^-]*$//' | grep -E "^($(echo ${deps} | tr ' ' '|'))$")

	# In case the locale is not available, install it
	# will ensure we don't fallback to C.UTF-8
	if command -v locale && {
		! locale -a | grep -qi en_us.utf8 || ! locale -a | grep -qi "$(echo "${HOST_LOCALE}" | tr -d '-')"
	}; then
		sed -i "s|#.*en_US.UTF-8|en_US.UTF-8|g" /etc/default/libc-locales
		sed -i "s|#.*${HOST_LOCALE}|${HOST_LOCALE}|g" /etc/default/libc-locales
		xbps-reconfigure --force glibc-locales
	fi

	# Ensure we have tzdata installed and populated, sometimes container
	# images blank the zoneinfo directory, so we reinstall the package to
	# ensure population
	if [ ! -e /usr/share/zoneinfo/UTC ]; then
		xbps-install --force -y tzdata
	fi

	# Install additional packages passed at distrbox-create time
	if [ -n "${container_additional_packages}" ]; then
		# shellcheck disable=SC2086
		xbps-install -Sy ${container_additional_packages}
	fi
}

# setup_zypper will upgrade or setup all packages for zypper based systems.
# Arguments:
#   None
# Expected global variables:
#   upgrade: if we need to upgrade or not
#   container_additional_packages: additional packages to install during this phase
# Expected env variables:
#   None
# Outputs:
#   None
setup_zypper()
{
	# If we need to upgrade, do it and exit, no further action required.
	if [ "${upgrade}" -ne 0 ]; then
		zypper dup -y
		exit
	fi
	if ! zypper install -y "${shell_pkg}"; then
		shell_pkg="bash"
	fi

	# In openSUSE official images, zypper is configured to ignore recommended
	# packages (i.e., weak dependencies). This however, results in a rather
	# poor out-of-the-box experience (e.g., when trying to run GUI apps).
	# So, let's enable them. For the same reason, we make sure we install
	# docs.
	if [ -d /etc/zypp/zypp.conf.d ]; then
		cat << EOF > /etc/zypp/zypp.conf.d/99-distrobox.conf
[main]
solver.onlyRequires = false
rpm.install.excludedocs = no
EOF
	else
		sed -i 's/.*solver.onlyRequires.*/solver.onlyRequires = false/g' /etc/zypp/zypp.conf
		sed -i 's/.*rpm.install.excludedocs.*/rpm.install.excludedocs = no/g' /etc/zypp/zypp.conf
	fi

	# With recommended packages, something might try to pull in
	# parallel-printer-support which can't be installed in rootless containers.
	# Since we very much likely never need it, just lock it
	zypper al parallel-printer-support
	# Check if shell_pkg is available in distro's repo. If not we
	# fall back to bash, and we set the SHELL variable to bash so
	# that it is set up correctly for the user.
	deps="
		${shell_pkg}
		bash-completion
		bc
		bzip2
		curl
		diffutils
		findutils
		glibc-locale
		glibc-locale-base
		gnupg
		hostname
		iputils
		keyutils
		less
		libvte-2*
		lsof
		man
		man-pages
		mtr
		ncurses
		nss-mdns
		openssh-clients
		pam
		pam-extra
		pigz
		pinentry
		procps
		rsync
		shadow
		sudo
		system-group-wheel
		systemd
		time
		timezone
		tree
		unzip
		util-linux
		util-linux-systemd
		wget
		words
		xauth
		zip
		Mesa-dri
		libvulkan1
		libvulkan_intel
		libvulkan_radeon
	"
	# zypper reserves exit codes >= 100 as informational: the transaction
	# committed, but something is worth noting - e.g. 106 (a repo was skipped) or
	# 107 (a package %post script failed, common in containers). Treat those as
	# non-fatal warnings. 105 (interrupted by a signal) shares the range but is a
	# genuine abort, so it stays fatal.
	# shellcheck disable=SC2086,SC2046
	zypper -n install -y $(zypper -n -q se --match-exact ${deps} | grep -e 'package$' | cut -d'|' -f2) || zypper_rc=${?}
	case "${zypper_rc:-0}" in
		0) ;;
		105) exit "${zypper_rc}" ;;
		1[0-9][0-9])
			printf "Warning: zypper exited %s (informational); packages installed, continuing.\n" "${zypper_rc}"
			;;
		*) exit "${zypper_rc}" ;;
	esac

	# In case the locale is not available, install it
	# will ensure we don't fallback to C.UTF-8
	if ! locale -a | grep -qi en_us.utf8 || ! locale -a | grep -qi "$(echo "${HOST_LOCALE}" | tr -d '-')"; then
		LANG="${HOST_LOCALE}" localedef -i "${HOST_LOCALE_LANG}" -f "${HOST_LOCALE_ENCODING}" "${HOST_LOCALE}" || true
	fi

	# Ensure we have tzdata installed and populated, sometimes container
	# images blank the zoneinfo directory, so we reinstall the package to
	# ensure population
	if [ ! -e /usr/share/zoneinfo/UTC ]; then
		zypper install -f -y timezone
	fi

	# Install additional packages passed at distrbox-create time
	if [ -n "${container_additional_packages}" ]; then
		# shellcheck disable=SC2086
		zypper install -y ${container_additional_packages}
	fi
}

# Check if all commands are installed.
# Arguments:
#   None
# Expected global variables:
#   shell_pkg: shell package of the container shell
# Expected env variables:
#   None
# Outputs:
#   None
check_missing_packages()
{
	dependencies="
		bc
		bzip2
		chpasswd
		curl
		diff
		find
		findmnt
		gpg
		hostname
		less
		lsof
		man
		mount
		passwd
		pigz
		pinentry
		ping
		ps
		rsync
		script
		ssh
		sudo
		time
		tree
		umount
		unzip
		useradd
		wc
		wget
		xauth
		zip
		${shell_pkg}
	"
	for dep in ${dependencies}; do
		! command -v "${dep}" && return 1
	done
	return 0
}

# Ensure we have the least minimal path of standard Linux File System set
PATH="${PATH}:/bin:/sbin:/usr/bin:/usr/sbin"

# Mask systemd-tmpfiles rules over host-shared paths before any package manager
# runs its maintainer scripts.
setup_tmpfiles_exceptions

# Setup pkg manager exceptions and excludes
if command -v apt-get; then
	if command -v rpm; then
		setup_rpm_exceptions
	else
		setup_deb_exceptions
	fi
elif command -v pacman; then
	setup_pacman_exceptions
elif command -v xbps-install; then
	setup_xbps_exceptions
elif command -v zypper; then
	setup_rpm_exceptions
elif command -v dnf; then
	setup_rpm_exceptions
elif command -v microdnf; then
	setup_rpm_exceptions
elif command -v yum; then
	setup_rpm_exceptions
fi

# Check if dependencies are met for the script to run.
if [ "${upgrade}" -ne 0 ] || [ ! -e /.containersetupdone ]; then

	# Detect the available package manager
	# install minimal dependencies needed to bootstrap the container:
	#	the same shell that's on the host + ${dependencies}
	if command -v apk; then
		setup_apk
	elif command -v apt-get; then
		if command -v rpm; then
			setup_aptrpm
		else
			setup_apt
		fi
	elif command -v emerge; then
		setup_emerge
	elif command -v pacman; then
		setup_pacman
	elif command -v slackpkg; then
		setup_slackpkg
	elif command -v xbps-install; then
		setup_xbps
	elif command -v zypper; then
		setup_zypper
	elif command -v dnf; then
		setup_dnf dnf
	elif command -v microdnf; then
		setup_microdnf
	elif command -v yum; then
		setup_dnf yum
	else
		printf "Error: could not find a supported package manager.\n"
		printf "Error: could not set up base dependencies.\n"
		# Exit as command not found
		exit 127
	fi
	if [ ! -e /.containersetupdone ]; then
		if ! check_missing_packages; then
			printf "Warning: some dependencies are missing.\n"
		fi
		touch /.containersetupdone
	fi
fi

# Set SHELL to the install path inside the container
SHELL="$(command -v "${shell_pkg}")"

# Attempt to download host-spawn during init, we don't care if it fails, so let's
# continue in that case
/usr/bin/distrobox-host-exec -Y test 2> /dev/null > /dev/null || :

# If xdg-open is not present and we don't have an init system, do a link of it.
# This is handy to handle opening of links, files and apps from inside the container
# into the host.
if [ "${init}" -eq 0 ] && ! command -v xdg-open; then
	mkdir -p /usr/local/bin/
	ln -sf /usr/bin/distrobox-host-exec /usr/local/bin/xdg-open
fi

# If flatpak is not present, do a link of it. This is handy to handle opening of
# links, files and apps from inside the container into the host.
# Note: we're using /usr/bin instead of /usr/local/bin because xdg-open will read
# the desktopfile, which will contain an absolute path of /usr/bin/flatpak
if ! command -v flatpak; then
	ln -sf /usr/bin/distrobox-host-exec /usr/bin/flatpak
fi

###############################################################################

# Ensure compatibility with older versions of su, this will allow to specify
# the --pty flag
#
# This won't work well on very old distros with su version from util-linux
# before version 2.34 or other su implementations but will give an usable
# shell nonetheless
if [ ! -e /usr/local/bin/su ] && {
	! su --version | grep -q util-linux ||
		su --version | awk '{printf  "%s\n%s", $4, 2.34}' | sort --check=quiet --version-sort
}; then
	cat << EOF > /usr/local/bin/su
#!/bin/sh

for i do
  [ "\$i" = --pty ] || set -- "\$@" "\$i"
  shift
done

/bin/su "\$@"
EOF
	chmod +x /usr/local/bin/su
fi

###############################################################################
printf "distrobox: Setting up devpts mounts...\n"

# First we need to ensure we have a tty group to assign /dev/pts to
if ! grep -q tty /etc/group; then
	printf "%s" 'tty:x:5:' >> /etc/group
fi

# Instantiate a new /dev/pts mount, this will ensure pseudoterminals are container-scoped
# and make easier in case of initful containers to have a separate /dev/console
#
# Podman supports a mount option to do this at creation time, but we're doing it
# here to support also other container rmanagers which does not support that flag
mount -t devpts devpts -o noexec,nosuid,newinstance,ptmxmode=0666,mode=0620,gid=tty /dev/pts/
mount --bind /dev/pts/ptmx /dev/ptmx

# Change mount propagation to shared to make the environment more similar to a
# modern Linux system, e.g. with Systemd as PID 1.
mount --make-rshared /
###############################################################################

###############################################################################
printf "distrobox: Setting up read-only mounts...\n"

for host_mount_ro in ${HOST_MOUNTS_RO}; do
	# Mounting read-only in a user namespace will trigger a check to see if certain
	# "locked" flags (line noexec,nodev,nosuid) are changed. This ensures we explicitly reuse those flags.
	locked_flags="$(get_locked_mount_flags /run/host"${host_mount_ro}")"
	if ! mount_bind /run/host"${host_mount_ro}" "${host_mount_ro}" ro"${locked_flags:+,${locked_flags}}"; then
		printf "Warning: %s integration with the host failed, runtime sync for %s disabled.\n" "${host_mount_ro}" "${host_mount_ro}"
		# Fallback options for files, we do a hard copy of it
		if [ -f /run/host"${host_mount_ro}" ]; then
			if ! (rm -f "${host_mount_ro}" && cp -f /run/host"${host_mount_ro}" "${host_mount_ro}"); then
				printf "Warning: Hard copy failed. Error: %s\n" "$(cp -f /run/host"${host_mount_ro}" "${host_mount_ro}" 2>&1)"
			fi
		fi
	fi
done
###############################################################################

###############################################################################
printf "distrobox: Setting up read-write mounts...\n"

# On some ostree systems, home is in /var/home, but most of the software expects
# it to be in /home. In the hosts systems this is fixed by using a symlink.
# Do something similar here with a bind mount.
if [ -e "/var/home/${container_user_name}" ]; then
	if ! mount_bind "/run/host/var/home/${container_user_name}" "/home/${container_user_name}"; then
		printf "Warning: Cannot bind mount %s to /run/host%s\n" "/var/home" "/home"
	fi
fi

for host_mount in ${HOST_MOUNTS}; do
	if ! mount_bind /run/host"${host_mount}" "${host_mount}"; then
		printf "Warning: Cannot bind mount %s to /run/host%s\n" "${host_mount}" "${host_mount}"
	fi
done
###############################################################################

###############################################################################
printf "distrobox: Setting up host's sockets integration...\n"
# 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 or accessing docker and libvirt sockets.
host_sockets="$(find /run/host/run \
	-xdev \
	-path /run/host/run/media -prune -o \
	-path /run/host/run/timeshift -prune -o \
	-name 'user' -prune -o \
	-name 'bees' -prune -o \
	-name 'nscd' -prune -o \
	-name 'schroot' -prune -o \
	-name 'system_bus_socket' -prune -o \
	-name 'io.systemd.Multiplexer' -prune -o \
	-name 'io.systemd.DropIn' -prune -o \
	-name 'io.systemd.NameServiceSwitch' -prune -o \
	-type s -print \
	2> /dev/null || :)"

# we're excluding system dbus socket, nscd socket and systemd-userdbd sockets here. Including them will
# create many problems with package managers thinking they have access to
# system dbus, user auth cache misused or query wrong user information.
for host_socket in ${host_sockets}; do
	container_socket="${host_socket#/run/host}"
	# Check if the socket already exists or the symlink already exists
	if [ ! -S "${container_socket}" ] && [ ! -L "${container_socket}" ]; then
		# link it.
		rm -f "${container_socket}"
		mkdir -p "$(dirname "${container_socket}")"
		if ! ln -s "${host_socket}" "${container_socket}"; then
			printf "Warning: Cannot link socket %s to %s\n" "${host_socket}" "${container_socket}"
		fi
	fi
done
###############################################################################

# mirror_host_ro read-only bind-mounts a host file onto its guest path, reusing
# the host's locked mount flags and resolving symlinks to the backing file.
# Factors out the body shared by the nvidia mount loops below.
# Arguments:
#	src: host path to mount (symlinks are resolved to the backing file)
#	dest: guest path to mount it onto
# Expected env variables:
#	None
# Expected global variables:
#	None
# Outputs:
#	No output on success; performs the read-only bind mount
#	Warning and return 1 if the guest parent dir is read-only
mirror_host_ro()
{
	src="$1"
	dest="$2"

	dest="$(readlink -m "$(dirname "${dest}")")/$(basename "${dest}")"

	dest_parent="$(dirname "${dest}")"
	if [ ! -e "${dest_parent}" ] && ! mkdir -p "${dest_parent}"; then
		printf "Warning: skipping file %s, %s mounted as read-only\n" "${dest}" "${dest_parent}"
		return 1
	fi
	if [ ! -w "${dest_parent}" ]; then
		printf "Warning: skipping file %s, %s mounted as read-only\n" "${dest}" "${dest_parent}"
		return 1
	fi

	if [ -L "${src}" ]; then
		src="$(readlink -fm "${src}")"
	fi

	# Mounting read-only in a user namespace re-checks "locked" flags
	# (nodev,noexec,nosuid); reuse the host's so the bind is permitted.
	locked_flags="$(get_locked_mount_flags "${src}")"
	mount_bind "${src}" "${dest}" ro"${locked_flags:+,${locked_flags}}"
}

# If --nvidia, we try to integrate host's nvidia drivers in to the guest
if [ "${nvidia}" -eq 1 ]; then
	printf "distrobox: Setting up host's nvidia integration...\n"

	# Refresh ldconfig cache, also detect if there are empty files remaining
	# and clean them.
	# This could happen when upgrading drivers and changing versions.
	# Use "*.so*" instead of "*.so.*" to also match unversioned .so stubs
	# (e.g. libcuda.so, libnvcuvid.so) that block CUDA detection. See #1764.
	find /usr/lib* -empty -iname "*.so*" -exec sh -c 'rm -rf "$1" || umount "$1" && rm -rf "$1"' sh {} ';' || :
	find /usr/ /etc/ -empty -iname "*nvidia*" -exec sh -c 'rm -rf "$1" || umount "$1" && rm -rf "$1"' sh {} ';' || :

	# First we find all generic config files we might need
	NVIDIA_FILES="$(find /run/host/etc/ -not -type d \
		-wholename "*nvidia*" || :)"
	for nvidia_file in ${NVIDIA_FILES}; do
		mirror_host_ro "${nvidia_file}" "${nvidia_file#/run/host}"
	done

	# Then we find the non-lib runtime bits the loaders discover by path: EGL
	# vendor/external-platform configs, Vulkan and Vulkan SC ICDs and layers,
	# OpenCL ICDs, Xorg confs, the OptiX blob and the application-profiles rc the
	# GL driver reads for per-app settings. Matched by directory + nvidia name
	# rather than exact filenames, so newly shipped files are picked up without
	# editing this list.
	NVIDIA_CONFS="$(find /run/host/usr/ -not -type d \
		-wholename "*/glvnd/egl_vendor.d/*nvidia*" \
		-o -wholename "*/OpenCL/vendors/*nvidia*" \
		-o -wholename "*/X11/xorg.conf.d/*nvidia*" \
		-o -wholename "*/egl_external_platform.d/*nvidia*" \
		-o -wholename "*/nvidia/nvoptix.bin" \
		-o -wholename "*/share/nvidia/*application-profiles*-rc" \
		-o -wholename "*/vulkan/explicit_layer.d/*nvidia*" \
		-o -wholename "*/vulkan/icd.d/*nvidia*" \
		-o -wholename "*/vulkan/implicit_layer.d/*nvidia*" \
		-o -wholename "*/vulkansc/icd.d/*nvidia*" \
		-o -wholename "*nvidia.icd" \
		-o -wholename "*nvidia.yaml" \
		-o -wholename "*nvidia.json" || :)"
	for nvidia_file in ${NVIDIA_CONFS}; do
		dest_file="${nvidia_file#/run/host}"
		# ICD/vendor JSONs can hard-code an absolute library_path pointing at the
		# host layout. The libraries are relocated into the private bucket, so an
		# absolute path will not resolve in the guest - and the Vulkan loader does
		# not fall back to the basename, so the ICD is silently ignored. Rewrite an
		# absolute library_path to the bare soname, letting ld.so (and thus our
		# ld.so.conf.d bucket) resolve it; everything else is mirrored verbatim.
		case "${nvidia_file}" in
			*.json)
				if grep -q '"library_path"[[:space:]]*:[[:space:]]*"/' "${nvidia_file}" 2> /dev/null; then
					dest_dir="$(dirname "${dest_file}")"
					if mkdir -p "${dest_dir}" 2> /dev/null && [ -w "${dest_dir}" ]; then
						sed 's@\("library_path"[[:space:]]*:[[:space:]]*"\)/[^"]*/\([^"/]*"\)@\1\2@g' \
							"${nvidia_file}" > "${dest_file}"
						continue
					fi
				fi
				;;
			*) ;; # non-JSON configs carry no library_path to rewrite
		esac
		mirror_host_ro "${nvidia_file}" "${dest_file}"
	done

	# Then we find all the CLI utilities
	NVIDIA_BINARIES="$(find /run/host/bin/ /run/host/sbin/ /run/host/usr/bin/ /run/host/usr/sbin/ -not -type d \
		-iname "*nvidia*" || :)"
	for nvidia_file in ${NVIDIA_BINARIES}; do
		mirror_host_ro "${nvidia_file}" "${nvidia_file#/run/host}"
	done

	# nVidia userspace libraries.
	#
	# Linker-resolved libs (libcuda, libGLX_nvidia, libnvidia-*, ...) go in a
	# private dir an /etc/ld.so.conf.d entry (below) points the linker at, so
	# they never clash with the guest's own libGL/Mesa, split into lib32/lib64
	# by ELF class (header byte 5: 1=32, 2=64) so a lib's two builds never collide.
	#
	# Fixed-path plugins - the Xorg/GBM/VDPAU drivers and Wine/NGX DLSS DLLs -
	# are dlopen'd by a compiled-in "<libdir>/<plugin>" path, not the linker, so
	# instead they go under the guest's native lib root for their ELF class
	# (keeping the "<plugin>/..." tail), again so the 32/64 builds never collide.
	nvidia_libdir="/usr/lib/distrobox-nvidia"
	mkdir -p "${nvidia_libdir}/lib64" "${nvidia_libdir}/lib32"

	# Native library root of the guest for each ELF class. 64-bit is probed
	# directly; the 32-bit root is derived from the distro convention
	guest_lib64="/usr/lib"
	for libroot in /usr/lib/x86_64-linux-gnu /usr/lib64 /usr/lib; do
		[ -d "${libroot}" ] && {
			guest_lib64="${libroot}"
			break
		}
	done
	case "${guest_lib64}" in
		*/x86_64-linux-gnu) guest_lib32="/usr/lib/i386-linux-gnu" ;; # Debian/Ubuntu multiarch
		*/lib64)
			# Fedora/SUSE keep 32-bit in /usr/lib; on Arch /usr/lib64 is a
			# symlink to the 64-bit /usr/lib, so its 32-bit tree is /usr/lib32.
			if [ "$(readlink -m /usr/lib64)" = "$(readlink -m /usr/lib)" ]; then
				guest_lib32="/usr/lib32"
			else
				guest_lib32="/usr/lib"
			fi
			;;
		*) guest_lib32="/usr/lib32" ;; # bare /usr/lib is 64-bit (Arch-like)
	esac
	# Single-tree distros: if 32-bit still resolves onto the 64-bit root, skip
	# 32-bit plugins rather than shadow it.
	[ "$(readlink -m "${guest_lib32}")" = "$(readlink -m "${guest_lib64}")" ] && guest_lib32=""

	NVIDIA_LIBS="$(find /run/host/usr/lib*/ -not -type d \
		-iname "*lib*nvidia*.so*" \
		-o -iname "*nvidia*.so*" \
		-o -iname "libcuda*.so*" \
		-o -iname "libnvcuvid*" \
		-o -iname "libnvoptix*" \
		-o -wholename "*/nvidia/wine/*" || :)"
	for nvidia_lib in ${NVIDIA_LIBS}; do
		# Resolve symlinks to the real ELF we mount and read the class from.
		real_lib="${nvidia_lib}"
		if [ -L "${real_lib}" ]; then
			real_lib="$(readlink -fm "${real_lib}")"
		fi

		# ELF class of the backing file (byte 5: 1 = 32-bit, 2 = 64-bit) picks
		# both the guest's native root and our private bucket.
		if [ "$(od -An -t u1 -j 4 -N 1 "${real_lib}" 2> /dev/null | tr -d ' ')" = "1" ]; then
			nvidia_class_root="${guest_lib32}"
			nvidia_bucket="${nvidia_libdir}/lib32"
		else
			nvidia_class_root="${guest_lib64}"
			nvidia_bucket="${nvidia_libdir}/lib64"
		fi

		case "${nvidia_lib}" in
			*/xorg/* | */gbm/* | */vdpau/* | */wine/*)
				# Fixed-path plugin/DLL: guest's native root for its class,
				# keeping the "<plugin>/..." path below the host library dir.
				[ -n "${nvidia_class_root}" ] || continue
				nvidia_sub="${nvidia_lib#/run/host}"
				case "${nvidia_sub}" in
					/usr/lib/*-linux-*/*) nvidia_sub="${nvidia_sub#/usr/lib/*-linux-*/}" ;;
					/usr/lib64/*) nvidia_sub="${nvidia_sub#/usr/lib64/}" ;;
					/usr/lib32/*) nvidia_sub="${nvidia_sub#/usr/lib32/}" ;;
					/usr/lib/*) nvidia_sub="${nvidia_sub#/usr/lib/}" ;;
					*) ;;
				esac
				dest_file="${nvidia_class_root}/${nvidia_sub}"
				;;
			*)
				# Linker library: isolated dir, bucketed by ELF class.
				dest_file="${nvidia_bucket}/$(basename "${nvidia_lib}")"
				;;
		esac

		# If the destination already exists, there is nothing to do.
		if [ -e "${dest_file}" ]; then
			continue
		fi

		mirror_host_ro "${real_lib}" "${dest_file}"
	done

	# Point the dynamic linker at our directory and refresh the cache. ldconfig
	# tags each entry by ELF class, so one conf covering both buckets is enough.
	printf '%s\n%s\n' "${nvidia_libdir}/lib64" "${nvidia_libdir}/lib32" \
		> /etc/ld.so.conf.d/00-distrobox-nvidia.conf
	ldconfig 2> /dev/null

fi

###############################################################################
printf "distrobox: Integrating host's themes, icons, fonts...\n"
# Themes and icons integration works using a bind mount inside the container
# of the host's themes and icons directory. This ensures that the host's home will
# not be littered with files and directories and broken symlinks.
if ! mount_bind "/run/host/usr/share/themes" "/usr/local/share/themes"; then
	printf "Warning: Cannot bind mount /run/host/usr/share/themes to /usr/local/share/themes\n"
	printf "Warning: Themes integration with the host is disabled.\n"
fi
if ! mount_bind "/run/host/usr/share/icons" "/usr/local/share/icons"; then
	printf "Warning: Cannot bind mount /run/host/usr/share/icons to /usr/local/share/icons\n"
	printf "Warning: Icons integration with the host is disabled.\n"
fi
if ! mount_bind "/run/host/usr/share/fonts" "/usr/local/share/fonts"; then
	printf "Warning: Cannot bind mount /run/host/usr/share/fonts to /usr/local/share/fonts\n"
	printf "Warning: Fonts integration with the host is disabled.\n"
fi
###############################################################################

printf "distrobox: Setting up distrobox profile...\n"

# This ensures compatibility with prompts and tools between toolbx and distrobox
touch /run/.toolboxenv

# Ensure we have some basic env variables and prompt as base if /etc/profile.d is missing
if [ ! -d /etc/profile.d ]; then
	rcfiles="
		/etc/profile
		/etc/bash.bashrc
		/etc/bashrc
		/etc/zshrc
	"
	for rcfile in ${rcfiles}; do
		if [ -e "${rcfile}" ] && ! grep -q 'distrobox_profile.sh' "${rcfile}"; then
			echo "[ -e /etc/profile.d/distrobox_profile.sh ] && . /etc/profile.d/distrobox_profile.sh" >> "${rcfile}"
		fi
	done
	mkdir -p /etc/profile.d
fi
cat << EOF > /etc/profile.d/distrobox_profile.sh
test -z "\$USER" && export USER="\$(id -un 2> /dev/null)"
test -z "\$UID"  && readonly UID="\$(id -ur 2> /dev/null)"
test -z "\$EUID" && readonly EUID="\$(id -u  2> /dev/null)"
export SHELL="\$(getent passwd "\${USER}" | cut -f 7 -d :)"

test -z "\${XDG_RUNTIME_DIR:-}" && export XDG_RUNTIME_DIR="/run/user/\$(id -ru)"
test -z "\${DBUS_SESSION_BUS_ADDRESS:-}" && export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/\$(id -ru)/bus"

# Ensure we have these two variables from the host, so that graphical apps
# also work in case we use a login session
if [ -z "\$XAUTHORITY" ]; then
    export XAUTHORITY="\$(host-spawn sh -c "printf "%s" \\\$XAUTHORITY")"
    # if the variable is still empty, unset it, because empty it could be harmful
    [ -z "\$XAUTHORITY" ] && unset XAUTHORITY
fi
if [ -z "\$XAUTHLOCALHOSTNAME" ]; then
    export XAUTHLOCALHOSTNAME="\$(host-spawn sh -c "printf "%s" \\\$XAUTHLOCALHOSTNAME")"
    [ -z "\$XAUTHLOCALHOSTNAME" ] && unset XAUTHLOCALHOSTNAME
fi
if [ -z "\$WAYLAND_DISPLAY" ]; then
    export WAYLAND_DISPLAY="\$(host-spawn sh -c "printf "%s" \\\$WAYLAND_DISPLAY")"
    [ -z "\$WAYLAND_DISPLAY" ] && unset WAYLAND_DISPLAY
fi
if [ -z "\$DISPLAY" ]; then
    export DISPLAY="\$(host-spawn sh -c "printf "%s" \\\$DISPLAY")"
    [ -z "\$DISPLAY" ] && unset DISPLAY
fi

# This will ensure a default prompt for a container, this will be remineshent of
# toolbx prompt: https://github.com/containers/toolbox/blob/main/profile.d/toolbox.sh#L47
# this will ensure greater compatibility between the two implementations
if [ -f /run/.toolboxenv ]; then
    [ "\${BASH_VERSION:-}" != "" ] && export PS1="📦[\u@\$CONTAINER_ID \W]\$ "
    [ "\${ZSH_VERSION:-}" != "" ] && export PS1="📦[%n@\$CONTAINER_ID]%~%# "
fi

# This will ensure we have a first-shell password setup for an user if needed.
# We're going to use this later in case of rootful containers
if [ -e /var/tmp/.\$USER.passwd.initialize ]; then
	echo "⚠️  First time user password setup ⚠️ "
	trap "echo; exit" INT
	passwd && rm -f /var/tmp/.\$USER.passwd.initialize
	trap - INT
fi
EOF

# It's also importanto to keep this working on fish shells
if [ -e "/etc/fish/config.fish" ]; then
	mkdir -p /etc/fish/conf.d
	cat << EOF > /etc/fish/conf.d/distrobox_config.fish
if status --is-interactive
	test -z "\$USER" && set -gx USER (id -un 2> /dev/null)
	test -z "\$UID"  && set -gx UID (id -ur 2> /dev/null)
	test -z "\$EUID" && set -gx EUID (id -u  2> /dev/null)
	set -gx SHELL (getent passwd "\$USER" | cut -f 7 -d :)

	test -z "\$XDG_RUNTIME_DIR && set -gx XDG_RUNTIME_DIR /run/user/(id -ru)
	test -z "\$DBUS_SESSION_BUS_ADDRESS && set -gx DBUS_SESSION_BUS_ADDRESS unix:path=/run/user/(id -ru)/bus

	# Ensure we have these two variables from the host, so that graphical apps
	# also work in case we use a login session
	if test -z \$XAUTHORITY
		set -gx XAUTHORITY (host-spawn sh -c "printf "%s" \\\$XAUTHORITY")
		# if the variable is still empty, unset it, because empty it could be harmful
		test -z \$XAUTHORITY ; and set -e XAUTHORITY
	end
	if test -z \$XAUTHLOCALHOSTNAME
		set -gx XAUTHLOCALHOSTNAME (host-spawn sh -c "printf "%s" \\\$XAUTHLOCALHOSTNAME")
		test -z \$XAUTHLOCALHOSTNAME ; and set -e XAUTHLOCALHOSTNAME
	end
	if test -z \$WAYLAND_DISPLAY
		set -gx WAYLAND_DISPLAY (host-spawn sh -c "printf "%s" \\\$WAYLAND_DISPLAY")
		test -z \$WAYLAND_DISPLAY ; and set -e WAYLAND_DISPLAY
	end
	if test -z \$DISPLAY
		set -gx DISPLAY (host-spawn sh -c "printf "%s" \\\$DISPLAY")
		test -z \$DISPLAY ; and set -e DISPLAY
	end

	# This will ensure we have a first-shell password setup for an user if needed.
	# We're going to use this later in case of rootful containers
	if test -e /var/tmp/.\$USER.passwd.initialize
		echo "⚠️  First time user password setup ⚠️ "
		trap "echo; exit" INT
		passwd && rm -f /var/tmp/.\$USER.passwd.initialize
		trap - INT
	end
end
EOF
	mkdir -p /etc/fish/functions
	cat << EOF > /etc/fish/functions/fish_prompt.fish
function fish_prompt
	set current_dir (basename (pwd))
	echo "📦[\$USER@\$CONTAINER_ID \$current_dir]> "
end
EOF
fi
###############################################################################

###############################################################################
printf "distrobox: Setting up sudo...\n"
mkdir -p /etc/sudoers.d
# Ensure we're using the user's password for sudo, not root
if [ -e /etc/sudoers ]; then
	sed -i "s|^Defaults targetpw.*||g" /etc/sudoers
fi

# Do not check fqdn when doing sudo, it will not work anyways
# Also allow canonical groups to use sudo
cat << EOF > /etc/sudoers.d/sudoers
Defaults !targetpw
Defaults !fqdn
%wheel ALL=(ALL:ALL) ALL
%sudo ALL=(ALL:ALL) ALL
%root ALL=(ALL:ALL) ALL
EOF
# ditto for doas
if [ -e /etc/doas.conf ]; then
	cat << EOF > /etc/doas.conf
permit persist :root
permit persist :wheel
permit nopass root
permit nopass keepenv setenv { PATH } root as root
EOF
fi
# if no sudo but doas is present, we want to have it as an alias
# of sudo, so other part of the program will find it as expected
if command -v doas && ! command -v sudo; then
	ln -sf "$(command -v doas)" /usr/bin/sudo
fi

# PAM config for "su" command
if [ ! -e /etc/pam.d/su ]; then
	mkdir -p /etc/pam.d
	cat << EOF > /etc/pam.d/su
auth            sufficient      pam_rootok.so
auth            required        pam_unix.so
account	        required        pam_unix.so
session         required        pam_unix.so
-session        optional        pam_systemd.so
EOF
fi

if ! grep -q "pam_systemd.so" /etc/pam.d/su; then
	printf "%s" '-session   optional   pam_systemd.so' >> /etc/pam.d/su
fi

# If we're running this script as root in a login shell (sudoless), we don't
# have to bother setting up sudo.
#
# Also if we're in a rootful container, we will setup user's password,
# so let's skip passwordless sudo too
if [ "${container_user_uid}" -ne 0 ] && [ "${rootful}" -eq 0 ]; then
	# Ensure passwordless sudo is set up for user
	printf "\"%s\" ALL = (root) NOPASSWD:ALL\n" "${container_user_name}" >> /etc/sudoers.d/sudoers
	# ditto for doas
	if [ -e /etc/doas.conf ]; then
		printf "permit nopass %s\n" "${container_user_name}" >> /etc/doas.conf
	fi
fi
###############################################################################

###############################################################################
# If not existing, ensure we have a group for our user.
if ! grep -q "^${container_user_name}:" /etc/group; then
	printf "distrobox: Setting up user groups...\n"

	if ! groupadd --force --gid "${container_user_gid}" "${container_user_name}"; then
		# It may occur that we have users with unsupported user name (eg. on LDAP or AD)
		# So let's try and force the group creation this way.
		printf "%s:x:%s:\n" "${container_user_name}" "${container_user_gid}" >> /etc/group
	fi
fi
###############################################################################

###############################################################################

# Setup kerberos integration with the host
if [ -d "/run/host/var/kerberos" ] &&
	[ -d "/etc/krb5.conf.d" ] &&
	[ ! -e "/etc/krb5.conf.d/kcm_default_ccache" ]; then

	printf "distrobox: Setting up kerberos integration...\n"

	cat << EOF > /etc/krb5.conf.d/kcm_default_ccache
# # To disable the KCM credential cache, comment out the following lines.
[libdefaults]
    default_ccache_name = KCM:
EOF
fi

printf "distrobox: Setting up user's group list...\n"
# If we have sudo/wheel groups, let's add the user to them.
# and ensure that user's in those groups can effectively sudo
additional_groups=""
if grep -q "^sudo" /etc/group; then
	additional_groups="sudo"
elif grep -q "^wheel" /etc/group; then
	additional_groups="wheel"
elif grep -q "^root" /etc/group; then
	additional_groups="root"
fi

# Let's add our user to the container. if the user already exists, enforce properties.
#
# In case of AD or LDAP usernames, it is possible we will have a backslach in the name.
# In that case grep would fail, so we replace the backslash with a point to make the regex work.
# shellcheck disable=SC1003
if ! grep -q "^$(printf '%s' "${container_user_name}" | tr '\\' '.'):" /etc/passwd &&
	! getent passwd "${container_user_uid}"; then
	printf "distrobox: Adding user...\n"
	if ! useradd \
		--home-dir "${container_user_home}" \
		--no-create-home \
		--groups "${additional_groups}" \
		--shell "${SHELL:-"/bin/bash"}" \
		--uid "${container_user_uid}" \
		--gid "${container_user_gid}" \
		"${container_user_name}"; then

		printf "Warning: There was a problem setting up the user with usermod, trying manual addition\n"

		printf "%s:x:%s:%s:%s:%s:%s\n" \
			"${container_user_name}" "${container_user_uid}" \
			"${container_user_gid}" "${container_user_name}" \
			"${container_user_home}" "${SHELL:-"/bin/bash"}" >> /etc/passwd
		printf "%s::1::::::" "${container_user_name}" >> /etc/shadow

		# Also add user to any additional groups when useradd failed
		for group in ${additional_groups}; do
			if grep -q "^${group}:" /etc/group && ! grep -q "^${group}.*${container_user_name}" /etc/group; then
				group_line="$(grep "^${group}:" /etc/group)"
				if grep -q "^${group}:.*:$" /etc/group; then
					sed -i "s|${group_line}|${group_line}${container_user_name}|g" /etc/group
				else
					sed -i "s|${group_line}|${group_line},${container_user_name}|g" /etc/group
				fi
			fi
		done
	fi
# Ensure we're not using the specified SHELL. Run it only once, so that future
# user's preferences are not overwritten at each start.
elif [ ! -e /etc/passwd.done ]; then
	# This situation is presented when podman or docker already creates the user
	# for us inside container. We should modify the user's prepopulated shadowfile
	# entry though as per user's active preferences.

	# Get current user attributes using container_user_uid as the reference
	# (script runs as root, so we must look up the target user by UID)
	current_user_entry=$(getent passwd "${container_user_uid}")
	current_user_name=$(printf '%s' "${current_user_entry}" | cut -d: -f1)
	current_shell=$(printf '%s' "${current_user_entry}" | cut -d: -f7)
	current_gid=$(printf '%s' "${current_user_entry}" | cut -d: -f4)
	current_groups=$(id -nG "${current_user_name}" 2> /dev/null)

	# Modify username if needed
	if [ "${current_user_name}" != "${container_user_name}" ]; then
		printf "distrobox: Setting up existing user - username...\n"
		if ! usermod --login "${container_user_name}" "${current_user_name}"; then
			printf "Warning: usermod --login failed, trying manual modification\n"
			sed -i "s|^${current_user_name}:|${container_user_name}:|g" /etc/passwd
			if ! getent passwd "${container_user_name}" > /dev/null 2>&1; then
				printf "Error: Failed to modify user login name\n" >&2
				exit 1
			fi
		fi
		# Update current_user_name for subsequent commands
		current_user_name="${container_user_name}"
	fi

	# Modify shell if needed
	if [ "${current_shell}" != "${SHELL:-"/bin/bash"}" ]; then
		printf "distrobox: Setting up existing user - shell...\n"
		if ! usermod --shell "${SHELL:-"/bin/bash"}" "${current_user_name}"; then
			printf "Warning: usermod --shell failed, trying manual modification\n"
			# sed to update shell field (7th field) in /etc/passwd
			sed -i "s|^\(${current_user_name}:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:\).*|\1${SHELL:-"/bin/bash"}|g" /etc/passwd
		fi
	fi

	# Modify GID if needed
	if [ "${current_gid}" != "${container_user_gid}" ]; then
		printf "distrobox: Setting up existing user - GID...\n"
		if ! usermod --gid "${container_user_gid}" "${current_user_name}"; then
			printf "Warning: usermod --gid failed, trying manual modification\n"
			# sed to update gid field (4th field) in /etc/passwd
			sed -i "s|^\(${current_user_name}:[^:]*:[^:]*:\)[^:]*|\1${container_user_gid}|g" /etc/passwd
		fi
	fi

	# Modify groups if needed (check if user is missing from any additional group)
	groups_need_modification=0
	for group in ${additional_groups}; do
		if ! printf '%s' " ${current_groups} " | grep -q " ${group} "; then
			groups_need_modification=1
			break
		fi
	done
	if [ "${groups_need_modification}" -eq 1 ]; then
		printf "distrobox: Setting up existing user - groups...\n"
		# Workaround: usermod --groups fails if an /etc/group file does not end with
		# a newline, so we preemptively add one just in case.
		printf '\n' >> /etc/group
		if ! usermod --append --groups "${additional_groups}" "${current_user_name}"; then
			printf "Warning: usermod --groups failed, trying manual modification\n"
			for group in ${additional_groups}; do
				if ! grep -q "^${group}.*${current_user_name}.*" /etc/group; then
					group_line="$(grep "^${group}.*" /etc/group)"
					if grep -q "^${group}.*:$" /etc/group; then
						sed -i "s|${group_line}|${group_line}${current_user_name}|g" /etc/group
					else
						sed -i "s|${group_line}|${group_line},${current_user_name}|g" /etc/group
					fi
				fi
			done
		fi
	fi

	# Modify UID if needed
	current_uid=$(getent passwd "${current_user_name}" | cut -d: -f3)
	if [ "${current_uid}" != "${container_user_uid}" ]; then
		printf "distrobox: UID...\n"
		if ! usermod --uid "${container_user_uid}" "${current_user_name}"; then
			printf "Warning: usermod --uid failed, trying manual modification\n"
			sed -i "s|^\(${current_user_name}:[^:]*:\)[^:]*|\1${container_user_uid}|g" /etc/passwd
		fi
	fi
fi

# Ensure we have our home correctly set, in case of cloned containers or whatnot
if [ "$(getent passwd "${container_user_name}" | cut -d: -f6)" != "${container_user_home}" ]; then
	printf "distrobox: Setting up user home...\n"

	if ! usermod -d "${container_user_home}" "${container_user_name}"; then
		sed -i "s|^${container_user_name}.*|${container_user_name}:x:${container_user_uid}:${container_user_gid}::${container_user_home}:${SHELL:-"/bin/bash"}|g" /etc/passwd
	fi
fi

# If we're rootless, delete password for root and user
if [ ! -e /etc/passwd.done ]; then
	printf "distrobox: Ensuring user's access...\n"

	temporary_password="$(md5sum < /proc/sys/kernel/random/uuid | cut -d' ' -f1)"
	# We generate a random password to initialize the entry for the user.
	chpasswd_failed=0
	printf "%s:%s" "${container_user_name}" "${temporary_password}" | chpasswd || chpasswd_failed=1
	# Then we remove the password for current user
	if ! passwd -d "${container_user_name}"; then
		# Fallback to chpasswd for older systems without passwd -d
		printf "%s:" "${container_user_name}" | chpasswd || chpasswd_failed=1
	fi

	if [ "${chpasswd_failed}" -eq 1 ]; then
		printf "Warning: There was a problem setting up the user, trying manual addition\n"
		if grep -q "${container_user_name}" /etc/shadow; then
			sed -i "s|^${container_user_name}.*|${container_user_name}::::::::|g" /etc/shadow
		else
			echo "${container_user_name}::::::::" >> /etc/shadow
		fi
	fi

	if [ "${rootful}" -eq 0 ]; then
		# We're rootless so we don't care about account password, so we remove it
		passwd_cmd=passwd
		if passwd --help 2>&1 | grep -q -- --stdin; then
			passwd_cmd="passwd --stdin"
		fi
		printf "%s\n%s\n" "${temporary_password}" "${temporary_password}" | ${passwd_cmd} root
		if ! passwd -d "root"; then
			# Fallback to chpasswd for older systems without passwd -d
			printf "%s:" "root" | chpasswd
		fi
	else
		# We're rootful, so we don't want passwordless accounts, so we lock them
		# down by default.

		# lock out root user
		if ! usermod -L root; then
			sed -i 's|^root.*|root:!:1::::::|g' /etc/shadow
		fi
	fi
fi

# If we are in a rootful container, let's setup a first-shell password setup
# so that sudo, and su has a password
#
# else we fallback to the usual setup with passwordless sudo/su user. This is
# likely because we're in a rootless setup, so privilege escalation is not a concern.
if [ "${rootful}" -eq 1 ] &&
	{
		[ "$(grep "${container_user_name}" /etc/shadow | cut -d':' -f2)" = '!!' ] ||
			[ "$(grep "${container_user_name}" /etc/shadow | cut -d':' -f2)" = "" ]
	}; then

	# force setup of user's password on first shell
	if [ ! -e /var/tmp ]; then
		mkdir -p /var/tmp
		chmod 0777 /var/tmp
	fi
	touch /var/tmp/."${container_user_name}".passwd.initialize
	chown "${container_user_name}:${container_user_gid}" /var/tmp/."${container_user_name}".passwd.initialize
fi

# Now we're done
touch /etc/passwd.done

# Ensure shadow files are readable by root without relying on CAP_DAC_OVERRIDE,
# which may not be effective on all container storage drivers (e.g. fuse-overlayfs
# in rootless mode, or VMs like Docker Desktop / Colima on macOS).
# Fedora/Arch ship these as mode 000, expecting the capability to bypass DAC.
chmod 0400 /etc/shadow 2> /dev/null || :
chmod 0400 /etc/gshadow 2> /dev/null || :
###############################################################################

###############################################################################
if [ -n "${DISTROBOX_HOST_HOME-}" ] && [ -d "/etc/skel" ]; then
	printf "distrobox: Setting up skel...\n"

	# If we do not have profile files in the home, we should copy the
	# skeleton files, if present.
	# Ensure we copy only if the dotfile is not already present.
	skel_files="$(find /etc/skel/ -type f || :)"
	for skel_file in ${skel_files}; do
		base_file_name=$(basename "${skel_file}")
		skel_file_path=$(dirname "${skel_file}")
		file_path_for_home=${skel_file_path#/etc/skel}

		if [ -n "${file_path_for_home}" ] &&
			[ ! -d "${container_user_home}/${file_path_for_home:+"${file_path_for_home}"}" ]; then
			mkdir -p "${container_user_home}/${file_path_for_home:+"${file_path_for_home}"/}"
			chown "${container_user_uid}":"${container_user_gid}" "${container_user_home}/${file_path_for_home:+"${file_path_for_home}"/}"
		fi

		if [ ! -f "${container_user_home}/${file_path_for_home:+"${file_path_for_home}"/}${base_file_name}" ] &&
			[ ! -L "${container_user_home}/${file_path_for_home:+"${file_path_for_home}"/}${base_file_name}" ]; then
			cp "${skel_file}" "${container_user_home}/${file_path_for_home:+"${file_path_for_home}"/}${base_file_name}"
			chown "${container_user_uid}":"${container_user_gid}" "${container_user_home}/${file_path_for_home:+"${file_path_for_home}"/}${base_file_name}"
		fi

	done
fi
###############################################################################

###############################################################################
if [ -n "${init_hook}" ]; then
	printf "distrobox: Executing init hooks...\n"
	# execute eventual init hooks if specified
	# shellcheck disable=SC2086
	eval ${init_hook}
fi
###############################################################################

HOST_WATCH="
	/etc/hostname
	/etc/hosts
	/etc/localtime
	/etc/resolv.conf
"
id="${CONTAINER_ID:-}"
if [ -e /run/.containerenv ]; then
	# shellcheck disable=SC1091,SC2034
	. /run/.containerenv
elif [ -e /.dockerenv ]; then
	id="$(curl -s --unix-socket /run/docker.sock http://docker/containers/"${CONTAINER_ID:-$(hostname | cut -d'.' -f1)}"/json |
		grep -Eo '"Id":"[a-zA-Z0-9]{64}",' | cut -d '"' -f4)"
fi

###############################################################################
# If init support is disabled, let's do our routine to keep the container
# up, running and in sync with host.
#
# For non-init containers, the init will stop here
if [ "${init}" -eq 0 ]; then
	printf "container_setup_done\n"
	# Keepalive loop
	# disable verbose logging for this phase.
	set +x
	while true; do
		# Let's check for changes every 15 seconds.
		# This way we can dynamically keep hosts, dns and timezone setups
		# in sync with host, without having permissions problems:
		#	- symlink will fail with "Device or Resource busy"
		#	- bindmount will need a container restart on changes
		for file_watch in ${HOST_WATCH}; do
			# do stuff, only if the file is a mountpoint, and if the mountpoint is NOT containing the
			# container id, because if it does, it is because it's part of the podman/docker setup
			# The mount point might not exist, either because it's umounted or it doesn't exist on
			# host in some cases like /etc/localtime, so ignore findmnt errors
			mount_source="$(findmnt -no SOURCE "${file_watch}")" || :
			if [ -n "${mount_source}" ] && ! echo "${mount_source}" | grep -q "${id}"; then
				file_watch_src="/run/host${file_watch}"
				# check if the target file exists
				if ls -l "${file_watch_src}" 2> /dev/null > /dev/null; then
					# if it's a symlink and take the source
					if [ -L "${file_watch_src}" ]; then
						file_watch_src="$(init_readlink "/run/host${file_watch}")"
						# if it's an absolute link, we need to append /run/host ourselves.
						if ! printf "%s" "${file_watch_src}" | grep -q "/run/host"; then
							file_watch_src="/run/host${file_watch_src}"
						fi
					fi
					if ! diff "${file_watch}" "${file_watch_src}" > /dev/null; then
						# We only do this, if the file is actually different
						umount "${file_watch}" &&
							mount_bind "${file_watch_src}" "${file_watch}"

						# Let's keep in sync host's hostname and container's hostname
						if [ "${file_watch}" = "/etc/hostname" ]; then
							hostname "$(cat /etc/hostname)"
						fi
					fi
				fi
			fi
		done
		sleep 15
	done
fi
###############################################################################

###############################################################################
# If we're here, the init support has been enabled.
printf "distrobox: Setting up init system...\n"

# some of this directories are needed by
# the init system. If they're mounts, there might
# be problems. Let's unmount them.
for host_mount in ${HOST_MOUNTS_RO_INIT}; do
	if findmnt "${host_mount}" > /dev/null; then umount "${host_mount}"; fi
done

# Remove symlinks
rm -f /run/systemd/coredump
rm -f /run/systemd/io.system.ManagedOOM
rm -f /run/systemd/notify
rm -f /run/systemd/private

# Restore the symlink if it's an empty file
if [ -f /etc/localtime ]; then
	rm -f /etc/localtime
	ln -sf /usr/share/zoneinfo/UCT /etc/localtime
fi

# Remove /dev/console when using init systems, this will confuse host system if
# we use rootful containers
# Instantiate a new pty to mount over /dev/console
# this way we will have init output right of the logs
[ -e /dev/console ] || touch /dev/console
rm -f /var/console
mkfifo /var/console
script -c "cat /var/console" /dev/null &

# Ensure the pty is created
sleep 0.5

# Mount the created pty over /dev/console in order to have systemd logs
# right into container logs
if ! mount --bind /dev/pts/0 /dev/console; then
	# Fallback to older behaviour or fake plaintext file in case it fails
	# this ensures rootful + initful boxes do not interfere with host's /dev/console
	rm -f /var/console
	touch /var/console
	mount --bind /var/console /dev/console
fi

if [ -e /etc/inittab ]; then
	# Cleanup openrc to not interfere with the host
	sed -i 's/^\(tty\d\:\:\)/#\1/g' /etc/inittab
fi

if [ -e /etc/rc.conf ]; then
	sed -i \
		-e 's/#rc_env_allow=".*"/rc_env_allow="\*"/g' \
		-e 's/#rc_crashed_stop=.*/rc_crashed_stop=NO/g' \
		-e 's/#rc_crashed_start=.*/rc_crashed_start=YES/g' \
		-e 's/#rc_provide=".*"/rc_provide="loopback net"/g' \
		/etc/rc.conf
fi

if [ -e /etc/init.d ]; then
	rm -f /etc/init.d/hwdrivers \
		/etc/init.d/hwclock \
		/etc/init.d/hwdrivers \
		/etc/init.d/modules \
		/etc/init.d/modules-load \
		/etc/init.d/modloop
fi

if command -v systemctl 2> /dev/null; then
	# Cleanup Systemd to not interfere with the host
	UNIT_TARGETS="
		/usr/lib/systemd/system/*.mount
		/usr/lib/systemd/system/console-getty.service
		/usr/lib/systemd/system/getty@.service
		/usr/lib/systemd/system/systemd-machine-id-commit.service
		/usr/lib/systemd/system/systemd-binfmt.service
		/usr/lib/systemd/system/systemd-tmpfiles*
		/usr/lib/systemd/system/systemd-udevd.service
		/usr/lib/systemd/system/systemd-udev-trigger.service
		/usr/lib/systemd/system/systemd-update-utmp*
		/usr/lib/systemd/user/pipewire*
		/usr/lib/systemd/user/wireplumber*
		/usr/lib/systemd/system/suspend.target
		/usr/lib/systemd/system/hibernate.target
		/usr/lib/systemd/system/hybrid-sleep.target
		/usr/lib/systemd/system/systemd-remount-fs.service
	"

	# in case /etc/resolv.conf is a mount, we need to mask resolved
	# in this case we're using network=host and systemd-resolved won't
	# be able to bind to localhost:53
	mount_source="$(findmnt -no SOURCE /etc/resolv.conf)" || :
	if [ -n "${mount_source}" ] && ! echo "${mount_source}" | grep -q "${id}"; then
		UNIT_TARGETS="${UNIT_TARGETS}
			/usr/lib/systemd/system/systemd-resolved.service
		"
	fi

	# shellcheck disable=SC2086,SC2044
	for unit in $(find ${UNIT_TARGETS} 2> /dev/null); do
		systemctl mask "$(basename "${unit}")" || :
	done
fi

# Let's do a minimal user-integration for the user when using system
# as the user@.service will trigger the user-runtime-dir@.service which will
# undo all the integration we did at the start of the script
#
# This will ensure the basic integration for x11/wayland/pipewire/keyring
if [ -e /usr/lib/systemd/system/user@.service ]; then
	cat << EOF > /usr/local/bin/user-integration
#!/bin/sh
sleep 1
ln -sf /run/host/run/user/\$(id -ru)/wayland-* /run/user/\$(id -ru)/
ln -sf /run/host/run/user/\$(id -ru)/pipewire-* /run/user/\$(id -ru)/
find /run/host/run/user/\$(id -ru)/ -maxdepth 1 -type f -exec sh -c 'grep -qlE COOKIE \$0 && ln -sf \$0 /run/user/\$(id -ru)/\$(basename \$0)' {} \;
mkdir -p /run/user/\$(id -ru)/app && ln -sf /run/host/run/user/\$(id -ru)/app/* /run/user/\$(id -ru)/app/
mkdir -p /run/user/\$(id -ru)/at-spi && ln -sf /run/host/run/user/\$(id -ru)/at-spi/* /run/user/\$(id -ru)/at-spi/
mkdir -p /run/user/\$(id -ru)/dbus-1 && ln -sf /run/host/run/user/\$(id -ru)/dbus-1/* /run/user/\$(id -ru)/dbus-1/
mkdir -p /run/user/\$(id -ru)/dconf && ln -sf /run/host/run/user/\$(id -ru)/dconf/* /run/user/\$(id -ru)/dconf/
mkdir -p /run/user/\$(id -ru)/gnupg && ln -sf /run/host/run/user/\$(id -ru)/gnupg/* /run/user/\$(id -ru)/gnupg/
mkdir -p /run/user/\$(id -ru)/keyring && ln -sf /run/host/run/user/\$(id -ru)/keyring/* /run/user/\$(id -ru)/keyring/
mkdir -p /run/user/\$(id -ru)/p11-kit && ln -sf /run/host/run/user/\$(id -ru)/p11-kit/* /run/user/\$(id -ru)/p11-kit/
mkdir -p /run/user/\$(id -ru)/pulse && ln -sf /run/host/run/user/\$(id -ru)/pulse/* /run/user/\$(id -ru)/pulse/
find /run/user/\$(id -ru) -maxdepth 2 -xtype l -delete
EOF

	chmod +x /usr/local/bin/user-integration

	cat << EOF > /usr/lib/systemd/system/user-integration@.service
[Unit]
Description=User runtime integration for UID %i
After=user@%i.service
Requires=user-runtime-dir@%i.service

[Service]
User=%i
Type=oneshot
ExecStart=/usr/local/bin/user-integration

Slice=user-%i.slice
EOF
fi

# Now we can launch init
printf "distrobox: Firing up init system...\n"

if [ -e /usr/lib/systemd/systemd ] || [ -e /lib/systemd/systemd ]; then
	# Start user Systemd unit, this will attempt until Systemd is ready
	sh -c "timeout=120 && sleep 1 && while [ \"\${timeout}\" -gt 0 ]; do \
	    systemctl is-system-running | grep -E 'running|degraded' && break; \
		echo 'waiting for systemd to come up...\n' && sleep 1 && timeout=\$(( timeout -1 )); \
	done && \
	systemctl start user@${container_user_name}.service && \
	systemctl start user-integration@${container_user_name}.service && \
	loginctl enable-linger ${container_user_name} || : && \
	echo container_setup_done" &

	[ -e /usr/lib/systemd/systemd ] && exec /usr/lib/systemd/systemd --system --log-target=console --unit=multi-user.target
	[ -e /lib/systemd/systemd ] && exec /lib/systemd/systemd --system --log-target=console --unit=multi-user.target

elif [ -e /sbin/init ]; then
	printf "container_setup_done\n"

	# Fallback to standard init path, this is useful in case of non-Systemd containers
	# like an openrc alpine
	exec /sbin/init
else
	printf "Error: could not set up init system, no init found! Consider using an image that ships with an init system, or add it with \"--additional-packages\" during creation.!\n"
	exit 1
fi
