Nix-Maid: systemd native dotfile management

Hello :wave:

Today I want to present my latest project: nix-maid. It started as a systemd-centric alternative to home-manager, as it only uses tmpfiles and regular user services (as opposed to activation hooks).

Please visit the page for more information, and let me know if you try it: https://viperml.github.io/nix-maid/ (GitHub)

This is an example standalone nix-maid configuration, that can be installed with nix-env -if ./my-config.nix && activate (I also provide a NixOS module and a Flake).

# my-config.nix
let
  sources = import ./npins;
  pkgs = import sources.nixpkgs;
  nix-maid = import sources.nix-maid;
in
nix-maid pkgs {
  # Add packages to install
  packages = [
    pkgs.yazi
    pkgs.bat
    pkgs.eza
  ];

  # Create files in your home directory
  file.home.".gitconfig".text = ''
    [user]
      name=Maid
  '';

  file.xdg_config."zellij/config.kdl".source = ./config.kdl;

  # `file` supports a mustache syntax, for dynamically resolving the value of {{home}}
  # This same configuration is portable between systems with different home dirs
  file.xdg_config."hypr/hyprland.conf".source = "{{home}}/dotfiles/hyprland.conf";

  # Define systemd-user services, like you would on NixOS
  systemd.services.waybar = {
    path = [ pkgs.waybar ];
    script = ''
      exec waybar
    '';
    wantedBy = [ "graphical-session.target" ];
  };

  # Configure gnome with dconf or gsettings
  gsettings.settings = {
    "org.gnome.mutter"."experimental-features" = [
      "scale-monitor-framebuffer" "xwayland-native-scaling"
    ];
  };

  dconf.settings = {
    "/org/gnome/desktop/interface/color-scheme" = "prefer-dark";
  };

  # Mustache syntax is also available in dynamicRules
  systemd.tmpfiles.dynamicRules = [
    "L {{xdg_config_dir}}/hypr/workspaces.conf - - - - {{home}}/dotfiles/workspaces.conf"
  ];
}
40 Likes

Why use Moustache when one already has access to Nix for (IMO) much stronger templating support?

2 Likes

I think it’s for allowing runtime resolution as opposed to eval/build time. I don’t think you could meet these two goals without doing runtime resolution.

🌐 Portable: Defers the value of your home directory, so the same configuration works for different users.
🚫 No Legacy: API redesigned from scratch, avoiding past mistakes like mkOutOfStoreSymlink.
1 Like

The mustache syntax is for values that are resolved at runtime, that’s a feature I wanted to use myself.

1 Like

OH, this is perfect !
Is exactly what i’m looking, i don’t really like Home Manager and this look a much simpler solution.
Thank You !

2 Likes

Interesting project!

The website says:

API redesigned from scratch, freeing us from past mistakes like mkOutOfStoreSymlink

What does this mean exactly? Can Nix-Maid create simple symlinks like ~/.foo -> ~/.dotfiles/foo without hackery (i.e. does it avoid intermediate symlinks through the Nix store etc.)?

HM breaks assumptions from NixOS, e.g. environment.etc."foo".source = "/bar" is fine on NixOS, but in HM it will try to cast the string into a path, which later fails when using flakes because of the eval sandbox. mkOutOfStoreSymlink exists to restore that functionality, and to me it’s a hack more than anything. For that reason, .source in nix-maid works the same as in NixOS.

The other topic is that nix-maid allows you to use a moustache syntax for runtime variables, so you can do either file.home.".foo".source = "/home/myuser/.dotfiles/foo or file.home.".foo".source = "{{home}}/.dotfiles/foo (both without mkOutOfStoreSymlink when using a string literal)

4 Likes