Is it possible to declare a directory creation in the NixOS configuration?

Hi,

I want to deploy a container with Podman on NixOS machine. The container has to mount some directories (bind volumes) - see the code below.

I don’t want to create them manually. Is it possible to define that a certain directory should be present with Nix?

The only thing I found is about systemd.tmpfiles.rules, but on mynixos.com it says that’s for volatile and temporary files. Should I use that for a persistent directory too?

The piece of code:

  virtualisation = {
    podman.enable = true;
    oci-containers = {
      backend = "podman";
      containers = {
        navidrome = {
          image = "deluan/navidrome:latest";
          ports = [ "4533:4533" ];
          volumes = [
            "/home/arek/navidrome/data:/data"
            "/home/arek/music:/music:ro"
          ];
        };
      };
    };
  };

6 Likes

tmpfiles.rules is usually the way to go, it’s used throughout NixOS for exactly this purpose. Have a read through the systemd docs to see which settings to set to make it never clean the directories. search.nixos.org is the official docs search page by the way, no idea what mynixos.com is: systemd.tmpfiles.rules.

A more “correct” alternative is using the systemd option for creating a StateDirectory, if you can figure out how to hook into the underlying systemd service. Shouldn’t be too hard, actually, it’s just something like:

systemd.services."${config.virtualisation.oci-containers.backend}-navidrome".serviceConfig = {
  StateDirectory = "navidrome/data:/home/arek/music navidrome/music:/home/arek/music";
};

YMMV, however, since it forces those directories to exist in the paths that are typically used for services, and with my config only symlinks them to your home directory. Hence, just using tmpfiles might be better, since this is really about your home directory existing. Personally I feel like this is going to be a permission settings nightmare for you in general, not a fan of mixing user and system services.

3 Likes

NixOS already has a navidrome module, you might want to use that instead of container.

As for temporary directories, I don’t even remember the whole process that lead to it, but I have this and its been working fine for a long time:

let
  payas = "payas";
in
{
  # Open navidrome port, but only for local network
  networking.firewall.extraCommands = ''
    iptables -A nixos-fw -p tcp --source 192.168.0.0/24 --dport 4533:4533 -j nixos-fw-accept
    iptables -A nixos-fw -p udp --source 192.168.0.0/24 --dport 4533:4533 -j nixos-fw-accept
  '';

  services.navidrome = {
    enable = true;
    settings = {
      # Address is set by individual host
      Port = 4533;
      MusicFolder = "/home/payas/Music/";
      DataFolder = "/home/payas/.navidrome/";
      EnableCoverAnimation = false;
      DefaultTheme = "Extra Dark";
      CoverJpegQuality = 100;
      LastFM.Enabled = false;
      ListenBrainz.Enabled = true;
      EnableUserEditing = true;
    };
  };

  systemd.services.navidrome =
    let
      cfg = config.services.navidrome.settings;
    in
    {
      serviceConfig = {
        User = payas;
        Group = payas;
        ProtectHome = pkgs.lib.mkForce "tmpfs";
        BindPaths = [ cfg.DataFolder ];
        BindReadOnlyPaths = pkgs.lib.mkForce [
          builtins.storeDir
          cfg.MusicFolder
        ];
      };
    };
}
2 Likes

Does BindPaths actually create the directory? From the documentation I was under the impression that the unit would just fail if it doesn’t exist yet, with the option of ignoring the lack of directory if you set the correct option.

I’m also surprised you need to set builtins.storeDir in there. I guess without it the unit can’t access certain runtime dependencies? I at least assumed the nix package patched mounting /nix in, very few things should work without it.

Do agree using the NixOS module is typically better, combined with systemd’s sandboxing nix doesn’t really need oci containers.

I remember this happening via some asking around (obviously :D), but have no memory for anything else.

You are right about path needing to exist beforehand, so this won’t be automatically created on a new system (from systemd doc):

Note that the destination directory must exist or systemd must be able to create it. Thus, it is not possible to use those options for mount points nested underneath paths specified in InaccessiblePaths= , or under /home/ and other protected directories if ProtectHome=yes is specified. TemporaryFileSystem= with " :ro " or ProtectHome=tmpfs should be used instead.

Perhaps this is something I need to address. Pointers welcome :slight_smile:

2 Likes

@TLATER I tested tmpfiles.rules. In the manual I found this for age column:

If omitted or set to " - ", no automatic clean-up is done.

So I’ve added the following code to my configuration:

  systemd.tmpfiles.rules = [
        "d /home/arek/navidrome 0770 arek users -"
        "d /home/arek/navidrome/data 0770 arek users -"
        "d /home/arek/music 0770 arek users -"
  ];

Looks like it’s persisted through reboots. Thanks!

I will probably test StateDirectory later

@payas I was thinking about using it, but currently I want to test containers on NixOS (I have some other stuff that’s being run in containers). I may try to go with systemd service later. Thanks for providing your config and your input!

3 Likes

I’ve ran into this, too, and others too I think. I wonder if it would be good to have a nixos module to “create directories” that doesn’t smell like it’s about temporary directories (even if that’s then the implementation), to not throw people off?

3 Likes

No worries, I myself deploy pi-hole via container only. If you figure out how to keep that container updated automatically as new versions get released to docker, I’d be very grateful.

nvfetcher supports pulling from dockerhub nowadays: GitHub - berberman/nvfetcher: Generate nix sources expr for the latest version of packages

This has been brought up with systemd upstream once or twice. They aren’t going to budge on the name, IIRC.

:man_shrugging:

No need to involve upstream though, there can absolutely be a wrapping NixOS module that just sounds friendlier.

I mean… we could provide another abstraction for users to learn and remember and eventually debug but I think we should ask ourselves what value this provides? Isn’t the right thing to improve documentation and make it very clear what tmpfiles does?

1 Like

I think naming is still important, and I think a wrapping module isn’t that out of place - there’s networking.useSystemd after all - but yeah that’s a good idea. Updating the documentation to reflect the option’s actual usage is probably more important.

3 Likes