How to test systemd services/unit/timer without `nixos-rebuild switch`?

Every nixos-rebuild switch take me about 2-5 minutes on average to finish. Sometimes systemd service script break because of permission or other issues, so I want to test it before make it permanent.

Is it possible to define a temporary services without nixos-rebuild switch? Thanks.

Sorry the actual question should be how to test the systemd service/timer immediately without the long evaluation and applying time of the whole flake? I don’t what to wait 1-5 minutes between every tiny little changes in those little scripts.

Try nixos-rebuild test (but I suppose that wouldn’t solve the part about waiting time).

as @ekhh points out, nixos-rebuild test will allow you to run an in-situ config in context of the current shell w/o applying it to the bootloader, while nixos-rebuild switch both applies it to the bootloader and flips to it.

I tried manually put some service file in /etc/systemd/system but it is a read-only file so I am unable to do it.

Sorry I am using nh actually. nh os test is still slow

Sorry the actual question should be how to test the systemd service/timer immediately without the long evaluation and applying time of the whole flake? I don’t what to wait 1-5 minutes between every tiny little changes in those little scripts.

@phaeseeKe5Ee NixOS aims to be declarative first, but you can create a systemd user service imperatively while iterating; ex.

% systemctl --user edit woo.service
No files found for woo.service.
Run 'systemctl edit --user --force --full woo.service' to create a new unit.

% systemctl edit --user --force --full woo.service
Successfully installed edited file '/home/rph/.config/systemd/user/woo.service'.

% systemctl status --user woo.service
○ woo.service
     Loaded: bad-setting (Reason: Unit woo.service has a bad unit file setting.)
     Active: inactive (dead)

Oct 12 06:22:35 x86mbp13 systemd[1446]: /home/rph/.config/systemd/user/woo.service:1: Assignment outside of section. Ignori>
Oct 12 06:22:35 x86mbp13 systemd[1446]: woo.service: Service has no ExecStart=, ExecStop=, or SuccessAction=. Refusing.
Oct 12 06:22:35 x86mbp13 systemd[1446]: /home/rph/.config/systemd/user/woo.service:1: Assignment outside of section. Ignori>

give that a shot, perhaps?

e: it wouldn’t be my first choice, personally, as the insurance provided by defining the service declaratively using the nix config language DSL trumps the 2m generation build time (IMHO), but to each their own. i’m trying to provide a workaround for you.

You can pretty quickly get the unit files you need from your current config:

$ nix build '.#nixosConfigurations.YourMachine.config.environment.etc."systemd/system".source'
$ cat result/nix-gc.service

You can also then link that file into your user units:

$ systemctl --user link result/test.service

if you then change your config, your can rerun the build and run systemctl --user daemon-reload to reload the unit from disk.

If you otherwise want to do some iterative testing on services within a module, I would recommend writing some nixos integration tests to exercise the code. They can be run pretty quickly, especially with the new support for running the tests in nixos containers rather than a full VM.

Why does it take that long? I’ve not seen it take more than like 30 seconds, personally. Maybe looking a bit into making the rebuild cycle faster is the real solution here.

The other suggestions in this thread won’t be any faster, because all of them still exercise the same rebuilds, they just don’t apply them to the real system (well, except for writing services by hand, but you probably don’t want to do that).

If 30 second cycles is still too much, you can consider using nixos-rebuild build-vm and then using systemctl edit to add overrides to the service within the VM for testing,

This is easy. Just don’t have RAM. On my machine I do as well get eval times in that ball park.

And on an older machine I don’t use anymore I had eval times of 30 to 45 minutes when abondoning it.

If you want a new, experimental, LLM-assisted tool, @tomberek just released this, which looks like it can also be used to edit config files from your current generation:

Similar things like systemctl edit were also mentioned before, but this is another interesting approach, and can get more than just units if you need to edit other configuration too.

I have no idea. I have 64GB ram. I have 21 flake inputs and the config have some overlays and it have 26K nix code lines, maybe thats the problem. my pi4 take 4mins to only evaluate it. I have already clean things up by some advice of reducing lines like import nixpkgs {}.

It definitely is part of it.

Inputs are not free, many small overlays are not free, actual code in your config is not free.

And if you even have another set of modules that you operate from “pure config” files, then of course that also adds to the cost.

I find it hard to imagine a configuration that needs this level of complexity, there’s probably a lot of pruning that could be done that would ultimately lead to less annoying UX.

