# 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/* # `gw` launcher: GW is 32-bit, so it needs the 32-bit NVIDIA GL libs (mounted # at /usr/lib/i386-linux-gnu/nvidia by the service) on the loader path, and # VirtualGL (EGL backend) to render on the M2000. Without this GW falls back # to llvmpipe (software) and pegs ~18 CPU cores at <20 fps. RUN printf '#!/bin/bash\nexport LD_LIBRARY_PATH=/usr/lib/i386-linux-gnu/nvidia\nexport VGL_DISPLAY=egl VGL_FPS=60\ncd "$HOME/.wine/drive_c/Program Files (x86)/Guild Wars/"\nexec vglrun wine Gw.exe "$@"\n' > /usr/local/bin/gw \ && chmod +x /usr/local/bin/gw USER 1000 ''; in { config = lib.mkIf (config.networking.hostName == "FredOS-Mediaserver") { virtualisation.docker.enable = true; # GPU into the container via CDI (nvidia.com/gpu=all). The CDI spec only # carries the 64-bit driver libs, so the 32-bit set (for 32-bit Wine/GW) is # bind-mounted separately below; enable32Bit makes them exist on the host. 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 \ -p 3478:3478 -p 3478:3478/udp \ -p 65532-65535:65532-65535/udp \ -e TZ=Europe/Stockholm \ -e DISPLAY_SIZEW=1280 -e DISPLAY_SIZEH=720 \ -e DISPLAY_REFRESH=60 -e DISPLAY_DPI=96 -e DISPLAY_CDEPTH=24 \ -e PASSWD=selkies \ -e SELKIES_ENCODER=nvh264enc \ -e SELKIES_VIDEO_BITRATE=8000 \ -e SELKIES_FRAMERATE=60 \ -e SELKIES_ENABLE_BASIC_AUTH=false \ -e SELKIES_TURN_HOST=10.0.0.1 \ -e SELKIES_TURN_PROTOCOL=udp \ -e SELKIES_TURN_PORT=3478 \ -e TURN_MIN_PORT=65532 -e TURN_MAX_PORT=65535 \ -v ${config.hardware.nvidia.package.lib32}/lib:/usr/lib/i386-linux-gnu/nvidia:ro \ -v /var/lib/selkies/home:/home/ubuntu \ selkies-gw:local ''; ExecStop = "${pkgs.docker}/bin/docker stop selkies"; }; }; }; }