Systemd.timer "cannot coerce a set to a string"

I’m trying to transition from fcron to systemd.timer and am using the article in the Nixos wiki.

Adapting the article from the wiki, i have:

  systemd.timers."@daily" = {
    wantedBy = [ "timers.target" ];
    timerConfig = {
      OnCalendar = "daily";
      Persistent = "true";
      Unit = "@daily.service";
    };
  };

  systemd.services."@daily" = {
    script = ''
      ${pkgs}/bin/cp "-u -r /home/sperber/.doom.d/ /home/sperber/Dokumente/Install/Linux/"
      ${pkgs}/bin/rsync "-r -t -p --delete -s --exclude hardware-configuration.nix /etc/nixos/ /home/sperber/Dokumente/Install/Linux/Nixos"
      ${pkgs}/bin/rsnapshot "-c /home/sperber/Dokumente/Install/Linux/rsnapshot.conf daily"
      ${pkgs}/bin/duply "manitu backup+purgeAuto --force"

      # @reboot cp -u -r /srv/ /home/sperber/Dokumente/Programmieren/
      # composer global update
      # npm -g update
    '';
    serviceConfig = {
      Type = "oneshot";
      User = "sperber";
    };
  };

  systemd.timers."@weekly" = {
    wantedBy = [ "timers.target" ];
    timerConfig = {
      OnCalendar = "weekly";
      Persistent = "true";
      Unit = "@weekly.service";
    };
  };

  systemd.services."@weekly" = {
    script = ''
      ${pkgs}/bin/rsnapshot "-c /home/sperber/Dokumente/Install/Linux/rsnapshot.conf weekly"
      ${pkgs}/bin/curl "https://block.energized.pro/ultimate/formats/unbound.conf -o /etc/unbound/ultimate.conf";
    '';
    serviceConfig = {
      Type = "oneshot";
      User = "sperber";
    };
  };

Running nixos-rebuild switch --upgrade yields

~ ❯ nupgrade                                                                                                                         16s
unpacking channels...
building Nix...
building the system configuration...
error: cannot coerce a set to a string

       at /etc/nixos/configuration.nix:97:7:

           96|     script = ''
           97|       ${pkgs}/bin/cp "-u -r /home/sperber/.doom.d/ /home/sperber/Dokumente/Install/Linux/"
             |       ^
           98|       ${pkgs}/bin/rsync "-r -t -p --delete -s --exclude hardware-configuration.nix /etc/nixos/ /home/sperber/Dokumente/Install/Linux/Nixos"
(use '--show-trace' to show detailed location information)

I understand that something is wrong in my configuration.nix but i don’t understand what it is (I’m still struggling with nix).

Can someone please tell me how to fix it and if the timer itself is correct? I’m anxious that the packages are not in PATH and that the timers won’t work because of this. My definitions for firejail use ${pkgs.lib.getBin pkgs.thunderbird}/bin/thunderbird, but the wiki article for systemd.timer uses ${pkgs.coreutils}/bin/echo. Which format is the correct one?

pkgs is a set of packages. You need to specify what you need from within.

pkgs.coreutils for cp, pkgs.rsync for rsync, etc.

1 Like

Try changing those lines to something like this:

${pkgs.coreutils}/bin/cp ...

You can find out which package a command lives in by running something like

readline $(which cp)
1 Like

Thank you both for your help!

Why does the firejail definition use a different format in adressing the packages? Does it yield a different output or is it redundant? e.g. ${pkgs.lib.getBin pkgs.thunderbird}/bin/thunderbird

getBin is a convinience function, that takes a derivation, which may be either multi output or single output, and returns the output that by convention would contain the executable or more exact the bin folder.

This means, for a single output derivation it unconditionally returns the derivation itself.

For multi-output derivations it returns the bin output if it exists, the default output otherwise.

It is not guaranteed that the returned output would indeed have a bin folder after being built. It just means, that there should be one if following the usual conventions.

1 Like

In case that confuses you, multi-output derivations are explained here: Nixpkgs 22.11 manual

Not very complicated, in a nutshell using getBin just allows you to save some disk space if you’re working with a package that is prepared correctly.

