How to start a daemon properly in NixOS?

I am using Fusuma for controlling swipe on touchpad. As NixOS doesn’t provide a configuration for it, I currently handle its initialization from services.xserver.displayManager.sessionCommands.

As I am a newbie to NixOS, I wonder if this is correct. Is there a better option?

2 Likes

proper thing would probably create an actual systemd service, or create a module, then set it true.

Something like

{ pkgs, lib, config, ... }:

with lib;

let
  cfg = config.services.fusuma;
in {
  options.services.fusuma = {
    enable = mkEnableOption "Enable fusuma service";

    package = mkOption {
      type = types.package;
      default = pkgs.fusuma;
      defaultText = "pkgs.fusuma";
      description = "Set version of fusuma package to use.";
    };
  };

  config = mkIf cfg.enable {
    environment.systemPackages = [ cfg.package ]; # if user should have the command available as well
    services.dbus.packages = [ cfg.package ]; # if the package has dbus related configuration

    systemd.services.fusuma = {
      description = "Fusuma server daemon.";

      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ]; # if networking is needed

      restartIfChanged = true; # set to false, if restarting is problematic

      serviceConfig = {
        DynamicUser = true;
        ExecStart = "${cfg.package}/bin/fusuma";
        Restart = "always";
      };
    };
  };

  meta.maintainers = with lib.maintainers; [  ];
}
6 Likes

This is really awesome, thank you! I will look into it.

I guess I can use these code by simply import back into configuration.nix. If I want to make a PR and contribute to github, is there anymore thing that I need to handle?

1 Like

For someone who have the same issue, I have found a great tutorial from nixos on this matter: NixOS:extend NixOS - NixOS Wiki

4 Likes

After some research and using your code as a reference, I am able to come up with this:

{ pkgs, lib, config, ... }:

with lib;

let cfg = config.services.fusuma;
in {
  options.services.fusuma = {
    enable = mkEnableOption "Enable fusuma service";

    package = mkOption {
      type = types.package;
      default = pkgs.fusuma;
      defaultText = "pkgs.fusuma";
      description = "Set the version of fusuma";
    };

    configFile = mkOption {
      type = types.str;
      description = "Path for fusuma's configuration file";
      default = "~/.config/fusuma/config.yaml";
    };
  };

  config = mkIf cfg.enable {
    systemd.services.fusuma = {
      description = "Start fusuma to handle swipe";
      wantedBy = [ "multi-user.target" ];
      after = [ "graphical-session.target" ];

      restartIfChanged = true;

      serviceConfig = {
        Restart = "always";
        DynamicUser = true;
        ExecStart = ''sudo ${cfg.package}/bin/fusuma --config=${cfg.configFile}'';
      };
    };
  };

  meta.maintainers = with lib.maintainers; [ ];
}

But how can I use sudo with ExecStart? Without sudo, fusuma is unable to track the touchpad. But right now as I have added sudo onto it, the process doesn’t start. If taking the sudo out, I will be able to ps aux | grep fusuma.

1 Like

sudo executes the command as the root user. If you want to run a service as the root user, you should start it as the root user in the first place :slight_smile:

Normally system-wide systemd services are run as root, but the option DynamicUser turns off running services as root. So simply remove that line from your service to do that.

2 Likes

I looked at fusuma more closely; it expects to be run as the user who is using the X instance (i.e., whoever logged in).

This means it shouldn’t work as root or a DynamicUser.

Instead, it would be best to run it with systemd.user.services, which are executed as the user who starts the unit.

You may then also need to set systemd.user.units..wantedBy to graphical.target, so that it is run after the X server is started and available for fusuma to pick up.

I have personally not yet used systemd.user.units, so take this with a grain of salt and expect to need to experiment. If that option can’t do it you may need to write a module for GitHub - nix-community/home-manager: Manage a user environment using Nix [maintainer=@rycee] instead, as it specializes in these applications/services.

Sadly these kinds of of services (need to be run as the user and execute in their graphical environment) are the hardest to run correctly.

2 Likes

Yes I was wondering how can I make it only start after X server started as well. Thank you for that one.

It is really weird, as I tried to debug by running the command manually, I got the following error:

❯ /nix/store/81gmv3nnn6j50rwn5frdz5v8qlva5b9k-fusuma-1.3.0/bin/.fusuma-wrapped --config=/home/hugosum/.config/fusuma/config.yaml

