From 448e44753f58af1f0c828dd0300a0d006990e50d Mon Sep 17 00:00:00 2001 From: rope Date: Thu, 25 Jun 2026 10:07:31 +0100 Subject: [PATCH] neko: Guild Wars in a browser (Xfce+Wine+NVIDIA), Authelia-gated Co-Authored-By: Claude Opus 4.8 --- common.nix | 1 + ports.toml | 5 +++ services/neko.nix | 85 ++++++++++++++++++++++++++++++++++++++++++++++ services/nginx.nix | 1 + 4 files changed, 92 insertions(+) create mode 100644 services/neko.nix diff --git a/common.nix b/common.nix index d07acc2..1dbcbaf 100644 --- a/common.nix +++ b/common.nix @@ -42,6 +42,7 @@ ./services/forgejo-runner.nix ./services/code-server.nix ./services/memos.nix + ./services/neko.nix ]; ### Make build time quicker diff --git a/ports.toml b/ports.toml index ac7f48d..3cf5547 100644 --- a/ports.toml +++ b/ports.toml @@ -45,5 +45,10 @@ name = "7DTD-coop voice/dynamic" ports = "26911-26912" protocol = "udp" +[[forward]] +name = "Neko WebRTC" +port = 59000 +protocol = "udp" + # DR (Dungeon Runners) forwards removed — services/dr-server.nix is disabled. # Re-add 2110 tcp, 2603 both, 2604-2605 udp, 2606 tcp if it comes back. diff --git a/services/neko.nix b/services/neko.nix new file mode 100644 index 0000000..1a455f1 --- /dev/null +++ b/services/neko.nix @@ -0,0 +1,85 @@ +# 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"; + }; + }; + }; +} diff --git a/services/nginx.nix b/services/nginx.nix index c340533..0487df1 100644 --- a/services/nginx.nix +++ b/services/nginx.nix @@ -123,6 +123,7 @@ in ''; }; "notes.nordhammer.it" = protectedProxy 5230; + "neko.nordhammer.it" = protectedProxy 8092; # --- Local-only: serves update history JSON to Homepage's customapi widget --- "homepage-updates.local" = {