1 Like

@R-VdP I have a follow up question on that please:

I don’t seem to be able to run numlockx from a systemd.service although i can run it from the terminal just fine. Running the command you suggested yields:

~ ❯ echo $(which numlockx)                                                                                                    
/run/current-system/sw/bin/numlockx

I want the command numlockx on to be run after boot and after wakeup from sleep so i defined the following:

  systemd.timers."numlockx" = {
    wantedBy = [ "timers.target" ];
    timerConfig = {
      OnStartupSec = "1s";
      # OnUnitActiveSec = "30s";
      OnUnitInactiveSec = "1s";
      # RestartSec = "5s";
      AccuracySec = "1s";
      Unit = "numlockx.service";
    };
  };

  systemd.services."numlockx" = {
    script = ''
      numlockx on
      /run/current-system/sw/bin/numlockx on
      ${pkgs.numlockx}/bin/numlockx on
    '';
    serviceConfig = {
      Type = "oneshot"; # "simple" für Prozesse, die weiterlaufen sollen
      User = "sperber";
    };
  };

but it says

~ ❯ systemctl status numlockx
× numlockx.service
     Loaded: loaded (/etc/systemd/system/numlockx.service; linked; preset: enabled)
     Active: failed (Result: exit-code) since Sun 2023-01-15 02:52:13 CET; 544ms ago
