Skip to content

Allow running without container with nix - #345

Draft
avandecreme wants to merge 6 commits into
netbrain:masterfrom
avandecreme:nix_claude
Draft

Allow running without container with nix#345
avandecreme wants to merge 6 commits into
netbrain:masterfrom
avandecreme:nix_claude

Conversation

@avandecreme

@avandecreme avandecreme commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

This is variant number 3 in #28

This is very much not ready for merge but since I was able to make my first ride with it, I figured I would share it already.

Things to fix before merging:

  • Remove duplicated code between existing scripts and the new nix files
  • Make it possible to switch between the existing container solution and this one
  • Fix auto-login
  • Fix process which keeps running after having quit swift
  • Cleanup, this was entirely generated by claude, so there is garbage things

@avandecreme
avandecreme marked this pull request as draft April 17, 2026 17:39
@netbrain

Copy link
Copy Markdown
Owner

Cool! it's definitely a start.

@avandecreme
avandecreme force-pushed the nix_claude branch 11 times, most recently from 1b5f6ca to 4e30e32 Compare May 20, 2026 19:07
@avandecreme
avandecreme force-pushed the nix_claude branch 4 times, most recently from f3490d2 to 325f2a4 Compare July 14, 2026 08:40
@avandecreme
avandecreme marked this pull request as ready for review July 14, 2026 08:42
@glennvl

glennvl commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

@avandecreme I appreciate the work you are doing here. But you keep force pushing a single commit with ~1500 lines changed, I'm not sure how to review that and understand all the changes, I can't follow your reasoning if it's a single commit.

Comment thread flake.nix Outdated
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

runfromprocess-rs.url = "github:quietvoid/runfromprocess-rs?rev=a3d003c07d1bd11ff93c4cac96d2c3aa5deb8471";

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not ideal because it pulls an old rust compiler. Maybe we should just embed a built binary in this repo?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it pull an old compiler? There's nothing in the flake indicating a specific version.
There could be a release with a pre-built binary which can be downloaded, instead.

@avandecreme avandecreme Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it pull an old compiler?

Because it picks the one in the lockfile.

Apparently, I could write this to avoid that though:

  runfromprocess-rs = {
    url = "github:quietvoid/runfromprocess-rs?rev=a3d003c07d1bd11ff93c4cac96d2c3aa5deb8471";
    inputs.nixpkgs.follows = "nixpkgs";
    inputs.fenix.follows = "fenix";
  };

Using a pre-built binary would be better in my opinion. This would avoid having to pull any rust compiler.

Would you accept a PR on https://github.com/quietvoid/runfromprocess-rs to make it publish a binary on release?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know Nix but there's no mention of the compiler in the lockfile either, only rust-analyzer.
I know @glennvl has forked this so maybe you guys should figure out if mine should be replaced, I don't really intend to maintain it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenix is the package bringing the rust toolchain. It is locked on a 2023 version in the lockfile.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When running Zwift without a container, there is no container image to provide the latest Zwift version. So updates have to be done locally. Why not just use the Zwift launcher instead of bypassing it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is this comment saying that the launcher is not fully functional under wine:

zwift/src/run_zwift.sh

Lines 143 to 145 in 0872729

# The Zwift launcher is not fully functional in wine:
# - It cannot show the login page (1)
# - It cannot launch Zwift (2)

It seems to work now though, albeit with some display glitches sometimes.
I will try to add a commit in that direction

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit added. I would still like to check which packages are really necessary in the FHS as well as checking what options still make sense when using the launcher (and update the doc accordingly).

@avandecreme
avandecreme force-pushed the nix_claude branch 2 times, most recently from c7658ca to 440ba48 Compare July 14, 2026 10:47
Move the container package wrapper, common derivation helpers, and NixOS
module out of flake.nix into separate files under nix/. flake.nix becomes
a thin orchestrator. No behavioral change.
Add support for running Zwift natively using Wine in a FHS environment as an alternative to containers. Introduces src/zwift-nix-fhs.sh as the FHS launch script, extends run_zwift.sh and update_zwift.sh to handle the nix-fhs container tool, adds nix/zwift-fhs-package.nix, wires up the new runfromprocess-rs input, and extends the NixOS module with containerTool = "fhs".
Parametrize the NixOS test flake to build all three containerTool values (podman, docker, fhs) and update CI to check all three.
Update the NixOS documentation with containerTool = "fhs" and winePrefix.
@avandecreme

Copy link
Copy Markdown
Contributor Author

