442 lines
9.0 KiB
Bash
442 lines
9.0 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# Host dependency bootstrap shared by install.sh and release validation.
|
|
|
|
ptlinux_dependency_family()
|
|
{
|
|
local distribution_id="${1,,}"
|
|
local distribution_like=" ${2,,} "
|
|
|
|
case "$distribution_id" in
|
|
ubuntu|debian|linuxmint|pop|elementary|zorin)
|
|
printf '%s\n' "debian"
|
|
return 0
|
|
;;
|
|
fedora|nobara)
|
|
printf '%s\n' "fedora"
|
|
return 0
|
|
;;
|
|
arch|cachyos|endeavouros|garuda|manjaro)
|
|
printf '%s\n' "arch"
|
|
return 0
|
|
;;
|
|
opensuse-tumbleweed|opensuse-leap|opensuse|suse)
|
|
printf '%s\n' "suse"
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
case "$distribution_like" in
|
|
*" debian "*|*" ubuntu "*)
|
|
printf '%s\n' "debian"
|
|
;;
|
|
*" fedora "*)
|
|
printf '%s\n' "fedora"
|
|
;;
|
|
*" arch "*)
|
|
printf '%s\n' "arch"
|
|
;;
|
|
*" suse "*)
|
|
printf '%s\n' "suse"
|
|
;;
|
|
*)
|
|
printf '%s\n' "unknown"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
ptlinux_resolve_dependency_packages()
|
|
{
|
|
local family="$1"
|
|
shift
|
|
local dependency
|
|
local package
|
|
local -a candidates=()
|
|
local -a packages=()
|
|
|
|
for dependency in "$@"
|
|
do
|
|
candidates=()
|
|
case "${family}:${dependency}" in
|
|
debian:python)
|
|
candidates=(python3)
|
|
;;
|
|
debian:python-tk)
|
|
candidates=(python3-tk)
|
|
;;
|
|
debian:wine)
|
|
candidates=(wine wine64 wine32:i386)
|
|
;;
|
|
debian:winetricks)
|
|
candidates=(winetricks)
|
|
;;
|
|
debian:gamescope)
|
|
candidates=(gamescope)
|
|
;;
|
|
debian:cabextract)
|
|
candidates=(cabextract)
|
|
;;
|
|
debian:openssl)
|
|
candidates=(openssl)
|
|
;;
|
|
debian:bubblewrap)
|
|
candidates=(bubblewrap)
|
|
;;
|
|
debian:apparmor)
|
|
candidates=(apparmor)
|
|
;;
|
|
fedora:python)
|
|
candidates=(python3)
|
|
;;
|
|
fedora:python-tk)
|
|
candidates=(python3-tkinter)
|
|
;;
|
|
fedora:wine)
|
|
candidates=(wine)
|
|
;;
|
|
fedora:winetricks)
|
|
candidates=(winetricks)
|
|
;;
|
|
fedora:gamescope)
|
|
candidates=(gamescope)
|
|
;;
|
|
fedora:cabextract)
|
|
candidates=(cabextract)
|
|
;;
|
|
fedora:openssl)
|
|
candidates=(openssl)
|
|
;;
|
|
fedora:bubblewrap)
|
|
candidates=(bubblewrap)
|
|
;;
|
|
arch:python)
|
|
candidates=(python)
|
|
;;
|
|
arch:python-tk)
|
|
candidates=(tk)
|
|
;;
|
|
arch:wine)
|
|
candidates=(wine)
|
|
;;
|
|
arch:winetricks)
|
|
candidates=(winetricks)
|
|
;;
|
|
arch:gamescope)
|
|
candidates=(gamescope)
|
|
;;
|
|
arch:cabextract)
|
|
candidates=(cabextract)
|
|
;;
|
|
arch:openssl)
|
|
candidates=(openssl)
|
|
;;
|
|
arch:bubblewrap)
|
|
candidates=(bubblewrap)
|
|
;;
|
|
arch:umu)
|
|
candidates=(umu-launcher)
|
|
;;
|
|
suse:python)
|
|
candidates=(python3)
|
|
;;
|
|
suse:python-tk)
|
|
candidates=(python3-tk)
|
|
;;
|
|
suse:wine)
|
|
candidates=(wine)
|
|
;;
|
|
suse:winetricks)
|
|
candidates=(winetricks)
|
|
;;
|
|
suse:gamescope)
|
|
candidates=(gamescope)
|
|
;;
|
|
suse:cabextract)
|
|
candidates=(cabextract)
|
|
;;
|
|
suse:openssl)
|
|
candidates=(openssl)
|
|
;;
|
|
suse:bubblewrap)
|
|
candidates=(bubblewrap)
|
|
;;
|
|
*)
|
|
printf 'No package mapping for %s on %s systems.\n' "$dependency" "$family" >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
for package in "${candidates[@]}"
|
|
do
|
|
if [[ " ${packages[*]} " != *" ${package} "* ]]
|
|
then
|
|
packages+=("$package")
|
|
fi
|
|
done
|
|
done
|
|
|
|
printf '%s\n' "${packages[*]}"
|
|
}
|
|
|
|
ptlinux_read_distribution()
|
|
{
|
|
local release_file="${PTLINUX_OS_RELEASE:-/etc/os-release}"
|
|
local key
|
|
local value
|
|
PTLINUX_DISTRIBUTION_ID="unknown"
|
|
PTLINUX_DISTRIBUTION_LIKE=""
|
|
|
|
if [[ ! -r "$release_file" ]]
|
|
then
|
|
return 0
|
|
fi
|
|
|
|
while IFS='=' read -r key value
|
|
do
|
|
value="${value%\"}"
|
|
value="${value#\"}"
|
|
value="${value%\'}"
|
|
value="${value#\'}"
|
|
case "$key" in
|
|
ID)
|
|
PTLINUX_DISTRIBUTION_ID="$value"
|
|
;;
|
|
ID_LIKE)
|
|
PTLINUX_DISTRIBUTION_LIKE="$value"
|
|
;;
|
|
esac
|
|
done < "$release_file"
|
|
}
|
|
|
|
ptlinux_has_wine_stack()
|
|
{
|
|
local wine_root
|
|
if command -v wine >/dev/null 2>&1 && command -v wineboot >/dev/null 2>&1
|
|
then
|
|
return 0
|
|
fi
|
|
for wine_root in /opt/wine-stable/bin /opt/wine-staging/bin /opt/wine-devel/bin
|
|
do
|
|
if [[ -x "${wine_root}/wine" && -x "${wine_root}/wineboot" ]]
|
|
then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
ptlinux_detect_missing_dependencies()
|
|
{
|
|
local python_bin="${PYTHON_BIN:-python3}"
|
|
local family="${1:-}"
|
|
PTLINUX_MISSING_DEPENDENCIES=()
|
|
|
|
if ! command -v "$python_bin" >/dev/null 2>&1
|
|
then
|
|
PTLINUX_MISSING_DEPENDENCIES+=(python python-tk)
|
|
elif ! "$python_bin" -c 'import _tkinter' >/dev/null 2>&1
|
|
then
|
|
PTLINUX_MISSING_DEPENDENCIES+=(python-tk)
|
|
fi
|
|
if ! ptlinux_has_wine_stack
|
|
then
|
|
PTLINUX_MISSING_DEPENDENCIES+=(wine)
|
|
fi
|
|
if ! command -v winetricks >/dev/null 2>&1
|
|
then
|
|
PTLINUX_MISSING_DEPENDENCIES+=(winetricks)
|
|
fi
|
|
if ! command -v cabextract >/dev/null 2>&1
|
|
then
|
|
PTLINUX_MISSING_DEPENDENCIES+=(cabextract)
|
|
fi
|
|
if ! command -v openssl >/dev/null 2>&1
|
|
then
|
|
PTLINUX_MISSING_DEPENDENCIES+=(openssl)
|
|
fi
|
|
if [[ ! -x /usr/bin/bwrap ]]
|
|
then
|
|
PTLINUX_MISSING_DEPENDENCIES+=(bubblewrap)
|
|
fi
|
|
if [[ "$family" == "debian" \
|
|
&& ( "${PTLINUX_DISTRIBUTION_ID,,}" == "ubuntu" \
|
|
|| " ${PTLINUX_DISTRIBUTION_LIKE,,} " == *" ubuntu "* ) \
|
|
&& -e /proc/sys/kernel/apparmor_restrict_unprivileged_userns \
|
|
&& ! -x /usr/sbin/apparmor_parser ]]
|
|
then
|
|
PTLINUX_MISSING_DEPENDENCIES+=(apparmor)
|
|
fi
|
|
if [[ "$family" == "arch" ]] && ! command -v umu-run >/dev/null 2>&1
|
|
then
|
|
PTLINUX_MISSING_DEPENDENCIES+=(umu)
|
|
fi
|
|
}
|
|
|
|
ptlinux_select_privilege_command()
|
|
{
|
|
PTLINUX_PRIVILEGE_COMMAND=()
|
|
if (( EUID == 0 ))
|
|
then
|
|
return 0
|
|
fi
|
|
if command -v sudo >/dev/null 2>&1
|
|
then
|
|
PTLINUX_PRIVILEGE_COMMAND=(sudo)
|
|
return 0
|
|
fi
|
|
if command -v doas >/dev/null 2>&1
|
|
then
|
|
PTLINUX_PRIVILEGE_COMMAND=(doas)
|
|
return 0
|
|
fi
|
|
if command -v pkexec >/dev/null 2>&1
|
|
then
|
|
PTLINUX_PRIVILEGE_COMMAND=(pkexec)
|
|
return 0
|
|
fi
|
|
printf '%s\n' "Root access is required, but sudo, doas, and pkexec are unavailable." >&2
|
|
return 1
|
|
}
|
|
|
|
ptlinux_print_command()
|
|
{
|
|
printf '%s' " +"
|
|
printf ' %q' "$@"
|
|
printf '\n'
|
|
}
|
|
|
|
ptlinux_run_as_root()
|
|
{
|
|
ptlinux_print_command "${PTLINUX_PRIVILEGE_COMMAND[@]}" "$@"
|
|
"${PTLINUX_PRIVILEGE_COMMAND[@]}" "$@"
|
|
}
|
|
|
|
ptlinux_install_resolved_packages()
|
|
{
|
|
local family="$1"
|
|
shift
|
|
local -a packages=("$@")
|
|
local architecture
|
|
local has_i386=0
|
|
local package
|
|
|
|
ptlinux_select_privilege_command
|
|
case "$family" in
|
|
debian)
|
|
for package in "${packages[@]}"
|
|
do
|
|
if [[ "$package" == "wine32:i386" ]]
|
|
then
|
|
has_i386=1
|
|
fi
|
|
done
|
|
if (( has_i386 == 1 ))
|
|
then
|
|
while IFS= read -r architecture
|
|
do
|
|
if [[ "$architecture" == "i386" ]]
|
|
then
|
|
has_i386=2
|
|
fi
|
|
done < <(dpkg --print-foreign-architectures)
|
|
if (( has_i386 == 1 ))
|
|
then
|
|
ptlinux_run_as_root dpkg --add-architecture i386
|
|
fi
|
|
fi
|
|
ptlinux_run_as_root apt-get update
|
|
ptlinux_run_as_root apt-get install -y "${packages[@]}"
|
|
;;
|
|
fedora)
|
|
if command -v dnf5 >/dev/null 2>&1
|
|
then
|
|
ptlinux_run_as_root dnf5 install -y "${packages[@]}"
|
|
else
|
|
ptlinux_run_as_root dnf install -y "${packages[@]}"
|
|
fi
|
|
;;
|
|
arch)
|
|
ptlinux_run_as_root pacman -S --needed --noconfirm "${packages[@]}"
|
|
;;
|
|
suse)
|
|
ptlinux_run_as_root zypper --non-interactive install "${packages[@]}"
|
|
;;
|
|
*)
|
|
printf 'Unsupported dependency family: %s\n' "$family" >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
ptlinux_install_host_dependencies()
|
|
{
|
|
local dry_run="$1"
|
|
local family
|
|
local package_line
|
|
local -a packages=()
|
|
|
|
ptlinux_read_distribution
|
|
family="$(ptlinux_dependency_family "$PTLINUX_DISTRIBUTION_ID" "$PTLINUX_DISTRIBUTION_LIKE")"
|
|
ptlinux_detect_missing_dependencies "$family"
|
|
if (( ${#PTLINUX_MISSING_DEPENDENCIES[@]} == 0 ))
|
|
then
|
|
printf '%s\n' "All required host dependencies are already available."
|
|
return 0
|
|
fi
|
|
if [[ "$family" == "unknown" ]]
|
|
then
|
|
printf 'Unsupported distribution: %s. Re-run with --skip-deps after installing: %s\n' \
|
|
"$PTLINUX_DISTRIBUTION_ID" "${PTLINUX_MISSING_DEPENDENCIES[*]}" >&2
|
|
return 1
|
|
fi
|
|
package_line="$(ptlinux_resolve_dependency_packages \
|
|
"$family" "${PTLINUX_MISSING_DEPENDENCIES[@]}")"
|
|
read -r -a packages <<< "$package_line"
|
|
|
|
printf 'Missing host components: %s\n' "${PTLINUX_MISSING_DEPENDENCIES[*]}"
|
|
printf 'Distribution packages: %s\n' "${packages[*]}"
|
|
if (( dry_run == 1 ))
|
|
then
|
|
printf '%s\n' "Dry run: system packages were not changed."
|
|
return 0
|
|
fi
|
|
|
|
printf '%s\n' "Installing missing host dependencies (authentication may be requested)."
|
|
ptlinux_install_resolved_packages "$family" "${packages[@]}"
|
|
hash -r
|
|
ptlinux_detect_missing_dependencies "$family"
|
|
if (( ${#PTLINUX_MISSING_DEPENDENCIES[@]} != 0 ))
|
|
then
|
|
printf 'Dependencies remain unavailable after package installation: %s\n' \
|
|
"${PTLINUX_MISSING_DEPENDENCIES[*]}" >&2
|
|
return 1
|
|
fi
|
|
printf '%s\n' "Host dependency installation completed."
|
|
}
|
|
|
|
ptlinux_dependency_main()
|
|
{
|
|
if [[ "${1:-}" != "--resolve" || $# -lt 3 ]]
|
|
then
|
|
printf '%s\n' "Usage: install-dependencies.sh --resolve DISTRO DEPENDENCY..." >&2
|
|
return 2
|
|
fi
|
|
shift
|
|
local distribution_id="$1"
|
|
shift
|
|
local family
|
|
family="$(ptlinux_dependency_family "$distribution_id" "")"
|
|
if [[ "$family" == "unknown" ]]
|
|
then
|
|
printf 'Unsupported distribution: %s\n' "$distribution_id" >&2
|
|
return 1
|
|
fi
|
|
ptlinux_resolve_dependency_packages "$family" "$@"
|
|
}
|
|
|
|
if [[ "${BASH_SOURCE[0]}" == "$0" ]]
|
|
then
|
|
set -euo pipefail
|
|
ptlinux_dependency_main "$@"
|
|
fi
|