A pi4 also does not have the strongest CPU. If you are going to both push the limits of what is reasonable with nix and eval on an rpi you’re going to struggle. I’d sincerely recommend using another device to evaluate and using one of the many ways to push deployments instead.

Hell, testing in a VM on a proper dev device would probably already significantly improve your cycle times.

Similar here, I’ve just ate the eval times (usually 1-2min for me on my X1E2) with my 71 flake.lock nodes.

The problem is that I do rely on stuff that’s not in nixpkgs (for example, firefox-nightly to get unsigned extensions for my firefox-extensions-declarative extension forks, epireyn/niri-flake for niri config, reaper-flake, system-manager, etc), several custom programs, software patches, etc.

I do try to avoid import nixpkgs and similar things, though.

I think those are usually the kinds of things that add most overhead. Upstream package definitions very often evaluate additional nixpkgs instances for arguably good reasons (or often just inexperience with nix), but as a consumer you probably don’t want that.

IMO the best approach is to just package that software properly, either in nixpkgs or to carry derivations downstream. This may look similar to upstream’s code, and therefore duplicated, but distro packaging serves a different purpose; trying to close the loop leads to a lot of the problems we see in the industry in general IMO (see e.g. the certificate mess we’re all worrying about today).


But this anyway sounds like a very different use case; You’re building an ultramonorepo for all your systems, it isn’t surprising that that results in complexity.

Not that I don’t have external dependencies in my configurations, at the moment I count about 17 for my desktop monorepo (though I’ve cut that down to just a handful of computers). I’m just careful about the projects I depend on and make sure they in turn aren’t very eval-heavy. I even use flake-parts (which did add notable overhead, but not more than a few seconds).

But related to this post, if you’re deploying to an rpi, you’re probably not configuring a complex firefox profile, so a lot of the code you seem to evaluate everywhere should not be necessary in that context. Even with flakes, that can probably be expressed quite cleanly; just don’t add modules you don’t use for a given system to imports.

If you are building firefox profiles on your rpi, or include your rpi stuff in an ultramonorepo, you probably have another device to eval with. But if you’re doing ultramonorepo stuff for systems this divergent maybe consider if that’s the best approach.

To be clear, though, we are talking about eval times, right? Any notable use of IFD?

All 1 of them (so far), nixos-bill is an unmaintained VM and the only other one is extreme-creeper, my laptop.

My most notable use of IFD (at least from my repo and not from any flake inputs) is for declarative and reproducible Catppuccin userstyles:

It runs this Rust program here:

There are also some projects I have using crate2nix IFD generation (AFAIK at least 1 of my flake inputs I use as well as maintain uses this, although I usually generate Cargo.nix and keep it in the repo now (not least for git deps)).

That said, I would definitely make a trimmed down config if I was evaluating on an RPI, or be using deploy-rs to evaluate on a better machine.

I do realize that my config is a bit of an outlier (maybe not so much amongst NixOS discourse regulars (although I do think my flake input number is unusually high, but I like to experiment with things), but definitely amongst all nix users).

I’m not sure which machine they are evaluating on here, but it sounds like it might be the pi4. If that’s the case, please check out deploy-rs if evaluating on your other machine is faster!

Any IFD will significantly bump that eval time, so yeah, I think your example is a worst-case scenario for nix eval times. I wouldn’t use it as a benchmark for what you should expect.

My configurations contain no IFD anymore these days, precisely because eval times were annoying. Wouldn’t be as nice without the absolute madness that is parsing elisp from nix, of course.

I think I’ve come to go back to my original conclusion that the issue here is probably eval complexity. It shouldn’t take that long.

Less than you think :wink:. I think it’s quite easy to end up with a big list unless you are very careful to prune this.

I have a pretty NixOS-purist stance, but I don’t deny that third party projects are useful. I just think folks should be a bit more deliberate in choosing when they are necessary - far too many newcomers are using upstream flakes for things that are packaged perfectly fine in nixpkgs, and incur the very integration issues nixpkgs exists to prevent.

Yep, I mostly use them for external nix libraries, things that I want to stay on the bleeding edge of, and things that are too experimental/personal to be in nixpkgs yet. I also use follows extensively, even though that makes updating take hours due to rebuilds.

That’s basically the reason that I (got my coding agent to) built that Rust parser for the userstyle stuff :wink:.

cat /tmp/flake.lock | jq ".nodes | length" for that is still 28 compared to my 71 :wink:.

Anyways, IMO for IFD we really just need parallel eval to continue doing other stuff while Nix builds/checks that the store patch output exists. I’m not writing a CSS parser in Nix, even w/ AI assistance :wink:.