Using proton-ge-bin package outside of Steam

Hi,

I want to use the Proton from nixpkg’s proton-ge-bin package outside of Steam, i.e. with the Heroic Games Launcher. As a workaround I manually linked the nix store path to the compatibiltytools.d directory like this:

$ ls -l ~/.steam/root/compatibilitytools.d/Proton-GE
lrwxrwxrwx 1 johannes users 89  2. Mai 22:00 /home/johannes/.steam/root/compatibilitytools.d/Proton-GE -> /nix/store/l14ibcy2p1ma4qvz10lkfg9afy39pn4d-proton-ge-bin-GE-Proton10-34-steamcompattool/

This works, but it is a maintenance burden. Any suggestions?

I used this simple hjem module to link proton to compatibiltytools.d, home-manager should have a similar option.

{ pkgs, ... }:
{
  files.".local/share/Steam/compatibilitytools.d/Ge-Proton".source =
    pkgs.proton-ge-bin.steamcompattool;
}
1 Like

After some back and forth with the Deepseek AI, I found the following solution as a user activation script:

  system.userActivationScripts.linkProtonGE = {
    text = ''
      TARGET="${pkgs.proton-ge-bin.steamcompattool.outPath}"
      LINK="$HOME/.steam/root/compatibilitytools.d/Proton-GE"
      
      # Create the link only, if it's not correct already.
      if [ ! -L "$LINK" ] || [ "$(readlink -e "$LINK")" != "$TARGET" ]; then
        mkdir -p "$(dirname "$LINK")"
        ln -Tfs "$TARGET" "$LINK"
      fi
    '';
  };

What’s wrong with:

{ pkgs, ... }: {
  home.files.".local/share/Steam/compatibilitytools.d/Proton-GE".src = pkgs.proton-ge-bin.steamcompattool;
}

It depends on home manager and I don’t use it. I am unsure: Does it create a copy or a symlink?

A symlink; If you want to declaratively manage files in your home directory, well, that’s exactly what home-manager is for. hjem is an alternative.

If you must reinvent the wheel, though, at least use sytemd.tmpfiles to avoid the issues with activation scripts, or, better yet, a user unit so you don’t hard-code creating stuff in user directories as root.

1 Like

A proper solution would adapt Nixpkgs’ proton-ge-bin and/or heroic package. This is out of scope for me. The symlinking is a workaround. In general, I don’t want nix to interfere with my user files.

[…]better yet, a user unit so you don’t hard-code creating stuff in user directories as root.

The code creates a user unit, doesn’t it?

No, it creates a symlink in your home directory using the user activation script, which is a centralized script for init things.

This script exists because systemd needs to be invoked by something, but using it directly is generally discouraged. You’re basically hooking into the NixOS bootstrap code just to create a symlink.

To create symlinks or files or directories as root in arbitrary locations, use systemd.tmpfiles. To run scripts, use systemd.user.services with serviceConfig.Type = "oneshot" and use the appropriate target in wantedBy.

Having looked a bit at how heroic works, this is likely super trivial. You just need to change the configuration of their defaultSteamPath. Something like this would give you a proton-ge packaged up correctly for this use case:

pkgs.buildEnv {
  paths = [ pkgs.proton-ge-bin.steamcompattool ];
  extraPrefix = "/compatibilitytools.d/Ge-Proton";
}

The rest is just hooking it into their config loading. I assume there’s a CLI flag or env variable, or worst case you could use XDG_CONFIG_HOME. Stick it into a pkgs.runCommand with a makeWrapper invocation and you’re done.

Thanks for your deep analysis. I agree, home-manager and hjem provide a better solution, if one uses one of them already. If heroic finds proton-ge-bin in the nix-store out of the box would be perfect.

I investigated the system.userActivationScripts option deeper and it concatenates the script snippets to a single systemd user unit nixos-activation. This unit is run during activation of the new system state (roughly nixos-rebuild switch).

Anyhow there is also the systemd.user.tmpfiles option. I have reworked the snippet to this:

  # Link the Proton-GE nix store path to the user directory.
  # This is needed for using nixpkgs' proton-ge-bin with Heroic and others.
  #
  # Discussion: https://discourse.nixos.org/t/using-proton-ge-bin-package-outside-of-steam/77598/2
  systemd.user.tmpfiles =  {
    enable = true; # Let this fail, if someone changes the default.
    rules = let 
      compatdir = "%h/.steam/root/compatibilitytools.d";
      link = "${compatdir}/Proton-GE";
      target = pkgs.proton-ge-bin.steamcompattool.outPath;
    in [
      "d ${compatdir} - - - - -"
      "L+ ${link} - - - - ${target}"
    ];
  };

You can write that a bit nicer with the .settings variant, no need to use the obtuse upstream syntax:

{ pkgs, ... }: {
  systemd.user.tmpfiles."50-proton-hack" = let
    # Alternatively, you should be able to use the non-symlinked version
    # compatdir = "%h/.local/share/Steam/compatibilitytools.d";

    compatdir = "%h/.steam/root/compatibilitytools.d";
  in {
    ${compatdir}.d = { };

    "${compatdir}/Proton-GE"."L+" = {
      argument = pkgs.proton-ge-bin.steamcompattool.outPath;
    };
  };
}

That way it also merges nicely with modules if you want to add other settings. Not that you have to do this, this is mostly for other people who might stumble upon the thread.

Be aware that this puts that symlink in every non-system user’s home directory.

@TLATER You pointed to an issue with your comment about the non-symlinked version of compatdir. Above snippet presumes, that Steam has been started before systemd creates the tmpfiles. A more proper solution is:

  # Link the Proton-GE nix store path to the user directory.
  # This is needed for using nixpkgs' proton-ge-bin with Heroic and others.
  #
  # Discussion: https://discourse.nixos.org/t/using-proton-ge-bin-package-outside-of-steam/77598/2
  systemd.user.tmpfiles =  {
    enable = true; # Let this fail, if someone changes the default.
    rules = let 
      steaminstalldir = "%h/.local/share/Steam";
      compatdir = "${steaminstalldir}/compatibilitytools.d";
      link = "${compatdir}/Proton-GE";
      target = pkgs.proton-ge-bin.steamcompattool.outPath;
      steamdir = "%h/.steam";
    in [
      "d ${compatdir} - - - - -"
      "L+ ${link} - - - - ${target}"

      # When Steam gets started for the first time, it creates a directory with
      # symlinks to the install directory (among other things). Third party
      # tools are using these to find Proton versions.
      "d ${steamdir} - - - - -"
      "L ${steamdir}/root - - - - ${steaminstalldir}"
      "L ${steamdir}/steam - - - - ${steaminstalldir}"
    ];
  };