Adding folders and scripts

Hey everyone,

I’m still learning NixOS (and Linux in general) and trying to put everything neatly in my .nix files – instead of creating stuff by hand after installation. Thus, I’m here to ask a very basic question, because I didn’t find the answer: how to create folders, and add bash scripts for users. Sorry in advance if it’s really obvious… :disappointed:

From what I have seen in this blog, it should have something to do with environment.etc – am I right? However, in my configuration.nix:

  • what <name?> attribute should I use to put users’ bash scripts in the text submodule?
  • who is owning thus created scripts, and how to chown/chmod them?
  • how to create a folder – for instance /mnt/NAS – and chmod/chown it?

Thanks for reading and all the best,

2 Likes

for “user” scripts, and folders, you should into home-manager. This should solve all your points.

looking for examples, found https://www.reddit.com/r/NixOS/comments/9bb9h9/post_your_homemanager_homenix_file/ with a good example of a home directory here

The <name?> parameter in environment.etc represents the file name/path relative to /etc. For example, I used to use the following to create a helper script for ClamAV:

environment.etc."clamav/helper.sh" = {
  mode = "0555";
  text = ''
    # Script here...
  '';
};

Similarly, I still do use the following to set up networks:

  environment.etc."NetworkManager/system-connections/my-network.nmconnection" = {
    mode = "0600";
    source = ./files/my-network.nmconnection;
  };

(I find it more useful to keep files for which don’t need to contain package paths, etc. separate from my NixOS configuration.) By default, environment.etc creates symlinks to read-only files in the Nix store, you’ll need to use the group, user, and mode attributes if something (like Network Manager) is too picky to deal with this.

But!

I’ve found that environment.etc is generally not the right tool for this job. You can generally get a lot more mileage out of pkgs.writeTextFile. For example, these days I create the same ClamAV helper script using the following pattern:

let
  clamavUserScanner = pkgs.writeTextFile {
    name = "clamav-user-scanner";
    executable = true;
    destination = "/bin/clamav-user-scanner.sh";
    text = ''
      # Script here...
    '';
in {
  # ...
  systemd.user.services.clamav-scan-weekly = {
    description = "Perform a full scan of the user's home directory for viruses";
    serviceConfig = {
      Type = "oneshot";
      ExecStart = "${clamavUserScanner}/bin/clamav-user-scanner.sh \"%h\"";
    };
  };
  # ...
}

(The reason I use pkgs.writeTextFile here rather than systemd.user.services.<name?>.script is that I re-use this file in a couple of different contexts.)

If you need to add a particular user script, then you can create your own package file in an overlay using a similar pattern. This requires a bit more setup; I start by putting something like the following in my configuration.nix:

{ config, pkgs, ... }:

{
  # Add local overlays
  #
  nixpkgs.overlays = [ (import ./overlays) ];

  # ...
}

(I haven’t been able to get nix.nixPath to work for me, which looks like how you’re supposed to specify an overlay, though honestly I’m still a bit of a noob myself.) Then I have a file /etc/nixos/overlays/default.nix that looks a bit like this:

self: super:

let
  callPackage = super.callPackage;
in {
  # ...
  my-script = callPackage ./pkgs/my-script { };
  # ...
}

And finally a file in /etc/nixos/overlays/pkgs/my-script/default.nix that looks like this:

{ stdenv, lib, pkgs, ... }:

let
  myScript = pkgs.writeTextFile {
    name = "my-script";
    executable = true;
    destination = "/bin/my-script.sh";
    text = ''
      # Script here...
    '';
  };
in stdenv.mkDerivation rec {
  pname = "my-script";
  version = "0.0.1";

  # Copy the script defined in the `let` statement above into the final
  # derivation.
  #
  buildInputs = [ myScript ];
  builder = pkgs.writeTextFile {
    name = "builder.sh";
    text = ''
      . $stdenv/setup
      mkdir -p $out/bin
      ln -sf ${myScript}/bin/my-script.sh $out/bin/my-script.sh
    '';
  };
}

With this setup, I can then add pkgs.my-script to environment.systemPackages, just like a normal Nix package.

One nice thing about these last two approaches is that you can reference packages in your scripts without needing to put them into the global environment. For example, if you want to use MozJPEG, you can reference its cjpeg binary as ${pkgs.mozjpeg}/bin/cjpeg, and Nix will pull in the package for your script without you having to do anything else. It’s really quite magical.

(As @jonringer mentions, for scripts just for your user, home-manager is probably a better solution. You can use the same patterns there, however. To create directories with home-manager, the easiest way I’ve found is to use a hidden, empty file. For example:

xdg.dataFile."foo/.keep".text = "";

This will create the directory ~/.local/share/foo. Check out home-manager's documentation for details, but the key attributes to search for are xdg.dataFile to create things in ~/.local/share, xdg.configFile for ~/.config, and home.file for a more generic approach. All three of these have semantics similar to pkgs.writeTextFile.)

As far as creating directories, check out systemd.tmpfiles.rules (despite its name, it’s about way more than just temporary files). man tmpfiles.d should get you started.

8 Likes

@jonringer Thanks a lot for your soon answer. However, I’d like to stick to pure NixOS and I’m a bit afraid of the home-manager words of warning, even if it seems very interesting – should I?

@necopinus Thanks a lot for your detailed answer.

  • in order to create folders, systemd.tmpfiles.rules was indeed marvelous! I used for instance – in case it might be useful to anyone:
systemd.tmpfiles.rules =
[
  "d /folder/to/create <chmod-value> <user> <group>"
] ;
  • in order to create scripts, I’ve found this NixOS wiki on nix scripts, and used pkgs.writeScriptBin, which is based on pkgs.writeTextFile but adds a little more. I used for instance – in case it might be useful to anyone:
{ pkgs, ... }:

let
  scriptName = pkgs.writeScriptBin "scriptName" ''
    #!${pkgs.stdenv.shell}
    …
  ''; 
in {
  environment.systemPackages = [ scriptName ];
}
4 Likes

The word of warning is more like a, “This works well for me and many others, but if it somehow borks your user-space, then don’t blame me”.

To me, he’s just being up front that this is a personal project, and that he’s not going to give any guarantees that it will work perfectly for everyone. If that seems like too much risk, then no one will force you to use it :slight_smile:

But it does solve a lot of your issues

Personally, I found that it was very upfront that it was going to, “create a ~/.gitconfig, but it already exists”. You also have the option to remove it if you find it too invasive.

@jonringer Thanks, I’ve read some on its configuration options and it’s tempting to use it – I’ll try to play with it, once I’m comfortable with NixOS’ basics :wink:!