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