mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-11 00:05:36 +02:00
## Description Oops.. another massive PR 🥲 This PR contains multiple improvements and changes. Firstly, thanks gst-wayland-display's PR [here](https://github.com/games-on-whales/gst-wayland-display/pull/20). NVIDIA path is now way more efficient than before. Secondly, adding controller support was a massive hurdle, requiring me to start another project [vimputti](https://github.com/DatCaptainHorse/vimputti) - which allows simple virtual controller inputs in isolated containers. Well, it's not simple, it includes LD_PRELOAD shims and other craziness, but the library API is simple to use.. Thirdly, split runner image into 3 separate stages, base + build + runtime, should help keep things in check in future, also added GitHub Actions CI builds for v2 to v4 builds (hopefully they pass..). Fourth, replaced the runner's runtime Steam patching with better and simpler bubblewrap patch, massive thanks to `games-on-whales` to figuring it out better! Fifth, relay for once needed some changes, the new changes are still mostly WIP, but I'll deal with them next time I have energy.. I'm spent now. Needed to include these changes as relay needed a minor change to allow rumble events to flow back to client peer. Sixth.. tons of package updates, minor code improvements and the usual. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * End-to-end gamepad/controller support (attach/detach, buttons, sticks, triggers, rumble) with client/server integration and virtual controller plumbing. * Optional Prometheus metrics endpoint and WebTransport support. * Background vimputti manager process added for controller handling. * **Improvements** * Multi-variant container image builds and streamlined runtime images. * Zero-copy video pipeline and encoder improvements for lower latency. * Updated Steam compat mapping and dependency/toolchain refreshes. * **Bug Fixes** * More robust GPU detection, input/fullscreen lifecycle, startup/entrypoint, and container runtime fixes. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
279 lines
9.3 KiB
Bash
279 lines
9.3 KiB
Bash
#!/bin/bash
|
|
|
|
# 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
|
|
|
|
# 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
|
|
}
|
|
|
|
# Configuration
|
|
MAX_RETRIES=3
|
|
RETRY_COUNT=0
|
|
WAYLAND_READY_DELAY=3
|
|
ENTCMD_PREFIX=""
|
|
PRELOAD_SHIM_64=/usr/lib64/libvimputti_shim.so
|
|
PRELOAD_SHIM_32=/usr/lib32/libvimputti_shim.so
|
|
|
|
# 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 nestri-server
|
|
start_nestri_server() {
|
|
kill_if_running "${NESTRI_PID:-}" "nestri-server"
|
|
|
|
WAYLAND_SOCKET="${XDG_RUNTIME_DIR}/wayland-1"
|
|
|
|
# Make sure to remove old socket if exists (along with .lock file)
|
|
if [[ -e "$WAYLAND_SOCKET" ]]; then
|
|
log "Removing stale Wayland socket $WAYLAND_SOCKET"
|
|
rm -f "$WAYLAND_SOCKET" 2>/dev/null || {
|
|
log "Error: Failed to remove stale Wayland socket $WAYLAND_SOCKET"
|
|
exit 1
|
|
}
|
|
# Ignore error if .lock file doesn't exist
|
|
rm -f "${WAYLAND_SOCKET}.lock" 2>/dev/null || true
|
|
fi
|
|
|
|
# Also if gstreamer cache exists, remove it to avoid previous errors from persisting
|
|
local gst_cache="${NESTRI_HOME}/.cache/gstreamer-1.0/"
|
|
if [[ -d "$gst_cache" ]]; then
|
|
log "Removing gstreamer cache at $gst_cache"
|
|
rm -rf "${gst_cache}" 2>/dev/null || {
|
|
log "Warning: Failed to remove gstreamer cache at $gst_cache"
|
|
}
|
|
fi
|
|
|
|
# Start nestri-server
|
|
log "Starting nestri-server.."
|
|
# Try with realtime scheduling first (chrt -f 80), if fails, launch normally
|
|
if $ENTCMD_PREFIX chrt -f 80 true 2>/dev/null; then
|
|
$ENTCMD_PREFIX chrt -f 80 nestri-server $NESTRI_PARAMS &
|
|
NESTRI_PID=$!
|
|
log "Started nestri-server with realtime scheduling"
|
|
else
|
|
$ENTCMD_PREFIX nestri-server $NESTRI_PARAMS &
|
|
NESTRI_PID=$!
|
|
log "Started nestri-server"
|
|
fi
|
|
|
|
log "Waiting for Wayland display $WAYLAND_SOCKET.."
|
|
for ((i=1; i<=15; i++)); do
|
|
if [[ -e "$WAYLAND_SOCKET" ]]; then
|
|
log "Wayland display $WAYLAND_SOCKET ready"
|
|
sleep "${WAYLAND_READY_DELAY:-3}"
|
|
start_compositor
|
|
return
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
log "Error: Wayland display $WAYLAND_SOCKET not available"
|
|
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="dbus-launch steam -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
|
|
|
|
# If PRELOAD_SHIM_arch's are set and exist, set LD_PRELOAD for 32/64-bit apps
|
|
local do_ld_preload=false
|
|
if [[ -f "$PRELOAD_SHIM_64" ]] || [[ -f "$PRELOAD_SHIM_32" ]]; then
|
|
do_ld_preload=true
|
|
log "Using LD_PRELOAD shim(s)"
|
|
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
|
|
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
|
|
# If ld_preload is true, add env with LD_PRELOAD
|
|
if $do_ld_preload; then
|
|
compositor_cmd+=" -- env LD_PRELOAD='/usr/\$LIB/libvimputti_shim.so' bash -c $(printf %q "$NESTRI_LAUNCH_CMD")"
|
|
else
|
|
compositor_cmd+=" -- bash -c $(printf %q "$NESTRI_LAUNCH_CMD")"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Get appropriate socket based on compositor type
|
|
if $is_gamescope; then
|
|
COMPOSITOR_SOCKET="${XDG_RUNTIME_DIR}/gamescope-0"
|
|
else
|
|
COMPOSITOR_SOCKET="${XDG_RUNTIME_DIR}/wayland-0"
|
|
fi
|
|
|
|
# Clean up old socket if exists
|
|
if [[ -e "$COMPOSITOR_SOCKET" ]]; then
|
|
log "Removing stale compositor socket $COMPOSITOR_SOCKET"
|
|
rm -f "$COMPOSITOR_SOCKET" 2>/dev/null || {
|
|
log "Error: Failed to remove stale compositor socket $COMPOSITOR_SOCKET"
|
|
exit 1
|
|
}
|
|
# Ignore error if .lock file doesn't exist
|
|
rm -f "${COMPOSITOR_SOCKET}.lock" 2>/dev/null || true
|
|
fi
|
|
|
|
log "Starting compositor: $compositor_cmd"
|
|
WAYLAND_DISPLAY=wayland-1 /bin/bash -c "$compositor_cmd" &
|
|
COMPOSITOR_PID=$!
|
|
|
|
# If ld_preload is true, export LD_PRELOAD
|
|
if $do_ld_preload; then
|
|
export LD_PRELOAD='/usr/$LIB/libvimputti_shim.so'
|
|
fi
|
|
|
|
log "Waiting for compositor socket $COMPOSITOR_SOCKET.."
|
|
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"
|
|
|
|
if [[ -n "${NESTRI_LAUNCH_CMD}" ]]; then
|
|
log "Starting application: $NESTRI_LAUNCH_CMD"
|
|
WAYLAND_DISPLAY=wayland-0 /bin/bash -c "$NESTRI_LAUNCH_CMD" &
|
|
APP_PID=$!
|
|
fi
|
|
else
|
|
log "Gamescope detected, skipping wlr-randr resolution patch"
|
|
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"
|
|
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
|
|
fi
|
|
done
|
|
}
|
|
|
|
main() {
|
|
parse_resolution "${RESOLUTION:-1920x1080}" || exit 1
|
|
get_container_info || {
|
|
log "Warning: Failed to detect container information."
|
|
}
|
|
|
|
if [[ "$container_runtime" != "apptainer" ]]; then
|
|
ENTCMD_PREFIX="sudo -E -u ${NESTRI_USER}"
|
|
fi
|
|
|
|
restart_chain
|
|
main_loop
|
|
}
|
|
|
|
main
|