@avandecreme I appreciate the work you are doing here. But you keep force pushing a single commit with ~1500 lines changed, I'm not sure how to review that and understand all the changes, I can't follow your reasoning if it's a single commit.

I split in four commits. Hopefully, this is more approachable. I also cleaned up stuff from claude.

@netbrain

Copy link
Copy Markdown
Owner

Took a proper look through this, really like where it's heading 🙌 getting a ride done natively is a great milestone. Being critical below since you asked, and since a few things need sorting before this can go in.

The thing that stands out most: this is basically 3 PRs in one (the flake refactor into nix/, the nixpkgs unstable → nixos-26.05 bump, and the fhs backend), and the "no behavioral change" refactor actually breaks the existing container path in two spots. Those are the real blockers for me:

Blockers (these hit the working container path, not just fhs)

  1. Unquoted exports in nix/zwift-container-package.nix. The old flake single quoted everything (export IMAGE='${image}'), the new script dropped the quotes (export CONTAINER_EXTRA_ARGS=${containerExtraArgs}). Anything with a space or shell char breaks right at the export line. containerExtraArgs almost always has spaces, and zwiftPassword / the dir paths are exposed too. Need the quotes back.

  2. The verbosity option is gone from the module and VERBOSITY is never exported anymore. So an existing config with programs.zwift.verbosity now fails to eval, and the container is stuck at verbosity 1 with no way to change it. Docs still list it.

