Why is `userActivationScripts` generally frowned upon? And what to use instead

I need to run a script that sets up some user specific stuff, which needs to succeed when I rebuild my system or else the user environment is rendered “unusable” for the sake of argument.

system.userActivationScripts seems perfectly suited for this, however the task I need to execute includes cloning a git repository (my homemanager config and dotfiles), which would break the constraint given in the description of the config option which requires scripts to be idempotent and fast,

Here is the thing:
A failed clone should also fail the rebuild since the repo is required for the system to work.
I have seen others use systemd user services that run at startup to accomplish this, but this won’t fail the rebuild if the script fails. It will also make the script run on each system startup which will waste performance when it is needed elsewhere, internet might not be available at that time and, since the script is dependent only on the system config (and the state of the external server, but lets ignore that for now) it only has to run whenever the system config changes and not on each boot.

Should I use userActivationScripts for this? Should I use a service that runs on startup? Is there another way to make scripts run when the system config is being rebuilt?

userActivationScripts are run on every activation. Each boot activates the system anew.

If you need your GH repo as part of the system, write a derivation that fetches it, and perhaps use the activation script for linking it into place.

Or otherwise make the desired behaviour depending on otherwise settable stuff that you can implement as much as possible in nix.

With flakes, you can use the git repo as a flake input, which will fetch it at build time (and lock its version in flake.lock). Your activation script can then simply link it into place, or do whatever else you want, and the repo is already guaranteed to be in your nix store (or the build failed). It also will only update your git repo when you update the flake input (using nix flake update or the specific-input-only update), which will allow you to keep your system config in sync with the version of the git repo you use.

1 Like

The thing is, I want the repo to be in my home directory and I want it to be mutable also the version should not be pinned.

Thank you for clearing that up. I thought the scripts only ran on a rebuild and not also on boot.

Nix has a tendency to want to eat (or encourage us to want it to eat) everything it comes into contact with, but there’s also inevitably some boundary where you’ll have to bootstrap. If you’re the system’s only user (or if you use your dotfiles on multiple systems), it might help to treat your dotfile repo as roughly outside of (broader in scope than) your nix configuration?

This might be a little off-the-mark because I haven’t invested much effort on my bootstrap process beyond macOS (I built my current NixOS system before I spent much time working on this and haven’t needed to cross that bridge yet…), but my own current approach is something like:

  1. open a browser to access my dotfile repo’s README
  2. copy a small bootstrap stub out of the README and run it in terminal; it will
    • install nix
    • open a nix shell with everything needed to clone/unpack my dotfile repo
    • run another bootstrap script inside the dotfile repo which will, among other things, build my Nix config

Good point. I will incorporate that thought into the structure of my dotfile repo, thanks!

My main motivation to setup my user environment using the system configuration stems from the desire to be able to perform an unattended installation of nixos and have it install to the same state as it would be on any other machine of the same hardware.
I have this process mostly down, to the point where I can plug a usb stick into any supported hardware, have it boot from it and come back to a working desktop environment some time later.

The only thing that’s missing at the moment is everything specific to the individual user.
Since I will be the only one using these systems, there is probably no point to go too overboard with this (i.e. I will not setup an ldap server for just a single person), but I still like to entertain the idea of how much this could scale.

I have posted another thread earlier, specifically about setting up homemanager declaratively and without user interaction and I have received this great response: Automating home manager setup - #6 by fricklerhandwerk

But using a systemd service that runs on every reboot seemed a little overkill to me since the location of the repo is only dependent on the system config (in this case).
I had heard of userActivationScripts before, but I thought that it only ran on a rebuild, which was luckily cleared up by the answer by NobbZ above.

I guess the triangulation between this process and mine would be for the bootstrapping script/program on your USB to handle cloning the repo.

Another thought might be to use the activation script but gate the clone by whether the path exists, so that it’s a no-op outside of the first run.

1 Like

That’s pretty much what the config in the other thread does, but it has to go through some pretty weird hoops to accomplish that efficiently. Just running the script on a rebuild would be much more elegant imo.

Then again, it has to run with user permissions, so things already are pretty weird to begin with.

I’ve had a related thought brewing, I guess roughly that there’s a missing ~declarative file-tree tool + DSL that is mostly-congruent with how Nix does things, but which recognizes that mutable state has some different needs and makes it possible to compromise selectively without just throwing up your hands?

I imagine being able to use these to describe my homedir, my project directory, and the projects inside of it.

Project definitions might express things like:

  • this project is cloned as a reference, and doesn’t need to be backed up
  • which of my systems/profiles/etc. the project should be materialized on (i.e., I have the definition everywhere, but “building” the configuration will only materialize it on these systems)
  • one or more sources for the project, with the ability to clone/checkout/download+unpack/restore-from-backup, etc.
  • when/where/why/how the project should be backed up (which files, retention policy, combined vs. per-project backups, etc.)
  • when a project dir can be de-materialized (i.e., never, it is fully-pushed, or backed up, as soon as it hasn’t changed for more than N days, etc.)
  • if/how/where uncommitted/unpushed work should be auto-committed and pushed