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