How to use sudo in a systemd service script?

I am trying to set up a backup systemd service that dumps my postgresql database. I looks roughly like this:

  systemd.services."backup-daily" = {
    script = ''
      # Other stuff ...

      # Postgres
      ${pkgs.coreutils}/bin/mkdir -p "''${BACKUP_DIR}/postgres"
      sudo -u postgres ${pkgs.postgresql}/bin/pg_dumpall | ${pkgs.coreutils}/bin/split -b 2G --filter='gzip > $FILE.gz' - "''${BACKUP_DIR}/postgres/pg_dumpall.sql"

      # More other stuff ...
    '';
    serviceConfig = {
      Type = "oneshot";
      User = "root";
    };
  };

However, while it was easy to find which packages the usual commands like mkdir come from, I am unable to figure out how to make sudo available to my systemd service. I tried the following:

$ which mkdir | xargs file
/run/current-system/sw/bin/mkdir: symbolic link to /nix/store/3rkmqbpa9x1cq16i7yz1rjl02z6i6p61-coreutils-full-9.5/bin/mkdir
$ which sudo | xargs file
/run/wrappers/bin/sudo: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

Which as you can see gives me a path to the nix store for mkdir, but not for sudo. How can I make sudo available in my systemd service? Is there another way to run a command under a different user within a systemd script?

You don’t. sudo is for interactive use. Doing anything else with it is either incredibly cumbersome or rips security holes into your system. NixOS makes this rather hard anyway due to how setuid is handled.

Assign the user to your service that needs to do the actual business logic - in this case, that looks like postgres. If you need to execute part of it on files not owned by this user, assign proper groups so all users who need access have it, or where you need elevated privileges use the + prefix (note you need to use serviceConfig.ExecStartPre and co. to use that, since the NixOS module turns all this into a bash script which is called instead).

Sometimes your task may also be more nicely represented by a set of inter-dependent services, which can then have more specific permissions set, based on what they actually do. You’d for example use systemd.tmpfiles to ensure a directory exists.

3 Likes

That’s a good point. I think the solution of having a chain of systemd services with different permissions would be the best.

Since my main backup service creates the backup directory, I would like the postgres backup to run every time the main backup service has completed successfully. Is there an easy way to do this with systemd in NixOS?

Nevermind, I figured it out myself:

  systemd.services."first" = {
    script = ''
      # Do stuff
    '';
    serviceConfig = {
      Type = "oneshot";
      User = "root";
    };
    unitConfig = {
      OnSuccess = "second.service";
    };
  };

  systemd.services."second" = {
    script = ''
      # Do stuff
    '';
    serviceConfig = {
      Type = "oneshot";
      User = "root";
    };
  };

And this can be extended indefinitely.

1 Like