1{ config, lib, pkgs, buildEnv, ... }:
2
3let
4 cfg = config.services.peering-manager;
5
6 pythonFmt = pkgs.formats.pythonVars {};
7 settingsFile = pythonFmt.generate "peering-manager-settings.py" cfg.settings;
8 extraConfigFile = pkgs.writeTextFile {
9 name = "peering-manager-extraConfig.py";
10 text = cfg.extraConfig;
11 };
12 configFile = pkgs.concatText "configuration.py" [ settingsFile extraConfigFile ];
13
14 pkg = (pkgs.peering-manager.overrideAttrs (old: {
15 postInstall = ''
16 ln -s ${configFile} $out/opt/peering-manager/peering_manager/configuration.py
17 '' + lib.optionalString cfg.enableLdap ''
18 ln -s ${cfg.ldapConfigPath} $out/opt/peering-manager/peering_manager/ldap_config.py
19 '';
20 })).override {
21 inherit (cfg) plugins;
22 };
23 peeringManagerManageScript = pkgs.writeScriptBin "peering-manager-manage" ''
24 #!${pkgs.stdenv.shell}
25 export PYTHONPATH=${pkg.pythonPath}
26 sudo -u peering-manager ${pkg}/bin/peering-manager "$@"
27 '';
28
29in {
30 options.services.peering-manager = with lib; {
31 enable = mkOption {
32 type = types.bool;
33 default = false;
34 description = ''
35 Enable Peering Manager.
36
37 This module requires a reverse proxy that serves `/static` separately.
38 See this [example](https://github.com/peering-manager/contrib/blob/main/nginx.conf on how to configure this.
39 '';
40 };
41
42 enableScheduledTasks = mkOption {
43 type = types.bool;
44 default = true;
45 description = ''
46 Set up [scheduled tasks](https://peering-manager.readthedocs.io/en/stable/setup/8-scheduled-tasks/)
47 '';
48 };
49
50 listenAddress = mkOption {
51 type = types.str;
52 default = "[::1]";
53 description = ''
54 Address the server will listen on.
55 '';
56 };
57
58 port = mkOption {
59 type = types.port;
60 default = 8001;
61 description = ''
62 Port the server will listen on.
63 '';
64 };
65
66 plugins = mkOption {
67 type = types.functionTo (types.listOf types.package);
68 default = _: [];
69 defaultText = literalExpression ''
70 python3Packages: with python3Packages; [];
71 '';
72 description = ''
73 List of plugin packages to install.
74 '';
75 };
76
77 secretKeyFile = mkOption {
78 type = types.path;
79 description = ''
80 Path to a file containing the secret key.
81 '';
82 };
83
84 peeringdbApiKeyFile = mkOption {
85 type = with types; nullOr path;
86 default = null;
87 description = ''
88 Path to a file containing the PeeringDB API key.
89 '';
90 };
91
92 settings = lib.mkOption {
93 description = ''
94 Configuration options to set in `configuration.py`.
95 See the [documentation](https://peering-manager.readthedocs.io/en/stable/configuration/optional-settings/) for more possible options.
96 '';
97
98 default = { };
99
100 type = lib.types.submodule {
101 freeformType = pythonFmt.type;
102
103 options = {
104 ALLOWED_HOSTS = lib.mkOption {
105 type = with lib.types; listOf str;
106 default = ["*"];
107 description = ''
108 A list of valid fully-qualified domain names (FQDNs) and/or IP
109 addresses that can be used to reach the peering manager service.
110 '';
111 };
112 };
113 };
114 };
115
116 extraConfig = mkOption {
117 type = types.lines;
118 default = "";
119 description = ''
120 Additional lines of configuration appended to the `configuration.py`.
121 See the [documentation](https://peering-manager.readthedocs.io/en/stable/configuration/optional-settings/) for more possible options.
122 '';
123 };
124
125 enableLdap = mkOption {
126 type = types.bool;
127 default = false;
128 description = ''
129 Enable LDAP-Authentication for Peering Manager.
130
131 This requires a configuration file being pass through `ldapConfigPath`.
132 '';
133 };
134
135 ldapConfigPath = mkOption {
136 type = types.path;
137 description = ''
138 Path to the Configuration-File for LDAP-Authentication, will be loaded as `ldap_config.py`.
139 See the [documentation](https://peering-manager.readthedocs.io/en/stable/setup/6-ldap/#configuration) for possible options.
140 '';
141 };
142 };
143
144 config = lib.mkIf cfg.enable {
145 services.peering-manager = {
146 settings = {
147 DATABASE = {
148 NAME = "peering-manager";
149 USER = "peering-manager";
150 HOST = "/run/postgresql";
151 };
152
153 # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
154 # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
155 # to use two separate database IDs.
156 REDIS = {
157 tasks = {
158 UNIX_SOCKET_PATH = config.services.redis.servers.peering-manager.unixSocket;
159 DATABASE = 0;
160 };
161 caching = {
162 UNIX_SOCKET_PATH = config.services.redis.servers.peering-manager.unixSocket;
163 DATABASE = 1;
164 };
165 };
166 };
167
168 extraConfig = ''
169 with open("${cfg.secretKeyFile}", "r") as file:
170 SECRET_KEY = file.readline()
171 '' + lib.optionalString (cfg.peeringdbApiKeyFile != null) ''
172 with open("${cfg.peeringdbApiKeyFile}", "r") as file:
173 PEERINGDB_API_KEY = file.readline()
174 '';
175
176 plugins = lib.mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
177 };
178
179 system.build.peeringManagerPkg = pkg;
180
181 services.redis.servers.peering-manager.enable = true;
182
183 services.postgresql = {
184 enable = true;
185 ensureDatabases = [ "peering-manager" ];
186 ensureUsers = [
187 {
188 name = "peering-manager";
189 ensureDBOwnership = true;
190 }
191 ];
192 };
193
194 environment.systemPackages = [ peeringManagerManageScript ];
195
196 systemd.targets.peering-manager = {
197 description = "Target for all Peering Manager services";
198 wantedBy = [ "multi-user.target" ];
199 wants = [ "network-online.target" ];
200 after = [ "network-online.target" "redis-peering-manager.service" ];
201 };
202
203 systemd.services = let
204 defaults = {
205 environment = {
206 PYTHONPATH = pkg.pythonPath;
207 };
208 serviceConfig = {
209 WorkingDirectory = "/var/lib/peering-manager";
210 User = "peering-manager";
211 Group = "peering-manager";
212 StateDirectory = "peering-manager";
213 StateDirectoryMode = "0750";
214 Restart = "on-failure";
215 };
216 };
217 in {
218 peering-manager-migration = lib.recursiveUpdate defaults {
219 description = "Peering Manager migrations";
220 wantedBy = [ "peering-manager.target" ];
221 serviceConfig = {
222 Type = "oneshot";
223 ExecStart = "${pkg}/bin/peering-manager migrate";
224 };
225 };
226
227 peering-manager = lib.recursiveUpdate defaults {
228 description = "Peering Manager WSGI Service";
229 wantedBy = [ "peering-manager.target" ];
230 after = [ "peering-manager-migration.service" ];
231
232 preStart = ''
233 ${pkg}/bin/peering-manager remove_stale_contenttypes --no-input
234 '';
235
236 serviceConfig = {
237 ExecStart = ''
238 ${pkg.python.pkgs.gunicorn}/bin/gunicorn peering_manager.wsgi \
239 --bind ${cfg.listenAddress}:${toString cfg.port} \
240 --pythonpath ${pkg}/opt/peering-manager
241 '';
242 };
243 };
244
245 peering-manager-rq = lib.recursiveUpdate defaults {
246 description = "Peering Manager Request Queue Worker";
247 wantedBy = [ "peering-manager.target" ];
248 after = [ "peering-manager.service" ];
249 serviceConfig.ExecStart = "${pkg}/bin/peering-manager rqworker high default low";
250 };
251
252 peering-manager-housekeeping = lib.recursiveUpdate defaults {
253 description = "Peering Manager housekeeping job";
254 after = [ "peering-manager.service" ];
255 serviceConfig = {
256 Type = "oneshot";
257 ExecStart = "${pkg}/bin/peering-manager housekeeping";
258 };
259 };
260
261 peering-manager-peeringdb-sync = lib.recursiveUpdate defaults {
262 description = "PeeringDB sync";
263 after = [ "peering-manager.service" ];
264 serviceConfig = {
265 Type = "oneshot";
266 ExecStart = "${pkg}/bin/peering-manager peeringdb_sync";
267 };
268 };
269
270 peering-manager-prefix-fetch = lib.recursiveUpdate defaults {
271 description = "Fetch IRR AS-SET prefixes";
272 after = [ "peering-manager.service" ];
273 serviceConfig = {
274 Type = "oneshot";
275 ExecStart = "${pkg}/bin/peering-manager grab_prefixes";
276 };
277 };
278
279 peering-manager-configuration-deployment = lib.recursiveUpdate defaults {
280 description = "Push configuration to routers";
281 after = [ "peering-manager.service" ];
282 serviceConfig = {
283 Type = "oneshot";
284 ExecStart = "${pkg}/bin/peering-manager configure_routers";
285 };
286 };
287
288 peering-manager-session-poll = lib.recursiveUpdate defaults {
289 description = "Poll peering sessions from routers";
290 after = [ "peering-manager.service" ];
291 serviceConfig = {
292 Type = "oneshot";
293 ExecStart = "${pkg}/bin/peering-manager poll_bgp_sessions --all";
294 };
295 };
296 };
297
298 systemd.timers = {
299 peering-manager-housekeeping = {
300 description = "Run Peering Manager housekeeping job";
301 wantedBy = [ "timers.target" ];
302 timerConfig.OnCalendar = "daily";
303 };
304
305 peering-manager-peeringdb-sync = {
306 enable = lib.mkDefault cfg.enableScheduledTasks;
307 description = "Sync PeeringDB at 2:30";
308 wantedBy = [ "timers.target" ];
309 timerConfig.OnCalendar = "02:30:00";
310 };
311
312 peering-manager-prefix-fetch = {
313 enable = lib.mkDefault cfg.enableScheduledTasks;
314 description = "Fetch IRR AS-SET prefixes at 4:30";
315 wantedBy = [ "timers.target" ];
316 timerConfig.OnCalendar = "04:30:00";
317 };
318
319 peering-manager-configuration-deployment = {
320 enable = lib.mkDefault cfg.enableScheduledTasks;
321 description = "Push router configuration every hour 5 minutes before full hour";
322 wantedBy = [ "timers.target" ];
323 timerConfig.OnCalendar = "*:55:00";
324 };
325
326 peering-manager-session-poll = {
327 enable = lib.mkDefault cfg.enableScheduledTasks;
328 description = "Poll peering sessions from routers every hour";
329 wantedBy = [ "timers.target" ];
330 timerConfig.OnCalendar = "*:00:00";
331 };
332 };
333
334 users.users.peering-manager = {
335 home = "/var/lib/peering-manager";
336 isSystemUser = true;
337 group = "peering-manager";
338 };
339 users.groups.peering-manager = {};
340 users.groups."${config.services.redis.servers.peering-manager.user}".members = [ "peering-manager" ];
341 };
342
343 meta.maintainers = with lib.maintainers; [ yuka ];
344}