Restart oneshot systemd service on every rebuild

I’d like to run a script on every configuration activation, but since activation scripts are now advised against, I’m trying to figure out how to do it using a oneshot systemd service instead. serviceConfig.RemainAfterExit = false doesn’t seem to work.

How about using systemd.services.<name>.restartTriggers

1 Like

Hmm… I could restart the service based on whether a file exists or not, but that file isn’t a part of the repo, so it wouldn’t work in pure mode…

Perhaps a system activation script that runs systemctl restart <service>?

https://search.nixos.org/options?channel=unstable&show=system.activationScripts&query=system.activationScripts

I would consider that activation scripts are discouraged as they tie things to the idea of updates and state (IE having just activated). This seems to be your goal, so doing anything else seems like activations scripts with extra steps, not solving at least my understanding of why they are discouraged.

I think there might be some systemd service that is restarted every activation, so that could be a trigger but I may be imagining its existence.

see the section on sysinit-reactivation.target: NixOS Manual though do note that it mentions restartTriggers when that’s only necessary if you’ve set RemainAfterExit=true

No. Activation scripts are discouraged because they’re a sequential, imperative hack with bad dependency management and which runs at an overly-sensitive phase of bootup. systemd services are very much preferred when at all possible because they are a much more robust model of system organization.

3 Likes

Well, fair enough.

I stand corrected, thank you.

1 Like

So would something similar to the following work?

{
  systemd.user.services = {
    a = {
      ...
      requiredBy = [ "sysinit-reactivation.target" ];
      before = [ "sysinit-reactivation.target" ];
      restartTriggers = [ config.environment.etc."a.d".source ];
      ...
    };
    b = {
      ...
      requiredBy = [ "sysinit-reactivation.target" ];
      before = [ "sysinit-reactivation.target" ];
      restartTriggers = [ config.environment.etc."b.d".source ];
      ...
    };
    c = {
      ...
      before = [ "d.service" ];
      after = [ "a.service" "b.service" ];
      ...
    };
    d = { ... };
  };
}

I’m trying to create some files before a home-manager service is run, and would like a.service and b.service to run on every rebuild.

Oh, user services are a whole different thing from system services. I don’t think we have an equivalent reactivation thing for user services, other than the user service nixos-activation.service, which just runs system.userActivationScripts (user activation scripts are much more reasonable than system activation scripts, though I still tend to prefer properly structured systemd services). Note that nixos-rebuild switch only restarts nixos-activation.service in the user level systemd manager for users that are currently logged in. And also note that if you’re using the home-manager NixOS module, that gets run on nixos-rebuild by a system service, not a user service, though of course that home-manager activation might setup user-level services and stuff.

Hmm… I don’t really need these to be user services, I think, or at least, not the ones that need to be restarted. Would switching just those two to systemd.services work with the setup above, then? Or do they all need to be systemd user services?

Within systemd, any relations between services (or any units) must be among services under the same service manager. There’s one service manager instance for the system, and one per user. So you cannot have user services depend on system ones or vice versa, by design.

1 Like

Okay, so I think I can change all of them to system services, since home-manager runs as a system service anyway. I’d wanted to move away from multiple users controlling files and directories in the system config anyway. If the services above were all system services, then, would the setup work?

Yes, that’s basically what we do in Hjem, though we created an intermediate target.

We also weakened the dependency on the reactivation target from requiredBy to wantedBy to avoid brittleness - i.e. failure (for our usecase) should not disrupt activation. See the upstream docs for more context:

Often, it is a better choice to use Wants= instead of Requires= in order to achieve a system that is more robust when dealing with failing services.

I suggest testing to see what works best for your usecase.

1 Like

I think I want it to fail pretty loudly, so requiredBy might be better for me… By the way, can I still use User in the serviceConfig, or is that limited just to user scripts?

https://www.freedesktop.org/software/systemd/man/latest/systemd.directives.html#User=

https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#User=

Got it. I figured out a workaround to avoid different user shenanigans, so I might not need it anyway. I’ll report back with the result.

Everything seems to be running fine after a few permission tweaks and applying the requirements, but it’s still saying that sysinit-reactivation.target failed to restart when rebuilding, even though the target seems to be reactivating properly.