TriggeredBy: ● numlockx.timer
    Process: 43089 ExecStart=/nix/store/193b1nikj1xwlmlwz8z9zim17j4d4zar-unit-script-numlockx-start/bin/numlockx-start (code=exited, st>
   Main PID: 43089 (code=exited, status=127)
         IP: 0B in, 0B out
        CPU: 5ms

Jan 15 02:52:13 nixos systemd[1]: Starting numlockx.service...
Jan 15 02:52:13 nixos numlockx-start[43090]: /nix/store/193b1nikj1xwlmlwz8z9zim17j4d4zar-unit-script-numlockx-start/bin/numlockx-start:>
Jan 15 02:52:13 nixos systemd[1]: numlockx.service: Main process exited, code=exited, status=127/n/a
Jan 15 02:52:13 nixos systemd[1]: numlockx.service: Failed with result 'exit-code'.
Jan 15 02:52:13 nixos systemd[1]: Failed to start numlockx.service.

I don’t understand it. In the service i addressed numlockx in all ways that i know of. I started by adressing it by one method at a time but nothing seemed to work.

Can you please tell me why it doesn’t work?

Hi @Father2303

TLDR: remove the first two lines in the string that you assign to script, and only keep the third one:

systemd.services.numlockx = {
  script = ''
    ${pkgs.numlockx}/bin/numlockx
  '';
  <other config>
};

Longer explanation:

Well, you have put 3 commands in the script for your service, and:

  1. The first command, simply numlockx, will not work, because the command numlockx will not be found when searching the directories in $PATH from the systemd service. You have the command available in your shell, but systemd services run in an environment that is a lot more restricted, and one of the things is that $PATH contains very little. You could use the path option to add it to your service’s $PATH variable, but in this case I would suggest to use the third option below instead.
  2. The second one, calling /run/current-system/sw/bin/numlockx, would work, provided that numlockx is available on your system $PATH, which seems to be the case for you. However, depending on this fact makes the service very brittle, as it would fail again if in the future you removed numlockx from the system path. So it is rather accidental that this works in your current setup and is not the recommended way of doing thing.
  3. The third option, calling ${pkgs.numlockx}/bin/numlockx is the correct and recommended way of doing things. It will always evaluate to the correct store path for the numlockx command and nix will know that your service depends on this command and therefore will make sure that it is always available in the nix store.

So you actually have the correct way of calling numlockx already in your script. However, the actual shell script that is generated by the script option in the systemd NixOS module, starts with set -e, which tells bash to immediately terminate the script in case of an error, and so when the first line throws an error because numlockx is not on the path, the whole script will crash and the second and third commands will never be executed.

BTW, you can inspect the generated script by opening /etc/systemd/system/numlockx and then opening the file that ExecStart points at.

I also made a mistake in my previous post, it should not be readline, but realpath, I think I hadn’t had my coffee yet. So the correct way to figure out the package, would be realpath $(which numlockx), from the output you can then see the package name, so in this case numlockx, and you can see that the actual script sits in bin/numlockx, and so then you use ${pkgs.numlockx}/bin/numlockx.

Hope this helps.

@R-VdP Thank you for your answer. Sorry that i phrased my question ambiguously, i already had this:

  systemd.timers."numlockx" = {
    wantedBy = [ "timers.target" ];
    timerConfig = {
      OnStartupSec = "1s";
      # OnUnitActiveSec = "30s";
      OnUnitInactiveSec = "1s";
      # RestartSec = "5s";
      AccuracySec = "1s";
      Unit = "numlockx.service";
    };
  };

  systemd.services."numlockx" = {
    script = ''
      ${pkgs.numlockx}/bin/numlockx on
    '';
    serviceConfig = {
      Type = "oneshot"; # "simple" für Prozesse, die weiterlaufen sollen
      User = "sperber";
    };
  };

but this isn’t working, that’s why i tried the two other methods.

Building fails with:

× numlockx.service
     Loaded: loaded (/etc/systemd/system/numlockx.service; linked; preset: enabled)
     Active: failed (Result: exit-code) since Mon 2023-01-16 04:01:10 CET; 42ms ago
TriggeredBy: ● numlockx.timer
    Process: 12540 ExecStart=/nix/store/bz75nqvpnzk5lhjv10ndhhnicigby8b7-unit-script-numlockx-start/bin/numlockx-start (code=exited, status=1/FAILURE)
   Main PID: 12540 (code=exited, status=1/FAILURE)
         IP: 0B in, 0B out
        CPU: 2ms

Jan 16 04:01:10 nixos systemd[1]: Starting numlockx.service...
Jan 16 04:01:10 nixos numlockx-start[12541]: Error opening display!
Jan 16 04:01:10 nixos systemd[1]: numlockx.service: Main process exited, code=exited, status=1/FAILURE
Jan 16 04:01:10 nixos systemd[1]: numlockx.service: Failed with result 'exit-code'.
Jan 16 04:01:10 nixos systemd[1]: Failed to start numlockx.service.
warning: error(s) occurred while switching to the new configuration

I don’t understand it, as the other services work and i can’t find a typo or something. I’m also wondering about Error opening display!, what’s the meaning of this?

I can run numlockx on in the terminal just fine and have it on autostart in my config for awesome but after i put my PC to sleep, it wakes up with numlock disabled and that’s what i would like to change.

Hmm, at this point you’re having an issue with how systemd services work, this is not really specific to NixOS anymore.

I think you are defining a system service here, and so it has no concept of your user session. I don’t know how numlockx works, ideally it would talk to DBus to figure out what’s your user session and then to find the display and such, but for that to work I think the systemd service will need to run as a user service and not as a system service.
If it relies on environment variables instead of using DBus, then you’ll also need to make sure that the required variables are correctly set in the service’s environment. By default most environment variables are not propagated to the service’s environment.

You can configure user services with NixOS as well, or with home-manager if you use that, it’s almost the same as for system services.

I have no experience with numlockx though, so I don’t think I can help much more than that.

This is the correct version, thank you for your help:

  systemd.user.timers."numlockx" = {
    wantedBy = [
      "timers.target"
      "suspend.target"
      "hibernate.target"
      "hybrid-sleep.target"
      "suspend-then-hibernate.target"
    ];
    after = [
      "timers.target"
      "suspend.target"
      "hibernate.target"
      "hybrid-sleep.target"
      "suspend-then-hibernate.target"
    ];
    timerConfig = {
      OnStartupSec = "1s";
      OnUnitInactiveSec = "1s";
      AccuracySec = "1s";
      Unit = "numlockx.service";
    };
  };

  systemd.user.services."numlockx" = {
    script = ''
      ${pkgs.numlockx}/bin/numlockx on
    '';
    serviceConfig = {
      Type = "oneshot"; # "simple" für Prozesse, die weiterlaufen sollen
    };
  };