Setup user for headless acccess on normal desktop configuration

Hi all.

I have a NixOS desktop with an NVIDIA graphics card that I use for both work and gaming that I would like to be able to login remotely and use as gaming server when on another device (eg, my laptop).

I intend to be able to launch steam remotely and use-it to run a game locally and stream to another device (that also has steam installed).

I already have Wake on LAN and ssh setup with X forwarding enabled, however steam is unable to be launched just with remote ssh access.

steam.sh[4236]: Running Steam on nixos 25.05 64-bit
steam.sh[4236]: STEAM_RUNTIME is enabled automatically
setup.sh[4284]: Steam runtime environment up-to-date!
steam.sh[4236]: Using supervisor /home/dvalinn/.local/share/Steam/ubuntu12_32/steam-runtime/amd64/usr/bin/steam-runtime-supervisor
steam.sh[4236]: Steam client's requirements are satisfied
CProcessEnvironmentManager is ready, 6 preallocated environment variables.
[2025-02-05 17:47:34] Startup - updater built Jan 28 2025 00:50:36
[2025-02-05 17:47:34] Startup - Steam Client launched with: '/home/dvalinn/.local/share/Steam/ubuntu12_32/steam' '-srt-logger-opened'
02/05 17:47:34 minidumps folder is set to /tmp/dumps
02/05 17:47:34 Init: Installing breakpad exception handler for appid(steam)/version(1738026274)/tid(4352)
Looks like steam didn't shutdown cleanly, scheduling immediate update check
[2025-02-05 17:47:35] Loading cached metrics from disk (/home/dvalinn/.local/share/Steam/package/steam_client_metrics.bin)
[2025-02-05 17:47:35] Using the following download hosts for Public, Realm steamglobal
[2025-02-05 17:47:35] 1. https://client-update.fastly.steamstatic.com, /, Realm 'steamglobal', weight was 900, source = 'update_hosts_cached.vdf'
[2025-02-05 17:47:35] 2. https://client-update.akamai.steamstatic.com, /, Realm 'steamglobal', weight was 100, source = 'update_hosts_cached.vdf'
[2025-02-05 17:47:35] 3. https://client-update.steamstatic.com, /, Realm 'steamglobal', weight was 1, source = 'baked in'
[2025-02-05 17:47:35] Checking for update on startup
[2025-02-05 17:47:35] Checking for available updates...
[2025-02-05 17:47:35] Downloading manifest: https://client-update.fastly.steamstatic.com/steam_client_ubuntu12
[2025-02-05 17:47:35] Manifest download: send request
[2025-02-05 17:47:35] Manifest download: waiting for download to finish
[2025-02-05 17:47:35] Manifest download: finished
[2025-02-05 17:47:35] Download skipped: /steam_client_ubuntu12 version 1738026274, installed version 1738026274, existing pending version 0
[2025-02-05 17:47:35] Nothing to do
[2025-02-05 17:47:35] Verifying installation...
[2025-02-05 17:47:35] Performing checksum verification of executable files
[2025-02-05 17:47:36] Verification complete
UpdateUI: skip show logo
Steam logging initialized: directory: /home/dvalinn/.local/share/Steam/logs

OpenGL GLX extension not supported by display
OpenGL GLX extension not supported by displaysrc/steamUI/spewmanager.cpp (184) : Assertion Failed: Error: OpenGL GLX extension not supported by display
src/steamUI/spewmanager.cpp (184) : Assertion Failed: Error: OpenGL GLX extension not supported by display
02/05 17:47:36 Init: Installing breakpad exception handler for appid(steam)/version(1738026274)/tid(4352)
assert_20250205174736_7.dmp[4362]: Uploading dump (out-of-process)
/tmp/dumps/assert_20250205174736_7.dmp
assert_20250205174736_7.dmp[4362]: Finished uploading minidump (out-of-process): success = no
assert_20250205174736_7.dmp[4362]: error: Could not resolve hostname
assert_20250205174736_7.dmp[4362]: file ''/tmp/dumps/assert_20250205174736_7.dmp'', upload no: ''Could not resolve hostname''
Xlib:  extension "RANDR" missing on display "localhost:10.0".
Xlib:  extension "RANDR" missing on display "localhost:10.0".
Xlib:  extension "XInputExtension" missing on display "localhost:10.0".
Xlib:  extension "RANDR" missing on display "localhost:10.0".
Xlib:  extension "XInputExtension" missing on display "localhost:10.0".
steamwebhelper.sh[4370]: Using supervisor /home/dvalinn/.steam/root/ubuntu12_32/steam-runtime/amd64/usr/bin/steam-runtime-supervisor
steamwebhelper.sh[4370]: Starting steamwebhelper under bootstrap sniper steam runtime via /home/dvalinn/.local/share/Steam/ubuntu12_64/steam-runtime-sniper.sh
steamwebhelper.sh[4370]: Using CEF sandbox \(try with -no-cef-sandbox if this fails\)
steamwebhelper.sh[4370]: Starting steamwebhelper with Sniper steam runtime at /home/dvalinn/.local/share/Steam/ubuntu12_64/steam-runtime-sniper/_v2-entry-point
src/vgui2/src/surface_linux.cpp (1954) : glXChooseVisual failed
src/vgui2/src/surface_linux.cpp (1954) : glXChooseVisual failed
src/vgui2/src/surface_linux.cpp (1954) : Fatal assert; application exiting
src/vgui2/src/surface_linux.cpp (1954) : Fatal assert; application exiting
02/05 17:47:42 Init: Installing breakpad exception handler for appid(steam)/version(1738026274)/tid(4352)
assert_20250205174742_11.dmp[4413]: Uploading dump (out-of-process)
/tmp/dumps/assert_20250205174742_11.dmp
assert_20250205174742_11.dmp[4413]: Finished uploading minidump (out-of-process): success = no
assert_20250205174742_11.dmp[4413]: error: Could not resolve hostname
assert_20250205174742_11.dmp[4413]: file ''/tmp/dumps/assert_20250205174742_11.dmp'', upload no: ''Could not resolve hostname''

