summary refs log tree commit diff
path: root/nixos/modules/services/games/minetest-server.nix
blob: 8dc360153497309af55f9c2a374b56a7da9f0782 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
{ config, lib, pkgs, ... }:

with lib;

let
  CONTAINS_NEWLINE_RE = ".*\n.*";
  # The following values are reserved as complete option values:
  # { - start of a group.
  # """ - start of a multi-line string.
  RESERVED_VALUE_RE = "[[:space:]]*(\"\"\"|\\{)[[:space:]]*";
  NEEDS_MULTILINE_RE = "${CONTAINS_NEWLINE_RE}|${RESERVED_VALUE_RE}";

  # There is no way to encode """ on its own line in a Minetest config.
  UNESCAPABLE_RE = ".*\n\"\"\"\n.*";

  toConfMultiline = name: value:
    assert lib.assertMsg
      ((builtins.match UNESCAPABLE_RE value) == null)
      ''""" can't be on its own line in a minetest config.'';
    "${name} = \"\"\"\n${value}\n\"\"\"\n";

  toConf = values:
    lib.concatStrings
      (lib.mapAttrsToList
        (name: value: {
          bool = "${name} = ${toString value}\n";
          int = "${name} = ${toString value}\n";
          null = "";
          set = "${name} = {\n${toConf value}}\n";
          string =
            if (builtins.match NEEDS_MULTILINE_RE value) != null
            then toConfMultiline name value
            else "${name} = ${value}\n";
        }.${builtins.typeOf value})
        values);

  cfg   = config.services.minetest-server;
  flag  = val: name: lib.optionals (val != null) ["--${name}" "${toString val}"];

  flags = [
    "--server"
  ]
    ++ (
      if cfg.configPath != null
      then ["--config" cfg.configPath]
      else ["--config" (builtins.toFile "minetest.conf" (toConf cfg.config))])
    ++ (flag cfg.gameId "gameid")
    ++ (flag cfg.world "world")
    ++ (flag cfg.logPath "logfile")
    ++ (flag cfg.port "port")
    ++ cfg.extraArgs;
in
{
  options = {
    services.minetest-server = {
      enable = mkOption {
        type        = types.bool;
        default     = false;
        description = lib.mdDoc "If enabled, starts a Minetest Server.";
      };

      gameId = mkOption {
        type        = types.nullOr types.str;
        default     = null;
        description = lib.mdDoc ''
          Id of the game to use. To list available games run
          `minetestserver --gameid list`.

          If only one game exists, this option can be null.
        '';
      };

      world = mkOption {
        type        = types.nullOr types.path;
        default     = null;
        description = lib.mdDoc ''
          Name of the world to use. To list available worlds run
          `minetestserver --world list`.

          If only one world exists, this option can be null.
        '';
      };

      configPath = mkOption {
        type        = types.nullOr types.path;
        default     = null;
        description = lib.mdDoc ''
          Path to the config to use.

          If set to null, the config of the running user will be used:
          `~/.minetest/minetest.conf`.
        '';
      };

      config = mkOption {
        type = types.attrsOf types.anything;
        default = {};
        description = lib.mdDoc ''
          Settings to add to the minetest config file.

          This option is ignored if `configPath` is set.
        '';
      };

      logPath = mkOption {
        type        = types.nullOr types.path;
        default     = null;
        description = lib.mdDoc ''
          Path to logfile for logging.

          If set to null, logging will be output to stdout which means
          all output will be caught by systemd.
        '';
      };

      port = mkOption {
        type        = types.nullOr types.int;
        default     = null;
        description = lib.mdDoc ''
          Port number to bind to.

          If set to null, the default 30000 will be used.
        '';
      };

      extraArgs = mkOption {
        type = types.listOf types.str;
        default = [];
        description = lib.mdDoc ''
          Additional command line flags to pass to the minetest executable.
        '';
      };
    };
  };

  config = mkIf cfg.enable {
    users.users.minetest = {
      description     = "Minetest Server Service user";
      home            = "/var/lib/minetest";
      createHome      = true;
      uid             = config.ids.uids.minetest;
      group           = "minetest";
    };
    users.groups.minetest.gid = config.ids.gids.minetest;

    systemd.services.minetest-server = {
      description   = "Minetest Server Service";
      wantedBy      = [ "multi-user.target" ];
      after         = [ "network.target" ];

      serviceConfig.Restart = "always";
      serviceConfig.User    = "minetest";
      serviceConfig.Group   = "minetest";

      script = ''
        cd /var/lib/minetest

        exec ${pkgs.minetest}/bin/minetest ${lib.escapeShellArgs flags}
      '';
    };
  };
}