mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-11 00:05:36 +02:00
## Description Whew.. - Steam can now run without namespaces using live-patcher (because Docker..) - Improved NVIDIA GPU selection and handling - Pipeline tests for GPU picking logic - Optimizations and cleanup all around - SSH (by default disabled) for easier instance debugging. - CachyOS' Proton because that works without namespaces (couldn't figure out how to enable automatically in Steam yet..) - Package updates and partial removal of futures (libp2p is going to switch to Tokio in next release hopefully) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - SSH server can now be enabled within the container for remote access when configured. - Added persistent live patching for Steam runtime entrypoints to improve compatibility with namespace-less applications. - Enhanced GPU selection with multi-GPU support and PCI bus ID matching for improved hardware compatibility. - Improved encoder selection by runtime testing of video encoders for better reliability. - Added WebSocket transport support in peer-to-peer networking. - Added flexible compositor and application launching with configurable commands and improved socket handling. - **Bug Fixes** - Addressed NVIDIA-specific GStreamer issues by setting new environment variables. - Improved error handling and logging for GPU and encoder selection. - Fixed process monitoring to handle patcher restarts and added cleanup logic. - Added GStreamer cache clearing workaround for Wayland socket failures. - **Improvements** - Real-time logging of container processes to standard output and error for easier monitoring. - Enhanced process management and reduced CPU usage in protocol handling loops. - Updated dependency versions for greater stability and feature support. - Improved audio capture defaults and expanded audio pipeline support. - Enhanced video pipeline setup with conditional handling for different encoder APIs and DMA-BUF support. - Refined concurrency and lifecycle management in protocol messaging for increased robustness. - Consistent namespace usage and updated crate references across the codebase. - Enhanced SSH configuration with key management, port customization, and startup verification. - Improved GPU and video encoder integration in pipeline construction. - Simplified error handling and consolidated write operations in protocol streams. - Removed Ludusavi installation from container image and updated package installations. - **Other** - Minor formatting and style changes for better code readability and maintainability. - Docker build context now ignores `.idea` directory to streamline builds. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
291 lines
9.7 KiB
Bash
291 lines
9.7 KiB
Bash
#!/bin/bash
|
|
|
|
log() {
|
|
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1"
|
|
}
|
|
|
|
# Parses resolution string
|
|
parse_resolution() {
|
|
local resolution="$1"
|
|
if [[ -z "$resolution" ]]; then
|
|
log "Error: No resolution provided"
|
|
return 1
|
|
fi
|
|
|
|
IFS='x' read -r width height <<< "$resolution"
|
|
if ! [[ "$width" =~ ^[0-9]+$ ]] || ! [[ "$height" =~ ^[0-9]+$ ]]; then
|
|
log "Error: Invalid resolution format. Expected: WIDTHxHEIGHT (e.g., 1920x1080), got: $resolution"
|
|
return 1
|
|
fi
|
|
|
|
export WIDTH="$width"
|
|
export HEIGHT="$height"
|
|
return 0
|
|
}
|
|
|
|
# Loads environment variables
|
|
load_envs() {
|
|
if [[ -f /etc/nestri/envs.sh ]]; then
|
|
log "Sourcing environment variables from envs.sh..."
|
|
source /etc/nestri/envs.sh
|
|
else
|
|
log "Error: envs.sh not found at /etc/nestri/envs.sh"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Configuration
|
|
MAX_RETRIES=3
|
|
RETRY_COUNT=0
|
|
|
|
# Kills process if running
|
|
kill_if_running() {
|
|
local pid="$1"
|
|
local name="$2"
|
|
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
|
log "Killing existing $name process (PID: $pid)..."
|
|
kill "$pid"
|
|
wait "$pid" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
# Starts up Steam namespace-less live-patcher
|
|
start_steam_namespaceless_patcher() {
|
|
kill_if_running "${PATCHER_PID:-}" "steam-patcher"
|
|
|
|
local entrypoints=(
|
|
"${HOME}/.local/share/Steam/steamrt64/steam-runtime-steamrt/_v2-entry-point"
|
|
"${HOME}/.local/share/Steam/steamapps/common/SteamLinuxRuntime_soldier/_v2-entry-point"
|
|
"${HOME}/.local/share/Steam/steamapps/common/SteamLinuxRuntime_sniper/_v2-entry-point"
|
|
# < Add more entrypoints here if needed >
|
|
)
|
|
local custom_entrypoint="/etc/nestri/_v2-entry-point"
|
|
local temp_entrypoint="/tmp/_v2-entry-point.padded"
|
|
|
|
if [[ ! -f "$custom_entrypoint" ]]; then
|
|
log "Error: Custom _v2-entry-point not found at $custom_entrypoint"
|
|
exit 1
|
|
fi
|
|
|
|
log "Starting Steam _v2-entry-point patcher..."
|
|
(
|
|
while true; do
|
|
for i in "${!entrypoints[@]}"; do
|
|
local steam_entrypoint="${entrypoints[$i]}"
|
|
|
|
if [[ -f "$steam_entrypoint" ]]; then
|
|
# Get original file size
|
|
local original_size
|
|
original_size=$(stat -c %s "$steam_entrypoint" 2>/dev/null)
|
|
if [[ -z "$original_size" ]] || [[ "$original_size" -eq 0 ]]; then
|
|
log "Warning: Could not determine size of $steam_entrypoint, retrying..."
|
|
continue
|
|
fi
|
|
|
|
# Copy custom entrypoint to temp location
|
|
cp "$custom_entrypoint" "$temp_entrypoint" 2>/dev/null || {
|
|
log "Warning: Failed to copy custom entrypoint to $temp_entrypoint"
|
|
continue
|
|
}
|
|
|
|
# Pad the temporary file to match original size
|
|
if (( $(stat -c %s "$temp_entrypoint") < original_size )); then
|
|
truncate -s "$original_size" "$temp_entrypoint" 2>/dev/null || {
|
|
log "Warning: Failed to pad $temp_entrypoint to $original_size bytes"
|
|
continue
|
|
}
|
|
fi
|
|
|
|
# Copy padded file to Steam's entrypoint, if contents differ
|
|
if ! cmp -s "$temp_entrypoint" "$steam_entrypoint"; then
|
|
cp "$temp_entrypoint" "$steam_entrypoint" 2>/dev/null || {
|
|
log "Warning: Failed to patch $steam_entrypoint"
|
|
}
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Sleep for 1s
|
|
sleep 1
|
|
done
|
|
) &
|
|
PATCHER_PID=$!
|
|
log "Steam _v2-entry-point patcher started (PID: $PATCHER_PID)"
|
|
}
|
|
|
|
# Starts nestri-server
|
|
start_nestri_server() {
|
|
kill_if_running "${NESTRI_PID:-}" "nestri-server"
|
|
|
|
log "Starting nestri-server..."
|
|
nestri-server $NESTRI_PARAMS &
|
|
NESTRI_PID=$!
|
|
|
|
log "Waiting for Wayland display 'wayland-1'..."
|
|
WAYLAND_SOCKET="${XDG_RUNTIME_DIR}/wayland-1"
|
|
for ((i=1; i<=15; i++)); do
|
|
if [[ -e "$WAYLAND_SOCKET" ]]; then
|
|
log "Wayland display 'wayland-1' ready."
|
|
sleep 3
|
|
start_compositor
|
|
return
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
log "Error: Wayland display 'wayland-1' not available."
|
|
|
|
# Workaround for gstreamer being bit slow at times
|
|
log "Clearing gstreamer cache.."
|
|
rm -rf "${HOME}/.cache/gstreamer-1.0" 2>/dev/null || true
|
|
|
|
increment_retry "nestri-server"
|
|
restart_chain
|
|
}
|
|
|
|
# Starts compositor with optional application
|
|
start_compositor() {
|
|
kill_if_running "${COMPOSITOR_PID:-}" "compositor"
|
|
kill_if_running "${APP_PID:-}" "application"
|
|
|
|
# Set default values only if variables are unset (not empty)
|
|
if [[ -z "${NESTRI_LAUNCH_CMD+x}" ]]; then
|
|
NESTRI_LAUNCH_CMD="steam-native -tenfoot -cef-force-gpu"
|
|
fi
|
|
if [[ -z "${NESTRI_LAUNCH_COMPOSITOR+x}" ]]; then
|
|
NESTRI_LAUNCH_COMPOSITOR="gamescope --backend wayland --force-grab-cursor -g -f --rt --mangoapp -W ${WIDTH} -H ${HEIGHT} -r ${FRAMERATE:-60}"
|
|
fi
|
|
|
|
# Start Steam patcher only if Steam command is present
|
|
if [[ -n "${NESTRI_LAUNCH_CMD}" ]] && [[ "$NESTRI_LAUNCH_CMD" == *"steam"* ]]; then
|
|
start_steam_namespaceless_patcher
|
|
fi
|
|
|
|
# Launch compositor if configured
|
|
if [[ -n "${NESTRI_LAUNCH_COMPOSITOR}" ]]; then
|
|
local compositor_cmd="$NESTRI_LAUNCH_COMPOSITOR"
|
|
local is_gamescope=false
|
|
|
|
# Check if this is a gamescope command
|
|
if [[ "$compositor_cmd" == *"gamescope"* ]]; then
|
|
is_gamescope=true
|
|
# Append application command for gamescope if needed
|
|
if [[ -n "$NESTRI_LAUNCH_CMD" ]] && [[ "$compositor_cmd" != *" -- "* ]]; then
|
|
# If steam in launch command, enable gamescope integration via -e
|
|
if [[ "$NESTRI_LAUNCH_CMD" == *"steam"* ]]; then
|
|
compositor_cmd+=" -e"
|
|
fi
|
|
compositor_cmd+=" -- $NESTRI_LAUNCH_CMD"
|
|
fi
|
|
fi
|
|
|
|
log "Starting compositor: $compositor_cmd"
|
|
WAYLAND_DISPLAY=wayland-1 /bin/bash -c "$compositor_cmd" &
|
|
COMPOSITOR_PID=$!
|
|
|
|
# Wait for appropriate socket based on compositor type
|
|
if $is_gamescope; then
|
|
COMPOSITOR_SOCKET="${XDG_RUNTIME_DIR}/gamescope-0"
|
|
log "Waiting for gamescope socket..."
|
|
else
|
|
COMPOSITOR_SOCKET="${XDG_RUNTIME_DIR}/wayland-0"
|
|
log "Waiting for wayland-0 socket..."
|
|
fi
|
|
|
|
for ((i=1; i<=15; i++)); do
|
|
if [[ -e "$COMPOSITOR_SOCKET" ]]; then
|
|
log "Compositor socket ready ($COMPOSITOR_SOCKET)."
|
|
# Patch resolution with wlr-randr for non-gamescope compositors
|
|
if ! $is_gamescope; then
|
|
local OUTPUT_NAME
|
|
OUTPUT_NAME=$(WAYLAND_DISPLAY=wayland-0 wlr-randr --json | jq -r '.[] | select(.enabled == true) | .name' | head -n 1)
|
|
if [ -z "$OUTPUT_NAME" ]; then
|
|
log "Warning: No enabled outputs detected. Skipping wlr-randr resolution patch."
|
|
return
|
|
fi
|
|
WAYLAND_DISPLAY=wayland-0 wlr-randr --output "$OUTPUT_NAME" --custom-mode "$WIDTH"x"$HEIGHT"
|
|
log "Patched resolution with wlr-randr."
|
|
fi
|
|
return
|
|
fi
|
|
sleep 1
|
|
done
|
|
log "Warning: Compositor socket not found after 15 seconds ($COMPOSITOR_SOCKET)."
|
|
else
|
|
# Launch standalone application if no compositor
|
|
if [[ -n "${NESTRI_LAUNCH_CMD}" ]]; then
|
|
log "Starting application: $NESTRI_LAUNCH_CMD"
|
|
WAYLAND_DISPLAY=wayland-1 /bin/bash -c "$NESTRI_LAUNCH_CMD" &
|
|
APP_PID=$!
|
|
else
|
|
log "No compositor or application configured."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Increments retry counter
|
|
increment_retry() {
|
|
local component="$1"
|
|
((RETRY_COUNT++))
|
|
if [[ "$RETRY_COUNT" -ge "$MAX_RETRIES" ]]; then
|
|
log "Error: Max retries reached for $component."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Restarts the chain
|
|
restart_chain() {
|
|
log "Restarting nestri-server and compositor..."
|
|
start_nestri_server
|
|
}
|
|
|
|
# Cleans up processes
|
|
cleanup() {
|
|
local exit_code=$?
|
|
log "Terminating processes..."
|
|
kill_if_running "${NESTRI_PID:-}" "nestri-server"
|
|
kill_if_running "${COMPOSITOR_PID:-}" "compositor"
|
|
kill_if_running "${APP_PID:-}" "application"
|
|
kill_if_running "${PATCHER_PID:-}" "steam-patcher"
|
|
rm -f "/tmp/_v2-entry-point.padded" 2>/dev/null
|
|
exit $exit_code
|
|
}
|
|
|
|
# Monitor processes for unexpected exits
|
|
main_loop() {
|
|
trap cleanup SIGINT SIGTERM EXIT
|
|
|
|
while true; do
|
|
sleep 1
|
|
# Check nestri-server
|
|
if [[ -n "${NESTRI_PID:-}" ]] && ! kill -0 "${NESTRI_PID}" 2>/dev/null; then
|
|
log "nestri-server died."
|
|
increment_retry "nestri-server"
|
|
restart_chain
|
|
# Check compositor
|
|
elif [[ -n "${COMPOSITOR_PID:-}" ]] && ! kill -0 "${COMPOSITOR_PID}" 2>/dev/null; then
|
|
log "compositor died."
|
|
increment_retry "compositor"
|
|
start_compositor
|
|
# Check application
|
|
elif [[ -n "${APP_PID:-}" ]] && ! kill -0 "${APP_PID}" 2>/dev/null; then
|
|
log "application died."
|
|
increment_retry "application"
|
|
start_compositor
|
|
# Check patcher
|
|
elif [[ -n "${PATCHER_PID:-}" ]] && ! kill -0 "${PATCHER_PID}" 2>/dev/null; then
|
|
log "steam-patcher died."
|
|
increment_retry "steam-patcher"
|
|
start_steam_namespaceless_patcher
|
|
fi
|
|
done
|
|
}
|
|
|
|
main() {
|
|
load_envs
|
|
parse_resolution "${RESOLUTION:-1920x1080}" || exit 1
|
|
restart_chain
|
|
main_loop
|
|
}
|
|
|
|
main |