I have also tried to setup sunshine in the past, but to no success as I could not get the video stream to work, only audio and inputs (both for remote desktop and specific application streaming).

I have found some configurations for nix headless servers online and was wondering if some sort of setup can be created with a normal graphical user and a separate one for “headless” access.

What code did you write? We need to see your config to suggest further.

Here are the relevant parts of the config:

steam.nix

{
  pkgs,
  lib,
  config,
  ...
}: {
  options = {
    steam.enable = lib.mkEnableOption "enables steam and proton";
  };

  config = lib.mkIf config.steam.enable {
    programs.steam = {
      enable = true;
      gamescopeSession.enable = true;
    };

    environment.systemPackages = with pkgs; [
      protonup # used to install proton ge compatibility layer
    ];

    environment.sessionVariables = {
      STEAM_EXTRA_COMPAT_TOOLS_PATH = "\${HOME}/.steam/root/compatibilitytools.d";
    };

    # gamemode and mangohud make it so that
    # on steam we can use as lauch options
    # gamemoderun %command% or gamescope %command% or mangohud %command%
  };
}

sunshine.nix

{
  config,
  lib,
  ...
}: {
  options = {
    sunshine.enable = lib.mkEnableOption "enables sunshine programs and options";
  };

  config = lib.mkIf config.sunshine.enable {
    services.sunshine = {
      enable = true;
      autoStart = false;
      openFirewall = true;
      capSysAdmin = true;
    };
  };
}

gaming.nix

{
  pkgs,
  lib,
  config,
  ...
}: {
  imports = [
    ./steam.nix
    ./hamachi.nix
    ./sunshine.nix
  ];

  options = {
    gaming.enable = lib.mkEnableOption "enables gaming programs and options";
  };

  config = lib.mkIf config.gaming.enable {
    steam.enable = lib.mkDefault true;
    hamachi.enable = lib.mkDefault false;
    sunshine.enable = lib.mkDefault false;
    programs.gamemode.enable = lib.mkDefault true;

    environment.systemPackages = with pkgs; [
      mangohud # msi afterburner monitor thingy
      bottles # wine presets
    ];
  };
}

ssh.nix

{
  lib,
  config,
  ...
}: {
  options = {
    openSSH.enable = lib.mkEnableOption "enables openshh daemon config";
  };

  config = lib.mkIf config.openSSH.enable {
    # Enable OpenSSH Deamon
    services.openssh = {
      enable = true;
      settings = {
        X11Forwarding = true;
        PermitRootLogin = "no";
        PasswordAuthentication = true;
      };
      openFirewall = true;
    };
  };
}

Now for the system config:

common.nix

  pkgs,
  user,
  ...
}: {
  nixpkgs.config.allowUnfree = true;

  # Bootloader.
  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;

  # Switch from LTS to the latest kernel
  boot.kernelPackages = pkgs.linuxPackages_latest;

  # Enable networking
  networking.networkmanager.enable = true;
  # Open ports in the firewall.
  networking.firewall.enable = true;

  # Locale settings
  time.timeZone = "Europe/Lisbon";

  i18n.defaultLocale = "en_US.UTF-8";

  i18n.extraLocaleSettings = {
    LC_ADDRESS = "pt_PT.UTF-8";
    LC_IDENTIFICATION = "pt_PT.UTF-8";
    LC_MEASUREMENT = "pt_PT.UTF-8";
    LC_MONETARY = "pt_PT.UTF-8";
    LC_NAME = "pt_PT.UTF-8";
    LC_NUMERIC = "pt_PT.UTF-8";
    LC_PAPER = "pt_PT.UTF-8";
    LC_TELEPHONE = "pt_PT.UTF-8";
    LC_TIME = "pt_PT.UTF-8";
  };

  # Keymap configuration for console
  console.keyMap = "pt-latin1";

  autoStyling.enable = true; ## stylix module

  environment.systemPackages = with pkgs; [
    xorg.xinit
    xorg.xrandr
  ];

  services.xserver = {
    enable = true;
    xkb = {
      layout = "pt";
      variant = "";
    };
  };

  services.avahi = {
    enable = true;

    ipv4 = true;
    nssmdns4 = true;

    ipv6 = true;
    nssmdns6 = true;

    publish = {
      enable = true;
      addresses = true;
      domain = true;
      hinfo = true;
      userServices = true;
    };
  };

  virtualisation.docker = {
    enable = true;
    rootless = {
      enable = true;
      setSocketVariable = true;
    };
  };

  # make the user not have to type the sudo password for poweroff/reboot
  security.sudo.extraConfig = let
    systemctl = "/run/current-system/sw/bin/systemctl";
  in ''
    ${user.name} ALL=NOPASSWD: ${systemctl} poweroff, ${systemctl} reboot
  '';
}

