# services/neko.nix — Guild Wars (2005) in a browser via Neko # # Streams an Xfce desktop running the Windows Guild Wars client (under Wine) # to a browser tab over WebRTC. Reach it at neko.nordhammer.it (Authelia-gated). # # Neko's stock images don't ship Wine, and apt installs land in /usr — which is # wiped whenever the container is recreated. So we bake Wine into a locally-built # image (FROM the upstream nvidia-xfce base) instead of relying on a volume. # Guild Wars' own data installs into the persistent /home/neko volume on first run. # # GPU: uses the host's NVIDIA 535 driver via the container toolkit (CDI). The # Quadro M2000 does the GL rendering and NVENC video encode. If you ever see a # black screen or no video, the NVIDIA capabilities exposed to the container # (graphics + video) are the first thing to check — verify with: # docker run --rm --device=nvidia.com/gpu=all neko-gw:local nvidia-smi { config, pkgs, lib, ... }: { config = lib.mkIf (config.networking.hostName == "FredOS-Mediaserver") { virtualisation.docker.enable = true; # Expose the host NVIDIA driver to containers via CDI (nvidia.com/gpu=all). hardware.nvidia-container-toolkit.enable = true; systemd.tmpfiles.rules = [ "d /var/lib/neko 0755 root root -" "d /var/lib/neko/home 0755 root root -" ]; # Dockerfile for the Wine-enabled image, built on first start (see below). environment.etc."neko-gw/Dockerfile".text = '' FROM ghcr.io/m1k1o/neko/nvidia-xfce:latest USER root RUN dpkg --add-architecture i386 \ && apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ wine wine64 wine32 winbind cabextract winetricks ca-certificates wget \ && rm -rf /var/lib/apt/lists/* ''; systemd.services.neko = { description = "Neko — Guild Wars in a browser (Xfce + Wine + NVIDIA)"; after = [ "docker.service" "network-online.target" ]; requires = [ "docker.service" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; # First start pulls a multi-GB base image and runs apt — give it room. # If it fails, back off but don't crash-loop (see 7dtd veth-flood note). startLimitIntervalSec = 600; startLimitBurst = 3; serviceConfig = { Restart = "on-failure"; RestartSec = "30s"; TimeoutStartSec = "3600"; ExecStartPre = [ "-${pkgs.docker}/bin/docker rm -f neko" "${pkgs.docker}/bin/docker build -t neko-gw:local /etc/neko-gw" ]; # Wrapped in a shell script so the ICE-server JSON survives quoting # (systemd's own ExecStart parser would strip the inner double quotes). ExecStart = pkgs.writeShellScript "neko-run" '' exec ${pkgs.docker}/bin/docker run --rm --name neko \ --device=nvidia.com/gpu=all \ -e NVIDIA_VISIBLE_DEVICES=all \ -e NVIDIA_DRIVER_CAPABILITIES=all \ --shm-size=1g \ -p 127.0.0.1:8092:8080 \ -p 59000:59000/udp \ -e NEKO_DESKTOP_SCREEN=1280x720@30 \ -e NEKO_MEMBER_PROVIDER=multiuser \ -e NEKO_MEMBER_MULTIUSER_USER_PASSWORD=neko \ -e NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD=neko-admin \ -e NEKO_WEBRTC_UDPMUX=59000 \ -e NEKO_WEBRTC_NAT1TO1=10.0.0.1 \ -e 'NEKO_WEBRTC_ICESERVERS_FRONTEND=[{"urls":["stun:stun.l.google.com:19302"]}]' \ -e 'NEKO_WEBRTC_ICESERVERS_BACKEND=[{"urls":["stun:stun.l.google.com:19302"]}]' \ -v /var/lib/neko/home:/home/neko \ neko-gw:local ''; ExecStop = "${pkgs.docker}/bin/docker stop neko"; }; }; }; }