I, [2021-07-12T21:40:09.767415 #4427]  INFO -- : reload config : /nix/store/krdk46sfsxfj6z679xr7ilbgxi0xp3di-ruby2.7.4-fusuma-1.3.0/lib/ruby/gems/2.7.0/gems/fusuma-1.3.0/lib/fusuma/config.yml
I, [2021-07-12T21:40:09.767571 #4427]  INFO -- : reload config : /home/hugosum/.config/fusuma/config.yaml
I, [2021-07-12T21:40:09.781336 #4427]  INFO -- : ---------------------------------------------
I, [2021-07-12T21:40:09.781369 #4427]  INFO -- : Fusuma: 1.3.0
E, [2021-07-12T21:40:09.781458 #4427] ERROR -- : install libinput-tools

But if I run fusuma in the nix-shell with the same flag, it is running correctly (so the package is bundled correctly???)

~
❮ nix-shell -p fusuma

[nix-shell:~]$ fusuma --config=/home/hugosum/.config/fusuma/config.yaml
I, [2021-07-12T21:44:16.143536 #5014]  INFO -- : reload config : /nix/store/krdk46sfsxfj6z679xr7ilbgxi0xp3di-ruby2.7.4-fusuma-1.3.0/lib/ruby/gems/2.7.0/gems/fusuma-1.3.0/lib/fusuma/config.yml
I, [2021-07-12T21:44:16.143718 #5014]  INFO -- : reload config : /home/hugosum/.config/fusuma/config.yaml
I, [2021-07-12T21:44:16.156867 #5014]  INFO -- : ---------------------------------------------
I, [2021-07-12T21:44:16.156900 #5014]  INFO -- : Fusuma: 1.3.0
I, [2021-07-12T21:44:16.158616 #5014]  INFO -- : libinput: 1.16.4
I, [2021-07-12T21:44:16.160462 #5014]  INFO -- : OS: Linux 5.10.48 #1-NixOS SMP Wed Jul 7 12:27:50 UTC 2021
I, [2021-07-12T21:44:16.161440 #5014]  INFO -- : Distribution:
<<< Welcome to NixOS 21.05.1408.9376bf7b342 (\m) - \l >>>

Run 'nixos-help' for the NixOS manual.
I, [2021-07-12T21:44:16.164673 #5014]  INFO -- : Desktop session: none+leftwm
I, [2021-07-12T21:44:16.164759 #5014]  INFO -- : ---------------------------------------------
I, [2021-07-12T21:44:16.164791 #5014]  INFO -- : ---------------------------------------------
I, [2021-07-12T21:44:16.164819 #5014]  INFO -- : Enabled Plugins:
I, [2021-07-12T21:44:16.165124 #5014]  INFO -- :   Fusuma::Plugin::Buffers::GestureBuffer
I, [2021-07-12T21:44:16.165220 #5014]  INFO -- :   Fusuma::Plugin::Detectors::PinchDetector
I, [2021-07-12T21:44:16.165268 #5014]  INFO -- :   Fusuma::Plugin::Detectors::RotateDetector
I, [2021-07-12T21:44:16.165313 #5014]  INFO -- :   Fusuma::Plugin::Detectors::SwipeDetector
I, [2021-07-12T21:44:16.165351 #5014]  INFO -- :   Fusuma::Plugin::Events::Records::GestureRecord
I, [2021-07-12T21:44:16.165461 #5014]  INFO -- :   Fusuma::Plugin::Events::Records::IndexRecord
I, [2021-07-12T21:44:16.165526 #5014]  INFO -- :   Fusuma::Plugin::Events::Records::TextRecord
I, [2021-07-12T21:44:16.165571 #5014]  INFO -- :   Fusuma::Plugin::Executors::CommandExecutor
I, [2021-07-12T21:44:16.165616 #5014]  INFO -- :   Fusuma::Plugin::Filters::LibinputDeviceFilter
I, [2021-07-12T21:44:16.165671 #5014]  INFO -- :   Fusuma::Plugin::Inputs::LibinputCommandInput
I, [2021-07-12T21:44:16.165733 #5014]  INFO -- :   Fusuma::Plugin::Parsers::LibinputGestureParser
I, [2021-07-12T21:44:16.165821 #5014]  INFO -- : ---------------------------------------------

Indeed! Welcome to NixOS :slight_smile:

Nix will transparently set up LD_LIBRARY_PATH (and similar for other languages, e.g. PYTHONPATH) to point your binaries at the version of the library they need to run with.

This enables running multiple versions of the same libraries without patching the binary, as well as lots of other nice things, like ensuring that package builds don’t take in libraries they shouldn’t (though this is handled by containers now aiui), and general clean isolation between packages. Where the binary does need patching, nix packages will usually actually do so to ensure reproducibility.

The binaries in /nix/store shouldn’t generally be expected to run, even if they often do. I think this will generally fail for scripting languages like ruby, at least if they depend on system libraries.

Hence, the best way to debug is nix-shell, or maybe running the systemd service directly.

2 Likes

Thank you @TLATER, I didn’t know about LD_LIBRARY_PATH before you mention it, and I am always curious what magic has nixOS made.

Digging deeper this morning, and I found out that there are actually two binaries of fusuma in that directory, and ./fusuma is working correctly

/nix/store/81gmv3nnn6j50rwn5frdz5v8qlva5b9k-fusuma-1.3.0/bin
❯ ls -a
.  ..  bundle  bundler  fusuma  .fusuma-wrapped

I think I have stated clearly which binary to use in the service declaration, but it uses .fusuma-wrapped instead, which causes the error.

ExecStart = ''sudo ${cfg.package}/bin/fusuma --config=${cfg.configFile}'';

Output of .fusuma-wrapped:

❮ ps aux | grep fusuma
root         596  0.3  0.2 437808 21904 ?        Ssl  06:36   0:08 /nix/store/1bdsj0wzyrlqyk5dv6r63mm0n3fd0v5b-ruby-2.7.4/bin/ruby /nix/store/81gmv3nnn6j50rwn5frdz5v8qlva5b9k-fusuma-1.3.0/bin/.fusuma-wrapped --config=/home/hugosum/.config/fusuma/config.yaml

How is that possible?

The fusuma file is a script that sets up some variables and then runs .fusuma-wrapped. ps lists the running processes, and since the script uses exec it will no longer be running.

Hence you see the wrapped version.

What is the error? Are you looking at journalctl's output or assuming that your manual run produces the same result as the unit?

For reference, to see the logs of a systemd service, you would run: journalctl -xe --unit <service name>. I think the name would be fusuma.service here. Add a --user after -xe if you’re working on a user unit.

I created the following home-manager module to manage fusuma:

{ config, lib, pkgs, ... }:

with lib;
let
  cfg = config.services.fusuma;
  yaml = pkgs.formats.yaml { };
in
{
  options.services.fusuma = {
    enable = mkEnableOption "Enable fusuma service";

    package = mkOption {
      type = types.package;
      default = pkgs.fusuma;
      defaultText = literalExample "pkgs.fusuma";
      description = "The fusuma package to install.";
    };

    settings = mkOption {
      type = yaml.type;
      default = { };
      example = literalExample ''
        {
          swipe = {
            "3" = {
              left.command = "xdotool key shift+l";
              right.command = "xdotool key shift+h";
            };
          };
        }
      '';
      description = ''
        Configuration written to
        <filename>~/.config/fusuma/config.yml</filename>.
      '';
    };
  };

  config = mkIf cfg.enable {
    home.packages = [ cfg.package ];

    xdg.configFile = {
      "fusuma/config.yml".source =
        yaml.generate "config.yml" cfg.settings;
    };

    systemd.user.services.fusuma = {
      Unit = {
        Description = "Fusuma multitouch gesture recognizer";
      };

      Service = {
        ExecStart = "${cfg.package}/bin/fusuma";
      };

      Install = {
        WantedBy = [ "graphical.target" ];
      };
    };
  };

  meta.maintainers = with lib.maintainers; [ ];
}

but unfortunately it doesn’t work. After a home-manager switch I get

...
The user systemd session is degraded:
  UNIT           LOAD   ACTIVE SUB    DESCRIPTION
● fusuma.service loaded failed failed Fusuma multitouch gesture recognizer

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type.
1 loaded units listed.
Attempting to reload services anyway...

and systemctl --user status fusuma.service returns

● fusuma.service - Fusuma multitouch gesture recognizer
     Loaded: loaded (/nix/store/rpgqxlnj467mmj62g2kj8gby59d5zkac-home-manager-files/.config/systemd/user/fusuma.service; enabled; vendor preset: enabled)
     Active: failed (Result: exit-code) since Sat 2021-07-17 15:17:23 CEST; 10min ago
   Main PID: 16649 (code=exited, status=1/FAILURE)
        CPU: 133ms

Jul 17 15:17:23 blade fusuma[16649]: I, [2021-07-17T15:17:23.934614 #16649]  INFO -- : libinput: 1.16.4
Jul 17 15:17:23 blade fusuma[16649]: /nix/store/wmpvdlhvsw5bbfar933kz1l6gj86iq6g-ruby2.7.3-fusuma-1.3.0/lib/ruby/gems/2.7.0/gems/fusuma-1.3.0/lib/fusuma.rb:50:in ``': No such file or directory - uname (Errno::ENOENT)
Jul 17 15:17:23 blade fusuma[16649]:         from /nix/store/wmpvdlhvsw5bbfar933kz1l6gj86iq6g-ruby2.7.3-fusuma-1.3.0/lib/ruby/gems/2.7.0/gems/fusuma-1.3.0/lib/fusuma.rb:50:in `print_version'
Jul 17 15:17:23 blade fusuma[16649]:         from /nix/store/wmpvdlhvsw5bbfar933kz1l6gj86iq6g-ruby2.7.3-fusuma-1.3.0/lib/ruby/gems/2.7.0/gems/fusuma-1.3.0/lib/fusuma.rb:36:in `read_options'
Jul 17 15:17:23 blade fusuma[16649]:         from /nix/store/wmpvdlhvsw5bbfar933kz1l6gj86iq6g-ruby2.7.3-fusuma-1.3.0/lib/ruby/gems/2.7.0/gems/fusuma-1.3.0/lib/fusuma.rb:16:in `run'
Jul 17 15:17:23 blade fusuma[16649]:         from /nix/store/viq59dff95j7zzs6kxh6zx1mdbh1lkk1-fusuma-1.3.0/lib/ruby/gems/2.7.0/gems/fusuma-1.3.0/exe/fusuma:41:in `<top (required)>'
Jul 17 15:17:23 blade fusuma[16649]:         from /nix/store/viq59dff95j7zzs6kxh6zx1mdbh1lkk1-fusuma-1.3.0/bin/.fusuma-wrapped:20:in `load'
Jul 17 15:17:23 blade fusuma[16649]:         from /nix/store/viq59dff95j7zzs6kxh6zx1mdbh1lkk1-fusuma-1.3.0/bin/.fusuma-wrapped:20:in `<main>'
Jul 17 15:17:23 blade systemd[1591]: fusuma.service: Main process exited, code=exited, status=1/FAILURE
Jul 17 15:17:23 blade systemd[1591]: fusuma.service: Failed with result 'exit-code'.
1 Like

Hm I haven’t made that far, and I haven’t seen that error.

Have you made it work now?

For what it’s worth, to resolve that error you might need to add a package containing uname to the PATH variable for that unit; this seems suspicious though.

I don’t know enough about fusuma or how it’s packaged, this may just be a misconfigured home-manager.

@noib3 I am able to make it work like this. For me it has to be run by the root in order to get the input from touchpad. This is not optimize for home-manager’s format tho.

  systemd.services = {
    # fusuma
    fusuma = {
      description = "Start fusuma to handle swipe";
      wantedBy = [ "graphical.target" ];
      after = [ "graphical-session.target" ];
      restartIfChanged = true;

      serviceConfig = {
        User = "root";
        Group = "root";
        Restart = "always";
        ExecStart = "${pkgs.fusuma}/bin/fusuma -c ${./fusuma/config.yaml}";
      };
    };
  };

But still, I cannot make it work with xdotool, not sure why. Which still I can make xdotool work with simply sudo fusuma -c ./config.yaml.

Would it be possible that fusuma needs to be run by root, yet xdotool needs to be run by the current user?

See their readme: GitHub - iberianpig/fusuma: Multitouch gestures with libinput driver on Linux

IMPORTANT : You MUST be a member of the INPUT group to read touchpad by Fusuma.

i.e., don’t run this as root. Add your user to the input group instead (and then xdotool should likely work fine).

1 Like

Thank you! Not sure how did I missed that. Just tried the input group, and now fusuma runs ok without sudo. However xdotool is still not working. Going to dig deeper into it later.

Make sure the DISPLAY variable is set in the shell that xdotool runs in :slight_smile:

Hm I have switched the service from a system service to user service, which based on arch linux wiki should have injected DISPLAY and XAUTHORITY. Still xdotool is not working yet.

Do I need to make sure the service has these variables, or the shell used by fusuma, or both?

    services.fusuma = {
      description = "Start fusuma to handle swipe";
      wantedBy = [ "default.target" ];
      after = [ "graphical-session.target" ];
      restartIfChanged = false;

      serviceConfig = {
        Restart = "on-failure";
        ExecStart = "${pkgs.fusuma}/bin/fusuma -c ${./fusuma/config.yaml}";
      };
    };