fhs stuff to sort

  • The module only actually passes winePrefix + debug through to the fhs package. Everything else (username/password, resolution, gamemode, wayland, the dir options...) is declared but silently ignored for fhs. Either wire them up or add asserts + document that fhs doesn't support them, otherwise it's a bit of a footgun.
  • Auto-login isn't really done for fhs even though the box is ticked. zwift-nix-fhs.sh reads ZWIFT_USERNAME/ZWIFT_PASSWORD but never uses them, login ends up manual through the launcher. The container path does the token dance via zwift-auth.sh. Fine to keep it manual for now, let's just drop the dead vars and be honest about it in the docs.
  • fhs is also missing gamemode (it's in the env but gamemoderun is never called), resolution override, and the settings rsync (the issue 366 workaround). Ok for a v1, should just be documented as limitations rather than looking like working options.

Smaller things

  • zwift-fhs-package.nix exports WINE_PREFIX (underscore) which nothing reads, the real one is WINEPREFIX baked into the FHS profile. Dead code.
  • The wayland reg key is hardcoded on install instead of gated behind wineExperimentalWayland like the container does.
  • zwift-nix-fhs.sh copies msgbox + the wine task helpers verbatim from run_zwift.sh. I get that it's host vs in-container so sharing is awkward, just noting it.
  • One to verify (not a bug): fhs waits on ZwiftLauncher.exe while the container kills the launcher and waits on ZwiftApp.exe, so opposite models. Worth confirming that closing the launcher tears a running game down cleanly and that that's the intended flow. The detection helpers are byte identical to the container so the detection itself is solid.

On the nixpkgs bump, no objection to pinning stable, but it's really its own change. For the container path nixpkgs only affects the wrapper script + devshell, for fhs it pins the wine version which is what drives game compat, so it deserves its own reasoning.

My suggestion: split it up, land the pure refactor first (with the two blockers fixed and verified actually no-op), then bring fhs in on top. Happy to help review the pieces. Nice work getting this far 👍

@glennvl glennvl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking this should be a separate project entirely. We have netbrain/zwift for the container-based solution. Why not create netbrain/zwift-nix for the native nix solution? Combining two projects that barely have anything in common creates a difficult to maintain mess in my opinion.

What are we doing in netbrain/zwift?

  • We have a container image with wine and zwift in it
  • A github action keeps zwift up-to-date in the container image
  • The end user uses the container and never updates zwift itself on his system
  • The zwift launcher is bypassed entirely (it used to not work in the past, it does now, but we don't need it because the latest zwift version is baked into the image)
  • Because we bypass the launcher, the end user's zwift credentials are not persisted automatically. So we work around this by having our custom login script and way to persist credentials.
  • We need a lot of configuration options to make hardware accelerated graphics work in the container
  • We provide some convenience options because it is hard to access data inside the container
  • We have a script that launches the zwift container image with all the right parameters, this launcher script is installed on the end-user's system using an installer script or through the nix flake

What should be done in netbrain/zwift-nix?

  • Updates happen on the end-user's system, so it cannot bypass the launcher
  • The nix flake should install the necessary dependencies (wine, winetricks, gamemode)
  • The nix flake should install zwift (wine ZwiftInstaller.exe)
  • The nix flake should start the zwift launcher, which then automatically handles persisting login info, updating zwift, ..., just the way it works on windows
  • After the launcher is started, its job is done, no need kill the launcher, wait for zwift to exit, ...
  • No need for fancy helper scripts that bypass the launcher, update zwift, login, ...

Which options still make sense in netbrain/zwift-nix?

  • Gamemode: If enabled, launch the zwift launcher using gamemode
  • Wayland: If enabled, set DISPLAY= before launching the zwift launcher
  • EGL/GLX: Potentially add an option that let's the end-user select whether wine should use EGL (default) or GLX as OpenGL backend
  • Anything related to zwift data and configuration (override graphics, override resolution, mapping directories): Document where to find/modify it instead, since it's easily accessible on the end user's system.

I'm not seeing a common base that justifies keeping these two projects together.

Obviously @avandecreme knows his way around nix, so if a new netbrain/zwift-nix project is created, I'd still like him to have a look at the nix stuff in the netbrain/zwift project to see what can be improved there.

@netbrain

Copy link
Copy Markdown
Owner

Really solid breakdown, you nailed why the two paths feel so different, and i agree the current module is a mess with the container options bleeding into the native path. But i'd push back on the separate repo, i think we'd be fixing a structure problem with a repo boundary.

What i don't want to lose by splitting: the single front door for "zwift on linux" (one repo, one issue tracker, one docs site, pick a backend), and we'd end up duplicating the flake/desktop/icon scaffolding across two repos. Honestly the PR is already 90% a monorepo, one flake exposing zwift-container + zwift-fhs. The real problem is just that it all hangs off one flat programs.zwift where ~15 options silently no-op for fhs.

So i'd rather fix the module than move the code. Rough shape:

programs.zwift = {
  enable = true;
  backend = "container";        # "container" | "native"  (default container)

  # shared
  username = "...";
  password = "...";             # native: only if we add token login, otherwise drop
  debug = false;
  verbosity = 1;

  container = {
    image; version;
    dontCheck; dontPull; dontClean; dryRun; interactive;
    networking; uid; gid; vgaDeviceFlag; privilegedContainer; extraArgs;
    overrideGraphics; overrideResolution;
    workoutDir; activityDir; logDir; screenshotsDir;
  };

  native = {
    winePrefix;
    gamemode = true;            # launch the launcher via gamemoderun
    wayland = false;            # DISPLAY= / wine wayland driver
    openglBackend = "egl";      # "egl" | "glx"
    # data/config: documented (it's on the user's own fs), not options
  };
};

plus assertions so setting native.* while backend = "container" (and the reverse) fails loudly instead of doing nothing. That's what actually kills the footgun.

File layout stays close to what's already there:

nix/
  module.nix        # backend switch + assertions, imports the two below
  container.nix     # container options + wrapContainerPackage
  native.nix        # native options + wrapFhsPackage
  zwift-common.nix  # desktop item, icon, meta  (the only genuinely shared bit)

flake keeps exposing packages.zwift-container and packages.zwift-fhs, default = container.

Fair warning: nesting like this is a breaking change for existing container configs (image -> container.image). If we want to avoid the migration we keep the flat names, add the backend enum, and lean on the asserts. I'm ok with the rename for a cleaner api but open either way.

And i fully agree with your option triage, native basically collapses to gamemode + wayland + egl/glx + "your data lives here" docs. That's a feature, the native module ends up tiny and sits happily next to the container one.

The one thing that'd flip me to a separate repo: if the plan is for the native path to move on its own cadence/CI without being gated on this repo. If that's where this is heading, split makes sense. If not, i'd keep it here. What do you both think?

@avandecreme

Copy link
Copy Markdown
Contributor Author

Thanks a lot for the extensive review!

At some point I wondered whether it should be a separate repo too. But I agree with @netbrain that having a single point of entry for zwift on linux is valuable.

I tried to avoid breaking changes until now but this probably would not be too much of a problem for nix users if we can make the error messages explicit on what the change should be.

The one thing that'd flip me to a separate repo: if the plan is for the native path to move on its own cadence/CI without being gated on this repo.

I don't really see that happening. On the other hand, even if the common code is low, it still exists. Having one doc for everything is nice too in my opinion. So keeping one repo is preferable in my opinion.

@glennvl
glennvl marked this pull request as draft August 21, 2026 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants