1{ config, lib, pkgs, buildEnv, ... }:
2
3with lib;
4
5let
6 cfg = config.services.peering-manager;
7 configFile = pkgs.writeTextFile {
8 name = "configuration.py";
9 text = ''
10 ALLOWED_HOSTS = ['*']
11 DATABASE = {
12 'NAME': 'peering-manager',
13 'USER': 'peering-manager',
14 'HOST': '/run/postgresql',
15 }
16
17 # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
18 # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
19 # to use two separate database IDs.
20 REDIS = {
21 'tasks': {
22 'UNIX_SOCKET_PATH': '${config.services.redis.servers.peering-manager.unixSocket}',
23 'DATABASE': 0,
24 },
25 'caching': {
26 'UNIX_SOCKET_PATH': '${config.services.redis.servers.peering-manager.unixSocket}',
27 'DATABASE': 1,
28 }
29 }
30
31 with open("${cfg.secretKeyFile}", "r") as file:
32 SECRET_KEY = file.readline()
33 '' + lib.optionalString (cfg.peeringdbApiKeyFile != null) ''
34 with open("${cfg.peeringdbApiKeyFile}", "r") as file:
35 PEERINGDB_API_KEY = file.readline()
36 '' + ''
37
38 ${cfg.extraConfig}
39 '';
40 };
41 pkg = (pkgs.peering-manager.overrideAttrs (old: {
42 postInstall = ''
43 ln -s ${configFile} $out/opt/peering-manager/peering_manager/configuration.py
44 '' + optionalString cfg.enableLdap ''
45 ln -s ${cfg.ldapConfigPath} $out/opt/peering-manager/peering_manager/ldap_config.py
46 '';
47 })).override {
48 inherit (cfg) plugins;
49 };
50 peeringManagerManageScript = with pkgs; (writeScriptBin "peering-manager-manage" ''
51 #!${stdenv.shell}
52 export PYTHONPATH=${pkg.pythonPath}
53 sudo -u peering-manager ${pkg}/bin/peering-manager "$@"
54 '');
55
56in {
57 options.services.peering-manager = {
58 enable = mkOption {
59 type = lib.types.bool;
60 default = false;
61 description = lib.mdDoc ''
62 Enable Peering Manager.
63
64 This module requires a reverse proxy that serves `/static` separately.
65 See this [example](https://github.com/peering-manager-community/peering-manager/blob/develop/contrib/nginx.conf/) on how to configure this.
66 '';
67 };
68
69 listenAddress = mkOption {
70 type = types.str;
71 default = "[::1]";
72 description = lib.mdDoc ''
73 Address the server will listen on.
74 '';
75 };
76
77 port = mkOption {
78 type = types.port;
79 default = 8001;
80 description = lib.mdDoc ''
81 Port the server will listen on.
82 '';
83 };
84
85 plugins = mkOption {
86 type = types.functionTo (types.listOf types.package);
87 default = _: [];
88 defaultText = literalExpression ''
89 python3Packages: with python3Packages; [];
90 '';
91 description = lib.mdDoc ''
92 List of plugin packages to install.
93 '';
94 };
95
96 secretKeyFile = mkOption {
97 type = types.path;
98 description = lib.mdDoc ''
99 Path to a file containing the secret key.
100 '';
101 };
102
103 peeringdbApiKeyFile = mkOption {
104 type = with types; nullOr path;
105 default = null;
106 description = lib.mdDoc ''
107 Path to a file containing the PeeringDB API key.
108 '';
109 };
110
111 extraConfig = mkOption {
112 type = types.lines;
113 default = "";
114 description = lib.mdDoc ''
115 Additional lines of configuration appended to the `configuration.py`.
116 See the [documentation](https://peering-manager.readthedocs.io/en/stable/configuration/optional-settings/) for more possible options.
117 '';
118 };
119
120 enableLdap = mkOption {
121 type = types.bool;
122 default = false;
123 description = lib.mdDoc ''
124 Enable LDAP-Authentication for Peering Manager.
125
126 This requires a configuration file being pass through `ldapConfigPath`.
127 '';
128 };
129
130 ldapConfigPath = mkOption {
131 type = types.path;
132 description = lib.mdDoc ''
133 Path to the Configuration-File for LDAP-Authentication, will be loaded as `ldap_config.py`.
134 See the [documentation](https://peering-manager.readthedocs.io/en/stable/setup/6-ldap/#configuration) for possible options.
135 '';
136 };
137 };
138
139 config = mkIf cfg.enable {
140 services.peering-manager.plugins = mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
141
142 system.build.peeringManagerPkg = pkg;
143
144 services.redis.servers.peering-manager.enable = true;
145
146 services.postgresql = {
147 enable = true;
148 ensureDatabases = [ "peering-manager" ];
149 ensureUsers = [
150 {
151 name = "peering-manager";
152 ensurePermissions = {
153 "DATABASE \"peering-manager\"" = "ALL PRIVILEGES";
154 };
155 }
156 ];
157 };
158
159 environment.systemPackages = [ peeringManagerManageScript ];
160
161 systemd.targets.peering-manager = {
162 description = "Target for all Peering Manager services";
163 wantedBy = [ "multi-user.target" ];
164 after = [ "network-online.target" "redis-peering-manager.service" ];
165 };
166
167 systemd.services = let
168 defaultServiceConfig = {
169 WorkingDirectory = "/var/lib/peering-manager";
170 User = "peering-manager";
171 Group = "peering-manager";
172 StateDirectory = "peering-manager";
173 StateDirectoryMode = "0750";
174 Restart = "on-failure";
175 };
176 in {
177 peering-manager-migration = {
178 description = "Peering Manager migrations";
179 wantedBy = [ "peering-manager.target" ];
180
181 environment = {
182 PYTHONPATH = pkg.pythonPath;
183 };
184
185 serviceConfig = defaultServiceConfig // {
186 Type = "oneshot";
187 ExecStart = ''
188 ${pkg}/bin/peering-manager migrate
189 '';
190 };
191 };
192
193 peering-manager = {
194 description = "Peering Manager WSGI Service";
195 wantedBy = [ "peering-manager.target" ];
196 after = [ "peering-manager-migration.service" ];
197
198 preStart = ''
199 ${pkg}/bin/peering-manager remove_stale_contenttypes --no-input
200 '';
201
202 environment = {
203 PYTHONPATH = pkg.pythonPath;
204 };
205
206 serviceConfig = defaultServiceConfig // {
207 ExecStart = ''
208 ${pkg.python.pkgs.gunicorn}/bin/gunicorn peering_manager.wsgi \
209 --bind ${cfg.listenAddress}:${toString cfg.port} \
210 --pythonpath ${pkg}/opt/peering-manager
211 '';
212 };
213 };
214
215 peering-manager-rq = {
216 description = "Peering Manager Request Queue Worker";
217 wantedBy = [ "peering-manager.target" ];
218 after = [ "peering-manager.service" ];
219
220 environment = {
221 PYTHONPATH = pkg.pythonPath;
222 };
223
224 serviceConfig = defaultServiceConfig // {
225 ExecStart = ''
226 ${pkg}/bin/peering-manager rqworker high default low
227 '';
228 };
229 };
230
231 peering-manager-housekeeping = {
232 description = "Peering Manager housekeeping job";
233 after = [ "peering-manager.service" ];
234
235 environment = {
236 PYTHONPATH = pkg.pythonPath;
237 };
238
239 serviceConfig = defaultServiceConfig // {
240 Type = "oneshot";
241 ExecStart = ''
242 ${pkg}/bin/peering-manager housekeeping
243 '';
244 };
245 };
246 };
247
248 systemd.timers.peering-manager-housekeeping = {
249 description = "Run Peering Manager housekeeping job";
250 wantedBy = [ "timers.target" ];
251
252 timerConfig = {
253 OnCalendar = "daily";
254 };
255 };
256
257 users.users.peering-manager = {
258 home = "/var/lib/peering-manager";
259 isSystemUser = true;
260 group = "peering-manager";
261 };
262 users.groups.peering-manager = {};
263 users.groups."${config.services.redis.servers.peering-manager.user}".members = [ "peering-manager" ];
264 };
265}