Systemd templates

Is there a way I can set a create a template for a service like this? Minecraft servers aren’t that different and the only differences arent that huge.

{ pkgs, ... }: {
  systemd.services.name = {
    enable = true;
    unitConfig = {
      Description = "Name's minecraft server";
      Wants = "network-online.target";
      After = "network-online.target";
    };

    serviceConfig = {
      User = "redhawk";
      WorkingDirectory = "/home/redhawk/minecraft/name/";
      ExecStart = "${pkgs.jre}/bin/java -jar server.jar";
    };

    wantedBy = [ "multi-user.target" ];
  };
}

If the thing changing is one string, you can use systemd template units (systemd: Template unit files - Fedora Magazine), although be warned that declaring them in NixOS requires a bit of tweaking to make them work as intended.

If you need something more complicated, you can do the templating in Nix by writing a custom function to create the unit defintion:

{ pkgs, ... }: 
let
  mkMinecraftServerService = {name, user}: {
    enable = true;
    unitConfig = {
      Description = "${name}'s minecraft server";
      Wants = "network-online.target";
      After = "network-online.target";
    };

    serviceConfig = {
      User = user;
      WorkingDirectory = "/home/${user}/minecraft/${name}/";
      ExecStart = "${pkgs.jre}/bin/java -jar server.jar";
    };

    wantedBy = [ "multi-user.target" ];
  };
in {
  systemd.services = {
    alice = mkMinecraftServerService { name = "alice"; user = "alice"; };
    bob = mkMinecraftServerService { name = "bob"; user = "bob"; };
    bobModded = mkMinectaftService { name = "moddedBob"; user = "bob"; };
  };
}
2 Likes

what would the systemd name of this process be? if possible i’d like them to all have something like minecraft-${user}-${name}

Each key in systemd.services creates a corresponding .service file in /etc/systemd/system.

Therefore, you’d want something like this:

systemd.services = {
    minecraft-alice-alice = mkMinecraftServerService { name = "alice"; user = "alice"; };
    minecraft-bob-bob = mkMinecraftServerService { name = "bob"; user = "bob"; };
    minecraft-bob-moddedBob = mkMinectaftService { name = "moddedBob"; user = "bob"; };
  }

It might be worthwhile to change the function to return the whole key/value pair instead of just the value:

mkMinecraftServerService = {name, user}: {
  "minecraft-${user}-${name}" = {
    enable = true;
    unitConfig = {
      Description = "${name}'s minecraft server";
      Wants = "network-online.target";
      After = "network-online.target";
    };

    serviceConfig = {
      User = user;
      WorkingDirectory = "/home/${user}/minecraft/${name}/";
      ExecStart = "${pkgs.jre}/bin/java -jar server.jar";
    };

    wantedBy = [ "multi-user.target" ];
  };
};

You then need to merge the attribute sets into systemd.services:

systemd.services = lib.attrsets.mergeAttrsList [
  (mkMinecraftService {name = "alice"; user = "alice";})
  (mkMinecraftService {name = "bob"; user = "bob";})
  (mkMinecraftService {name = "moddedBob"; user = "bob";})
];

Nix is so cool, thank you.

So, I’ve come up with something very close to what I want

{ lib, pkgs, ... }:
let
  Minecraft = { name }: {
    "minecraft-${name}" = {
      enable = true;
      unitConfig = {
        Description = "${name}'s minecraft server";
        Wants = "network-online.target";
        After = "network-online.target";
      };

      serviceConfig = {
        User = "minecraft";
        Group = "minecraft";
        StateDirectory = "minecraft/${name}";
        StateDirectoryMode = "0775";
        WorkingDirectory = "/var/lib/minecraft/${name}"; // <- problem
        ExecStart = "${pkgs.jre}/bin/java -jar server.jar";
      };

      wantedBy = [ "multi-user.target" ];
    };
  };
in {
  systemd.services =
    lib.attrsets.mergeAttrsList [ (Minecraft { name = "bob"; }) ];
}

However I can’t seem to set or access the statedirectory of the server, the only way i can fix this currently is set the workingdirectory.

based on what you have this is what i would use:

{ ... }:
{
  systemd.services."minecraft@" = {
    description = "%i's minecraft server";
    wantedBy = [ "multi-user.target" ];
    wants = [ "network-online.target" ];
    after [ "network-online.target" ];

    serviceConfig = {
      DynamicUser = true; # or set a user if wanted/needed
      StateDirectory = "minecraft/%i";
      StateDirectoryMode = "0775";
      WorkingDirectory = "%S/minecraft/%i";
      ExecStart = "${pkgs.jre}/bin/java -jar server.jar";
    };
  };
}

then you can spin them up dynamically via systemctl start minecarft@timmy.service, or have them start automatically via some syntax that is escaping me at the moment :thinking:

Where do the %'s come from? is this something I haven’t seen in the systemd docs?

Yeah, they’re specifiers.

1 Like