#!/bin/bash set -euo pipefail # Common helpers as requirement if [[ -f /etc/nestri/common.sh ]]; then source /etc/nestri/common.sh else echo "Error: Common script not found at /etc/nestri/common.sh" >&2 exit 1 fi # Configuration CACHE_DIR="${NESTRI_HOME}/.cache/nestri" NVIDIA_INSTALLER_DIR="/tmp" TIMEOUT_SECONDS=10 ENTCMD_PREFIX="" # Ensures user directory ownership chown_user_directory() { if ! $ENTCMD_PREFIX chown "${NESTRI_USER}:${NESTRI_USER}" "${NESTRI_HOME}" 2>/dev/null; then echo "Error: Failed to change ownership of ${NESTRI_HOME} to ${NESTRI_USER}:${NESTRI_USER}" >&2 return 1 fi return 0 } # Waits for a given socket to be ready wait_for_socket() { local socket_path="$1" local name="$2" log "Waiting for $name socket at $socket_path..." for ((i=1; i<=TIMEOUT_SECONDS; i++)); do if [[ -e "$socket_path" ]]; then log "$name socket is ready." return 0 fi sleep 1 done log "Error: $name socket did not appear after ${TIMEOUT_SECONDS}s." return 1 } # Prepares environment for namespace-less applications (like Steam) setup_namespaceless() { $ENTCMD_PREFIX rm -f /run/systemd/container || true $ENTCMD_PREFIX mkdir -p /run/pressure-vessel || true } # Ensures cache directory exists setup_cache() { log "Setting up NVIDIA driver cache directory at $CACHE_DIR..." mkdir -p "$CACHE_DIR" || { log "Warning: Failed to create cache directory, continuing without cache." return 1 } $ENTCMD_PREFIX chown "${NESTRI_USER}:${NESTRI_USER}" "$CACHE_DIR" 2>/dev/null || { log "Warning: Failed to set cache directory ownership, continuing..." } } # Grabs NVIDIA driver installer get_nvidia_installer() { local driver_version="$1" local arch="$2" local filename="NVIDIA-Linux-${arch}-${driver_version}.run" local cached_file="${CACHE_DIR}/${filename}" local tmp_file="${NVIDIA_INSTALLER_DIR}/${filename}" # Check cache if [[ -f "$cached_file" ]]; then log "Found cached NVIDIA installer at $cached_file." cp "$cached_file" "$tmp_file" || { log "Warning: Failed to copy cached installer, proceeding with download." rm -f "$cached_file" 2>/dev/null } fi # Download if not in tmp if [[ ! -f "$tmp_file" ]]; then log "Downloading NVIDIA driver installer ($filename)..." local urls=( "https://international.download.nvidia.com/XFree86/Linux-${arch}/${driver_version}/${filename}" "https://international.download.nvidia.com/tesla/${driver_version}/${filename}" ) local success=0 for url in "${urls[@]}"; do if wget -q --show-progress "$url" -O "$tmp_file"; then success=1 break fi log "Failed to download from $url, trying next source..." done if [[ "$success" -eq 0 ]]; then log "Error: Failed to download NVIDIA driver from all sources." return 1 fi # Cache the downloaded file cp "$tmp_file" "$cached_file" 2>/dev/null && \ $ENTCMD_PREFIX chown "${NESTRI_USER}:${NESTRI_USER}" "$cached_file" 2>/dev/null || \ log "Warning: Failed to cache NVIDIA driver, continuing..." fi chmod +x "$tmp_file" || { log "Error: Failed to make NVIDIA installer executable." return 1 } return 0 } # Installs the NVIDIA driver install_nvidia_driver() { local filename="$1" log "Installing NVIDIA driver components from $filename..." $ENTCMD_PREFIX bash ./"$filename" \ --silent \ --skip-depmod \ --skip-module-unload \ --no-kernel-module \ --install-compat32-libs \ --no-nouveau-check \ --no-nvidia-modprobe \ --no-systemd \ --no-rpms \ --no-backup \ --no-distro-scripts \ --no-libglx-indirect \ --no-install-libglvnd \ --no-check-for-alternate-installs || { log "Error: NVIDIA driver installation failed." return 1 } log "NVIDIA driver installation completed." return 0 } log_container_info() { if ! declare -p container_runtime &>/dev/null; then log "Warning: container_runtime is not defined" return fi if [[ "${container_runtime:-none}" != "none" ]]; then log "Detected container:" log "> ${container_runtime}" else log "No container runtime detected" fi } log_gpu_info() { if ! declare -p vendor_devices &>/dev/null; then log "Warning: vendor_devices array is not defined" return fi log "Detected GPUs:" for vendor in "${!vendor_devices[@]}"; do log "> $vendor: ${vendor_devices[$vendor]}" done } configure_ssh() { # Return early if SSH not enabled if [ -z "${SSH_ENABLE_PORT+x}" ] || [ "${SSH_ENABLE_PORT:-0}" -eq 0 ]; then return 0 fi # Check if we have required key if [ -z "${SSH_ALLOWED_KEY+x}" ] || [ -z "${SSH_ALLOWED_KEY}" ]; then return 0 fi log "Configuring SSH server on port ${SSH_ENABLE_PORT} with public key authentication" # Ensure SSH host keys exist $ENTCMD_PREFIX ssh-keygen -A 2>/dev/null || { log "Error: Failed to generate SSH host keys" return 1 } # Create .ssh directory and authorized_keys file for nestri user mkdir -p "${NESTRI_HOME}/.ssh" echo "${SSH_ALLOWED_KEY}" > "${NESTRI_HOME}/.ssh/authorized_keys" chmod 700 "${NESTRI_HOME}/.ssh" chmod 600 "${NESTRI_HOME}/.ssh/authorized_keys" chown -R "${NESTRI_USER}:${NESTRI_USER}" "${NESTRI_HOME}/.ssh" # Configure secure SSH settings { echo "PasswordAuthentication no" echo "PermitRootLogin no" echo "ChallengeResponseAuthentication no" echo "UsePAM no" echo "PubkeyAuthentication yes" } | while read -r line; do if ! grep -qF "$line" /etc/ssh/sshd_config; then printf '%s\n' "$line" | $ENTCMD_PREFIX tee -a /etc/ssh/sshd_config >/dev/null fi done # Start SSH server log "Starting SSH server on port ${SSH_ENABLE_PORT}" $ENTCMD_PREFIX /usr/sbin/sshd -D -p "${SSH_ENABLE_PORT}" & SSH_PID=$! # Verify the process started if ! ps -p $SSH_PID > /dev/null 2>&1; then log "Error: SSH server failed to start" return 1 fi log "SSH server started with PID ${SSH_PID}" return 0 } main() { # Wait for required sockets wait_for_socket "${NESTRI_XDG_RUNTIME_DIR}/dbus-1" "DBus" || exit 1 wait_for_socket "${NESTRI_XDG_RUNTIME_DIR}/pipewire-0" "PipeWire" || exit 1 # Start by getting the container we are running under get_container_info || { log "Warning: Failed to detect container information." } log_container_info if [[ "$container_runtime" != "apptainer" ]]; then ENTCMD_PREFIX="sudo -E" fi # Configure SSH if [ -n "${SSH_ENABLE_PORT+x}" ] && [ "${SSH_ENABLE_PORT:-0}" -ne 0 ] && \ [ -n "${SSH_ALLOWED_KEY+x}" ] && [ -n "${SSH_ALLOWED_KEY}" ]; then if ! configure_ssh; then log "Error: SSH configuration failed with given variables - exiting" exit 1 fi else log "SSH not configured (missing SSH_ENABLE_PORT or SSH_ALLOWED_KEY)" fi # Get and detect GPU(s) get_gpu_info || { log "Error: Failed to detect GPU information." exit 1 } log_gpu_info # Handle NVIDIA GPU if [[ -n "${vendor_devices[nvidia]:-}" ]]; then log "NVIDIA GPU(s) detected, applying driver fix..." # Determine NVIDIA driver version local nvidia_driver_version="" if [[ -f "/proc/driver/nvidia/version" ]]; then nvidia_driver_version=$(awk '/NVIDIA/ {for(i=1;i<=NF;i++) if ($i ~ /^[0-9]+\.[0-9\.]+/) {print $i; exit}}' /proc/driver/nvidia/version | head -n1) elif command -v nvidia-smi >/dev/null 2>&1; then nvidia_driver_version=$(nvidia-smi --version | grep -i 'driver version' | cut -d: -f2 | tr -d ' ') fi if [[ -z "$nvidia_driver_version" ]]; then log "Error: Failed to determine NVIDIA driver version." # Check for other GPU vendors before exiting if [[ -n "${vendor_devices[amd]:-}" || -n "${vendor_devices[intel]:-}" ]]; then log "Other GPUs (AMD or Intel) detected, continuing without NVIDIA driver." else log "No other GPUs detected, exiting due to NVIDIA driver version failure." exit 1 fi else log "Detected NVIDIA driver version: $nvidia_driver_version" # Set up cache and get installer setup_cache local arch=$(uname -m) local filename="NVIDIA-Linux-${arch}-${nvidia_driver_version}.run" cd "$NVIDIA_INSTALLER_DIR" || { log "Error: Failed to change to $NVIDIA_INSTALLER_DIR." exit 1 } get_nvidia_installer "$nvidia_driver_version" "$arch" || { # Check for other GPU vendors before exiting if [[ -n "${vendor_devices[amd]:-}" || -n "${vendor_devices[intel]:-}" ]]; then log "Other GPUs (AMD or Intel) detected, continuing without NVIDIA driver." else log "No other GPUs detected, exiting due to NVIDIA installer failure." exit 1 fi } # Install driver install_nvidia_driver "$filename" || { # Check for other GPU vendors before exiting if [[ -n "${vendor_devices[amd]:-}" || -n "${vendor_devices[intel]:-}" ]]; then log "Other GPUs (AMD or Intel) detected, continuing without NVIDIA driver." else log "No other GPUs detected, exiting due to NVIDIA driver installation failure." exit 1 fi } fi fi # Make sure gamescope has CAP_SYS_NICE capabilities if available log "Checking for CAP_SYS_NICE availability..." if capsh --print | grep -q "Current:.*cap_sys_nice"; then log "Giving gamescope compositor CAP_SYS_NICE permissions..." setcap 'CAP_SYS_NICE+eip' /usr/bin/gamescope 2>/dev/null || { log "Warning: Failed to set CAP_SYS_NICE on gamescope, continuing without it..." } else log "Skipping CAP_SYS_NICE for gamescope, capability not available..." fi # Handle user directory permissions log "Ensuring user directory permissions..." chown_user_directory || exit 1 # Setup namespaceless env if needed for container runtime if [[ "$container_runtime" != "podman" ]]; then log "Applying namespace-less configuration" setup_namespaceless fi # Switch to nestri runner entrypoint log "Switching to application startup entrypoint..." if [[ ! -f /etc/nestri/entrypoint_nestri.sh ]]; then log "Error: Application entrypoint script /etc/nestri/entrypoint_nestri.sh not found" exit 1 fi if [[ "$container_runtime" == "apptainer" ]]; then exec /etc/nestri/entrypoint_nestri.sh else exec sudo -E -u "${NESTRI_USER}" /etc/nestri/entrypoint_nestri.sh fi } # Trap signals for clean exit trap 'log "Received termination signal, exiting..."; exit 1' SIGINT SIGTERM main