Systemd backend or "using NixOps to manage Ubuntu"

Overall question: Is this worth looking into? Not just for NixOps but also for users of Nix to manage services. One hacky approach is to mangle the activation scripts. That is described below.

I’ve been playing with an idea to create a backend for NixOps to manage non-NixOS machines. They would most likely be systemd-based to take advantage of the “expert system database” [<nixpkgs/nixos> is an expert system database]. My initial look was to just point NixOps at an Ubuntu machine and work through the errors one-by-one.

After setting up Ubuntu with:
bash <(curl --daemon
touch /etc/NIXOS
groupadd keys
ln -s /nix/var/nix/profiles/system /run/current-system

, we have to trick NixOps with:

  • boot.isContainer (seems to help)
  • filesystem.“/”.device = “/dev/sda1”;
  • deployment.targetHost = “”;
  • ensure proper SSH keys are everywhere

then NixOps happily uploaded all the proper closures. Then it moves on to activate and switch-to-configuration. This breaks everything.

Notable items:

  • restart systemd with a new version (which would break many things)
  • reload user units (causes warnings and potential unintended benefits)
  • mangle resolved (causes loss of name resolution until manually fixed)
  • dbus (something really bad happens)
  • etc ( overrides stuff that probably shouldn’t )
  • users and groups are created ( this changes all the users to point to /run/current-system versus /bin/bash)
  • other things i haven’t found yet
  • PATH on the host is not set

So we can use system.extraSystemBuilderCmds to add some surgical sed/awk in-place on $out/activate and $out/bin/switch-to-configuration to remove some of the steps in those scripts. Now we can create a service services.nginx.enable = true;… almost. We need four more items:

  • adjust .bashrc to have the correct paths so that SSH/NixOps still functions
  • add nginx user/group creation
  • cp the service file to the right place
  • tell the host systemctl to start
system.activationScripts = {
       nginx = {
         text = ''
           /usr/bin/rsync -av /nix/var/nix/profiles/system/etc/systemd/system/nginx.service /etc/systemd/system/nginx.service
           /bin/systemctl start nginx.service
         deps = [ ];

Now nginx starts! We can install packages! NixOps manages a portion of that machine now.

This was interesting and made me dig into sections of the ecosystem I haven’t really explored before (the activation scripts), so it was worth it for that. But would it make sense to have some alternate activation scripts that allowed someone to manage a non-NixOS systemd-based machine? Doing it properly (ie. safe user update/creation, /etc files) might not be too hard.


I think there’s at least one piece of this that would be quite beneficial: making our systemd configuration able to use the host system’s systemd. The main benefit of this is that it would allow tools like home-manager to reuse some of the nixos modules that configure systemd services, whereas currently it has to define its own versions of lots of things.

User services at the very least should work on many host systemds, since they mostly just install text files with pointers into the store for executables.

However, actually implementing this seems hard, and would require an intimate knowledge of how our systemd setup currently works.


@michaelpj yes, that’s exactly my thoughts (obviously my hack isn’t the final form of this, just proof it’s possible). Disnix already has a concept of adding activation scripts to services, we’d just have a different activation for different distributions (though I suspect most systemd-based ones will be nearly identical in implementation). We can get almost all the way there if we could express that we only want to perform “activation” for specific users and for specific “/etc” files. Maybe a few more config options allowing us to filter the affected users/files and a build.nix wrapper so people could just import/module it into home-manager/nixops/disnix/etc.

1 Like

I was able to reduce the hackery to the following. It only needs a modified ./switch-to-configuration with these sections commented out:

@@ -35,8 +35,8 @@
@@ -51,17 +51,17 @@
@@ -309,47 +309,47 @@
@@ -371,7 +371,7 @@
@@ -401,10 +401,10 @@
@@ -413,17 +413,17 @@

Remaining modifications are to forcibly clear out some elements of system.activationScripts.

The following can be used by NixOps after some minimal prep on the Ubuntu machine

  • Install Nix
  • .bashrc add to PATH /nix/var/nix/profiles/default/bin
  • .bashrc add to PATH /nix/var/nix/profiles/system/sw/bin
  • groupadd keys
  ubuntu = {config,pkgs,...}:{
    deployment.targetHost = "";
    deployment.hasFastConnection = true;
    imports = [ <nixpkgs/nixos/modules/profiles/minimal.nix> ];
    boot.isContainer = true;
    fileSystems."/".device = "/dev/sda1";
    environment.systemPackages = [ pkgs.vim pkgs.coreutils pkgs.nettools];
    services.nginx = {
      enable = true;
      virtualHosts = {
        testing = {
          listen = [
            {addr = ""; port = 80; ssl = false;}
          default = true;
          locations."/" = {
            root = "/srv";
    system.extraSystemBuilderCmds = ''
      substituteAll ${./switch-to-configuration} $out/bin/switch-to-configuration
      chmod +x $out/bin/switch-to-configuration
    system.activationScripts = {
      resolvconf = lib.mkForce "";
      users = lib.mkForce "";
      nginx = {
        text = ''
          /usr/sbin/useradd nginx || true
          /usr/sbin/groupadd nginx || true
          /usr/bin/rsync -av /nix/var/nix/profiles/system/etc/systemd/system/nginx.service /etc/systemd/system/nginx.service
          /bin/systemctl start nginx.service
        deps = [ ];