From d31a4501f148b748a923556a2fb8681b7cef1c2d Mon Sep 17 00:00:00 2001 From: rope Date: Thu, 25 Jun 2026 11:42:45 +0100 Subject: [PATCH] selkies: browser game streaming for GW (pointer-lock relative mouse), retire neko Co-Authored-By: Claude Opus 4.8 --- common.nix | 3 +- services/authelia.nix | 2 +- services/nginx.nix | 2 +- services/selkies.nix | 85 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 services/selkies.nix diff --git a/common.nix b/common.nix index 1dbcbaf..b745c30 100644 --- a/common.nix +++ b/common.nix @@ -42,7 +42,8 @@ ./services/forgejo-runner.nix ./services/code-server.nix ./services/memos.nix - ./services/neko.nix + # ./services/neko.nix # superseded by selkies.nix (Neko can't handle GW's mouse grab) + ./services/selkies.nix ]; ### Make build time quicker diff --git a/services/authelia.nix b/services/authelia.nix index b1823e2..92b0e0d 100644 --- a/services/authelia.nix +++ b/services/authelia.nix @@ -41,7 +41,7 @@ { domain = "sabnzbd.nordhammer.it"; policy = "one_factor"; } { domain = "code.nordhammer.it"; policy = "one_factor"; } { domain = "notes.nordhammer.it"; policy = "one_factor"; } - { domain = "neko.nordhammer.it"; policy = "one_factor"; } + { domain = "selkies.nordhammer.it"; policy = "one_factor"; } ]; }; diff --git a/services/nginx.nix b/services/nginx.nix index 0487df1..ac98d9e 100644 --- a/services/nginx.nix +++ b/services/nginx.nix @@ -123,7 +123,7 @@ in ''; }; "notes.nordhammer.it" = protectedProxy 5230; - "neko.nordhammer.it" = protectedProxy 8092; + "selkies.nordhammer.it" = protectedProxy 8093; # --- Local-only: serves update history JSON to Homepage's customapi widget --- "homepage-updates.local" = { diff --git a/services/selkies.nix b/services/selkies.nix new file mode 100644 index 0000000..9230b44 --- /dev/null +++ b/services/selkies.nix @@ -0,0 +1,85 @@ +# services/selkies.nix — Guild Wars in a browser via Selkies +# +# Replaces the Neko attempt (services/neko.nix, now unimported): Neko's +# absolute-pointer input model can't handle Guild Wars' exclusive mouse grab. +# Selkies captures the mouse client-side with the browser Pointer Lock API and +# sends *relative* movement, so the grab is a non-issue — and it uses the GPU +# (NVENC + EGL) instead of software rendering. +# +# Reach it at selkies.nordhammer.it (Authelia-gated). The Wine prefix with +# Guild Wars already installed is reused from the old Neko home, seeded into +# /var/lib/selkies/home/.wine (see the deploy note in the repo). +{ config, pkgs, lib, ... }: +let + # Selkies' NVIDIA EGL desktop (Ubuntu 24.04) plus Wine for the 32-bit GW + # client. Built from stdin (no build context); see neko.nix for the why. + dockerfile = pkgs.writeText "selkies-gw.Dockerfile" '' + FROM ghcr.io/selkies-project/nvidia-egl-desktop:24.04 + USER root + RUN add-apt-repository -y multiverse \ + && dpkg --add-architecture i386 \ + && apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + wine wine32 wine64 winbind cabextract ca-certificates wget \ + && rm -rf /var/lib/apt/lists/* + USER 1000 + ''; +in +{ + config = lib.mkIf (config.networking.hostName == "FredOS-Mediaserver") { + + virtualisation.docker.enable = true; + # GPU into the container (CDI: nvidia.com/gpu=all) + 32-bit host GL libs so + # the toolkit can expose them to the 32-bit Wine/GW OpenGL stack. + hardware.nvidia-container-toolkit.enable = true; + hardware.graphics.enable32Bit = true; + + systemd.tmpfiles.rules = [ + "d /var/lib/selkies 0755 root root -" + # Container user is uid/gid 1000 and must own its home. + "d /var/lib/selkies/home 0755 1000 1000 -" + ]; + + systemd.services.selkies = { + description = "Selkies — Guild Wars in a browser (EGL desktop + Wine + NVENC)"; + after = [ "docker.service" "network-online.target" ]; + requires = [ "docker.service" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + + startLimitIntervalSec = 600; + startLimitBurst = 3; + + serviceConfig = { + Restart = "on-failure"; + RestartSec = "30s"; + TimeoutStartSec = "3600"; + ExecStartPre = [ + "-${pkgs.docker}/bin/docker rm -f selkies" + "${pkgs.writeShellScript "selkies-build" '' + exec ${pkgs.docker}/bin/docker build -t selkies-gw:local - < ${dockerfile} + ''}" + ]; + ExecStart = pkgs.writeShellScript "selkies-run" '' + exec ${pkgs.docker}/bin/docker run --rm --name selkies \ + --device=nvidia.com/gpu=all \ + -e NVIDIA_VISIBLE_DEVICES=all \ + -e NVIDIA_DRIVER_CAPABILITIES=all \ + --shm-size=2g \ + -p 127.0.0.1:8093:8080 \ + -e TZ=Europe/Stockholm \ + -e DISPLAY_SIZEW=1280 -e DISPLAY_SIZEH=720 \ + -e DISPLAY_REFRESH=30 -e DISPLAY_DPI=96 -e DISPLAY_CDEPTH=24 \ + -e PASSWD=selkies \ + -e SELKIES_ENCODER=nvh264enc \ + -e SELKIES_VIDEO_BITRATE=8000 \ + -e SELKIES_FRAMERATE=30 \ + -e SELKIES_ENABLE_BASIC_AUTH=false \ + -v /var/lib/selkies/home:/home/ubuntu \ + selkies-gw:local + ''; + ExecStop = "${pkgs.docker}/bin/docker stop selkies"; + }; + }; + }; +}