Creating a writeable file in a derivation

I am building a derivation whose upstream source contains a settings file that must 1) exist and 2) be writeable by the time the user runs the software. The upstream contains an example settings file that I can just copy the data out of and put somewhere else.

However, I’m struggling to figure out where to put it or how to manipulate it during installPhase so that I can accomplish this.

What options do I have? Can I write the settings file into /etc and then symlink it to the package so it knows where to find it? Or should I be looking at something else?

The closest I’ve gotten so far is to do something like this:

settingsFile = builtins.readFile "${src}/example.settings.file";
myPackageSettings = pkgs.writeTextFile = {
  name = "my-settings.file";
  text = settingsFile;
};

and then in the installPhase writing

ln -s ${myPackageSettings} $out/settings.file

This solution satisfies the first problem-- it makes it so the file exists. However, since it is in /nix/store, it can’t be modified.

What else can I do?

The usual thing is to put variable files somewhere in /var (probably /var/lib/<softwarename>), and ensure that the software is either configured or patched to read from /var instead of symlinking anything from outside the store.

If the software needs the file to exist the first time it’s run, I would probably end up wrapping it in a script that copies /nix/store/...-your-software-1.2.3/share/your-software/example.settings.file into /var if the corresponding /var file doesn’t exist, before execing the real program.

1 Like

This makes since. Somewhat similar to how nixpkgs/nixos/modules/services/web-apps/wordpress.nix has done it, I’ve set up a stateDir in /var/lib/my-package that stores directories that will contain state once the program runs. I could put it there, however, the /var/lib directory doesn’t exist during the installPhase (I’m testing this by building a nixos vm), so I can’t actually copy any files there.

I can, however, symlink a directory and that seems to work for making a directory exist (once the filesystem is created), but not for a file. Is this a side effect of running a vm that has to create and mount the root filesystem, or am I not thinking about your comment right?

Yes, all you can do during any phase of your derivation is put files into your derivation’s store outputs. The build sandbox prevents writes to any other location (other than temporary files that won’t exist when the sandbox exits).

Install your starting state into $out/share/your-package (just a suggestion; this exact path is not important), and write and install a wrapper shell script as your package’s entry point that initializes /var/lib/your-package if necessary based on the contents of $out/share/your-package, when it is invoked by the user and not during your derivation’s build.

2 Likes

I ended up doing something a little differently than this because an entry script didn’t quite make since for what I’m building (a webserver).

Instead, I wrote a oneshot systemd service file that runs an ExecScript that does the copying that I need, and I used a systemd.tempfiles set of tempfiles definitions to create the directories I needed. This had the effect I was looking for and could be accomplished without overriding the package definition.

Like it or not, but systemd is right the tool for the job!