1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11 cfg = config.services.unit;
12
13 configFile = pkgs.writeText "unit.json" cfg.config;
14
15in
16{
17 options = {
18 services.unit = {
19 enable = mkEnableOption "Unit App Server";
20 package = mkPackageOption pkgs "unit" { };
21 user = mkOption {
22 type = types.str;
23 default = "unit";
24 description = "User account under which unit runs.";
25 };
26 group = mkOption {
27 type = types.str;
28 default = "unit";
29 description = "Group account under which unit runs.";
30 };
31 stateDir = mkOption {
32 type = types.path;
33 default = "/var/spool/unit";
34 description = "Unit data directory.";
35 };
36 logDir = mkOption {
37 type = types.path;
38 default = "/var/log/unit";
39 description = "Unit log directory.";
40 };
41 config = mkOption {
42 type = types.str;
43 default = ''
44 {
45 "listeners": {},
46 "applications": {}
47 }
48 '';
49 example = ''
50 {
51 "listeners": {
52 "*:8300": {
53 "application": "example-php-72"
54 }
55 },
56 "applications": {
57 "example-php-72": {
58 "type": "php 7.2",
59 "processes": 4,
60 "user": "nginx",
61 "group": "nginx",
62 "root": "/var/www",
63 "index": "index.php",
64 "options": {
65 "file": "/etc/php.d/default.ini",
66 "admin": {
67 "max_execution_time": "30",
68 "max_input_time": "30",
69 "display_errors": "off",
70 "display_startup_errors": "off",
71 "open_basedir": "/dev/urandom:/proc/cpuinfo:/proc/meminfo:/etc/ssl/certs:/var/www",
72 "disable_functions": "exec,passthru,shell_exec,system"
73 }
74 }
75 }
76 }
77 }
78 '';
79 description = "Unit configuration in JSON format. More details here https://unit.nginx.org/configuration";
80 };
81 };
82 };
83
84 config = mkIf cfg.enable {
85
86 environment.systemPackages = [ cfg.package ];
87
88 systemd.tmpfiles.rules = [
89 "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
90 "d '${cfg.logDir}' 0750 ${cfg.user} ${cfg.group} - -"
91 ];
92
93 systemd.services.unit = {
94 description = "Unit App Server";
95 after = [ "network.target" ];
96 wantedBy = [ "multi-user.target" ];
97 preStart = ''
98 [ ! -e '${cfg.stateDir}/conf.json' ] || rm -f '${cfg.stateDir}/conf.json'
99 '';
100 postStart = ''
101 ${pkgs.curl}/bin/curl -X PUT --data-binary '@${configFile}' --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config'
102 '';
103 serviceConfig = {
104 Type = "forking";
105 PIDFile = "/run/unit/unit.pid";
106 ExecStart = ''
107 ${cfg.package}/bin/unitd --control 'unix:/run/unit/control.unit.sock' --pid '/run/unit/unit.pid' \
108 --log '${cfg.logDir}/unit.log' --statedir '${cfg.stateDir}' --tmpdir '/tmp' \
109 --user ${cfg.user} --group ${cfg.group}
110 '';
111 ExecStop = ''
112 ${pkgs.curl}/bin/curl -X DELETE --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config'
113 '';
114 # Runtime directory and mode
115 RuntimeDirectory = "unit";
116 RuntimeDirectoryMode = "0750";
117 # Access write directories
118 ReadWritePaths = [
119 cfg.stateDir
120 cfg.logDir
121 ];
122 # Security
123 NoNewPrivileges = true;
124 # Sandboxing
125 ProtectSystem = "strict";
126 ProtectHome = true;
127 PrivateTmp = true;
128 PrivateDevices = true;
129 PrivateUsers = false;
130 ProtectHostname = true;
131 ProtectClock = true;
132 ProtectKernelTunables = true;
133 ProtectKernelModules = true;
134 ProtectKernelLogs = true;
135 ProtectControlGroups = true;
136 RestrictAddressFamilies = [
137 "AF_UNIX"
138 "AF_INET"
139 "AF_INET6"
140 ];
141 LockPersonality = true;
142 MemoryDenyWriteExecute = true;
143 RestrictRealtime = true;
144 RestrictSUIDSGID = true;
145 PrivateMounts = true;
146 # System Call Filtering
147 SystemCallArchitectures = "native";
148 };
149 };
150
151 users.users = optionalAttrs (cfg.user == "unit") {
152 unit = {
153 group = cfg.group;
154 isSystemUser = true;
155 };
156 };
157
158 users.groups = optionalAttrs (cfg.group == "unit") {
159 unit = { };
160 };
161
162 };
163}