nix-desktop/default.nix

# Edit this configuration file to define what should be installed on
# your system.  Help is available in the configuration.nix(5) man page;;
# and in the NixOS manual (accessible by running ‘nixos-help’).
{
  config,
  pkgs,
  user,
  ...
}: let
  gruvbox-dark-medium = "${pkgs.base16-schemes}/share/themes/gruvbox-dark-medium.yaml";
in {
  imports = [
    # Include the results of the hardware scan.
    ./hardware-configuration.nix
    ./../common.nix
  ];

  networking = {
    hostName = "nix-desktop"; # Define your hostname.
    # wireless.enable = true;  # Enables wireless support via wpa_supplicant.

    # Configure network proxy if necessary
    # proxy.default = "http://user:password@proxy:port/";
    # proxy.noProxy = "127.0.0.1,localhost,internal.domain";
    firewall.allowedTCPPorts = [];
    firewall.allowedUDPPorts = [];
  };

  services = {
    # enable nvidia drivers for x11 and wayland
    xserver.videoDrivers = ["nvidia"];

    # session commands are executed just after wm starts
    xserver.displayManager.sessionCommands = ''
      xrandr \
      --output DP-0  --mode 1920x1080 --rate 144 --pos 1920x0 --rotate normal \
      --output DP-4 --primary --mode 1920x1080 --rate 144 --pos 0x0 --rotate normal \
    '';
    # displayManager.defaultSession = "none+awesome";
    desktopManager.plasma6.enable = true;
  };

  awesomeWM.enable = false;
  autoStyling.colorScheme = gruvbox-dark-medium;

  hardware = {
    graphics = {
      # renamed from "opengl"
      # https://nixos.wiki/wiki/Nvidia
      enable = true;
      enable32Bit = true; # renamed from driSupport32Bit
    };

    nvidia = {
      modesetting.enable = true;
      powerManagement.enable = false;
      powerManagement.finegrained = false;

      open = false;
      nvidiaSettings = true;
      forceFullCompositionPipeline = true;
      package = config.boot.kernelPackages.nvidiaPackages.stable;
    };
  };

  # TODO: make this a module
  virtualisation.virtualbox = {
    host = {
      enable = false;
      enableExtensionPack = false;
    };
    guest = {
      enable = false;
      dragAndDrop = true;
    };
  };
  users.extraGroups.vboxusers.members = [user.name];

  # Module configuration options
  gaming.enable = true;
  sunshine.enable = false;
  homelabCifs.enable = true;

  # This value determines the NixOS release from which the default
  # settings for stateful data, like file locations and database versions
  # on your system were taken. It‘s perfectly fine and recommended to leave
  # this value at the release version of the first install of this system.
  # Before changing this value read the documentation for this option
  # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
  system.stateVersion = "23.11"; # Did you read the comment?
}

hardware-configuration.nix

# Do not modify this file!  It was generated by ‘nixos-generate-config’
# and may be overwritten by future invocations.  Please make changes
# to /etc/nixos/configuration.nix instead.
{
  config,
  lib,
  modulesPath,
  ...
}: {
  imports = [
    (modulesPath + "/installer/scan/not-detected.nix")
  ];

  boot.initrd.availableKernelModules = ["nvme" "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod"];
  boot.initrd.kernelModules = [];
  boot.kernelModules = ["kvm-amd"];
  boot.extraModulePackages = [];

  fileSystems."/" = {
    device = "/dev/disk/by-uuid/203bebf8-3a66-4c49-96ad-0a8a237c9ed6";
    fsType = "ext4";
  };

  fileSystems."/boot" = {
    device = "/dev/disk/by-uuid/A130-AF37";
    fsType = "vfat";
  };

  swapDevices = [];

  # Enables DHCP on each ethernet and wireless interface. In case of scripted networking
  # (the default) this is the recommended approach. When using systemd-networkd it's
  # still possible to use this option, but it's recommended to use it in conjunction
  # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
  networking.useDHCP = lib.mkDefault true;
  networking.interfaces.enp7s0.useDHCP = lib.mkDefault true;

  nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
  hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}