1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.temporal;
9
10 settingsFormat = pkgs.formats.yaml { };
11
12 usingDefaultDataDir = cfg.dataDir == "/var/lib/temporal";
13 usingDefaultUserAndGroup = cfg.user == "temporal" && cfg.group == "temporal";
14in
15{
16 meta.maintainers = [ lib.maintainers.jpds ];
17
18 options.services.temporal = {
19 enable = lib.mkEnableOption "Temporal";
20
21 package = lib.mkPackageOption pkgs "Temporal" {
22 default = [ "temporal" ];
23 };
24
25 settings = lib.mkOption {
26 type = lib.types.submodule {
27 freeformType = settingsFormat.type;
28 };
29
30 description = ''
31 Temporal configuration.
32
33 See <https://docs.temporal.io/references/configuration> for more
34 information about Temporal configuration options
35 '';
36 };
37
38 dataDir = lib.mkOption {
39 type = lib.types.path;
40 default = "/var/lib/temporal";
41 apply = lib.converge (lib.removeSuffix "/");
42 description = ''
43 Data directory for Temporal. If you change this, you need to
44 manually create the directory. You also need to create the
45 `temporal` user and group, or change
46 [](#opt-services.temporal.user) and
47 [](#opt-services.temporal.group) to existing ones with
48 access to the directory.
49 '';
50 };
51
52 user = lib.mkOption {
53 type = lib.types.str;
54 default = "temporal";
55 description = ''
56 The user Temporal runs as. Should be left at default unless
57 you have very specific needs.
58 '';
59 };
60
61 group = lib.mkOption {
62 type = lib.types.str;
63 default = "temporal";
64 description = ''
65 The group temporal runs as. Should be left at default unless
66 you have very specific needs.
67 '';
68 };
69
70 restartIfChanged = lib.mkOption {
71 type = lib.types.bool;
72 description = ''
73 Automatically restart the service on config change.
74 This can be set to false to defer restarts on a server or cluster.
75 Please consider the security implications of inadvertently running an older version,
76 and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
77 '';
78 default = true;
79 };
80 };
81
82 config = lib.mkIf cfg.enable {
83 environment.etc."temporal/temporal-server.yaml".source =
84 settingsFormat.generate "temporal-server.yaml" cfg.settings;
85
86 systemd.services.temporal = {
87 description = "Temporal server";
88 wantedBy = [ "multi-user.target" ];
89 after = [ "network.target" ];
90 inherit (cfg) restartIfChanged;
91 restartTriggers = [ config.environment.etc."temporal/temporal-server.yaml".source ];
92 environment = {
93 HOME = cfg.dataDir;
94 };
95 serviceConfig = {
96 ExecStart = ''
97 ${cfg.package}/bin/temporal-server --root / --config /etc/temporal/ -e temporal-server start
98 '';
99 User = cfg.user;
100 Group = cfg.group;
101 Restart = "on-failure";
102 DynamicUser = usingDefaultUserAndGroup && usingDefaultDataDir;
103 CapabilityBoundingSet = [ "" ];
104 DevicePolicy = "closed";
105 LockPersonality = true;
106 MemoryDenyWriteExecute = true;
107 NoNewPrivileges = true;
108 PrivateDevices = true;
109 ProcSubset = "pid";
110 ProtectClock = true;
111 ProtectHome = true;
112 ProtectHostname = true;
113 ProtectControlGroups = true;
114 ProtectKernelLogs = true;
115 ProtectKernelModules = true;
116 ProtectKernelTunables = true;
117 ProtectProc = "invisible";
118 ProtectSystem = "strict";
119 ReadWritePaths = [
120 cfg.dataDir
121 ];
122 RestrictAddressFamilies = [
123 "AF_NETLINK"
124 "AF_INET"
125 "AF_INET6"
126 ];
127 RestrictNamespaces = true;
128 RestrictRealtime = true;
129 RestrictSUIDSGID = true;
130 SystemCallArchitectures = "native";
131 SystemCallFilter = [
132 # 1. allow a reasonable set of syscalls
133 "@system-service @resources"
134 # 2. and deny unreasonable ones
135 "~@privileged"
136 # 3. then allow the required subset within denied groups
137 "@chown"
138 ];
139 }
140 // (lib.optionalAttrs (usingDefaultDataDir) {
141 StateDirectory = "temporal";
142 StateDirectoryMode = "0700";
143 });
144 };
145 };
146}