···
9
+
cfg = config.services.nipap;
10
+
iniFmt = pkgs.formats.ini { };
12
+
configFile = iniFmt.generate "nipap.conf" cfg.settings;
14
+
defaultUser = "nipap";
15
+
defaultAuthBackend = "local";
16
+
dataDir = "/var/lib/nipap";
18
+
defaultServiceConfig = {
19
+
WorkingDirectory = dataDir;
21
+
Group = config.users.users."${cfg.user}".group;
22
+
Restart = "on-failure";
26
+
escapedHost = host: if lib.hasInfix ":" host then "[${host}]" else host;
29
+
options.services.nipap = {
30
+
enable = lib.mkEnableOption "global Neat IP Address Planner (NIPAP) configuration";
32
+
user = lib.mkOption {
33
+
type = lib.types.str;
34
+
description = "User to use for running NIPAP services.";
35
+
default = defaultUser;
38
+
settings = lib.mkOption {
40
+
Configuration options to set in /etc/nipap/nipap.conf.
45
+
type = lib.types.submodule {
46
+
freeformType = iniFmt.type;
50
+
listen = lib.mkOption {
51
+
type = lib.types.str;
53
+
description = "IP address to bind nipapd to.";
55
+
port = lib.mkOption {
56
+
type = lib.types.port;
58
+
description = "Port to bind nipapd to.";
61
+
foreground = lib.mkOption {
62
+
type = lib.types.bool;
64
+
description = "Remain in foreground rather than forking to background.";
66
+
debug = lib.mkOption {
67
+
type = lib.types.bool;
69
+
description = "Enable debug logging.";
72
+
db_host = lib.mkOption {
73
+
type = lib.types.str;
75
+
description = "PostgreSQL host to connect to. Empty means use UNIX socket.";
77
+
db_name = lib.mkOption {
78
+
type = lib.types.str;
80
+
defaultText = defaultUser;
81
+
description = "Name of database to use on PostgreSQL server.";
86
+
default_backend = lib.mkOption {
87
+
type = lib.types.str;
88
+
default = defaultAuthBackend;
89
+
description = "Name of auth backend to use by default.";
91
+
auth_cache_timeout = lib.mkOption {
92
+
type = lib.types.int;
94
+
description = "Seconds to store cached auth entries for.";
101
+
authBackendSettings = lib.mkOption {
103
+
auth.backends options to set in /etc/nipap/nipap.conf.
107
+
"${defaultAuthBackend}" = {
108
+
type = "SqliteAuth";
109
+
db_path = "${dataDir}/local_auth.db";
113
+
type = lib.types.submodule {
114
+
freeformType = iniFmt.type;
119
+
enable = lib.mkEnableOption "nipapd server";
120
+
package = lib.mkPackageOption pkgs "nipap" { };
122
+
database.createLocally = lib.mkOption {
123
+
type = lib.types.bool;
125
+
description = "Create a nipap database automatically.";
130
+
enable = lib.mkEnableOption "nipap-www server";
131
+
package = lib.mkPackageOption pkgs "nipap-www" { };
133
+
xmlrpcURIFile = lib.mkOption {
134
+
type = lib.types.nullOr lib.types.path;
136
+
description = "Path to file containing XMLRPC URI for use by web UI - this is a secret, since it contains auth credentials. If null, it will be initialized assuming that the auth database is local.";
139
+
workers = lib.mkOption {
140
+
type = lib.types.int;
142
+
description = "Number of worker processes for Gunicorn to fork.";
144
+
umask = lib.mkOption {
145
+
type = lib.types.str;
147
+
description = "umask for files written by Gunicorn, including UNIX socket.";
150
+
unixSocket = lib.mkOption {
151
+
type = lib.types.nullOr lib.types.str;
153
+
description = "Path to UNIX socket to bind to.";
154
+
example = "/run/nipap/nipap-www.sock";
156
+
host = lib.mkOption {
157
+
type = lib.types.nullOr lib.types.str;
159
+
description = "Host to bind to.";
161
+
port = lib.mkOption {
162
+
type = lib.types.nullOr lib.types.port;
164
+
description = "Port to bind to.";
169
+
config = lib.mkIf cfg.enable (
172
+
systemd.tmpfiles.rules = [
173
+
"d '${dataDir}' - ${cfg.user} ${config.users.users."${cfg.user}".group} - -"
176
+
environment.etc."nipap/nipap.conf" = {
177
+
source = configFile;
180
+
services.nipap.settings = lib.attrsets.mapAttrs' (name: value: {
181
+
name = "auth.backends.${name}";
183
+
}) cfg.authBackendSettings;
185
+
services.nipap.nipapd.enable = lib.mkDefault true;
186
+
services.nipap.nipap-www.enable = lib.mkDefault true;
188
+
environment.systemPackages = [
192
+
(lib.mkIf (cfg.user == defaultUser) {
193
+
users.users."${defaultUser}" = {
194
+
isSystemUser = true;
195
+
group = defaultUser;
198
+
users.groups."${defaultUser}" = { };
200
+
(lib.mkIf (cfg.nipapd.enable && cfg.nipapd.database.createLocally) {
201
+
services.postgresql = {
203
+
extensions = ps: with ps; [ ip4r ];
209
+
ensureDatabases = [ cfg.settings.nipapd.db_name ];
212
+
systemd.services.postgresql.serviceConfig.ExecStartPost =
214
+
sqlFile = pkgs.writeText "nipapd-setup.sql" ''
215
+
CREATE EXTENSION IF NOT EXISTS ip4r;
217
+
ALTER SCHEMA public OWNER TO "${cfg.user}";
218
+
ALTER DATABASE "${cfg.settings.nipapd.db_name}" OWNER TO "${cfg.user}";
223
+
${lib.getExe' config.services.postgresql.finalPackage "psql"} -d "${cfg.settings.nipapd.db_name}" -f "${sqlFile}"
227
+
(lib.mkIf cfg.nipapd.enable {
228
+
systemd.services.nipapd =
230
+
pkg = cfg.nipapd.package;
233
+
description = "Neat IP Address Planner";
236
+
"systemd-tmpfiles-setup.service"
237
+
] ++ lib.optional (cfg.settings.nipapd.db_host == "") "postgresql.service";
238
+
requires = lib.optional (cfg.settings.nipapd.db_host == "") "postgresql.service";
239
+
wantedBy = [ "multi-user.target" ];
240
+
preStart = lib.optionalString (cfg.settings.auth.default_backend == defaultAuthBackend) ''
241
+
# Create/upgrade local auth database
243
+
${pkg}/bin/nipap-passwd create-database >/dev/null 2>&1
244
+
${pkg}/bin/nipap-passwd upgrade-database >/dev/null 2>&1
246
+
serviceConfig = defaultServiceConfig // {
247
+
KillSignal = "SIGINT";
249
+
${pkg}/bin/nipapd \
250
+
--auto-install-db \
251
+
--auto-upgrade-db \
258
+
(lib.mkIf cfg.nipap-www.enable {
262
+
cfg.nipap-www.xmlrpcURIFile == null -> cfg.settings.auth.default_backend == defaultAuthBackend;
263
+
message = "If no XMLRPC URI secret file is specified, then the default auth backend must be in use to automatically generate credentials.";
267
+
# Ensure that _something_ exists in the [www] group.
268
+
services.nipap.settings.www = lib.mkDefault { };
270
+
systemd.services.nipap-www =
272
+
pkg = cfg.nipap-www.package;
275
+
description = "Neat IP Address Planner web server";
278
+
"systemd-tmpfiles-setup.service"
279
+
] ++ lib.optional cfg.nipapd.enable "nipapd.service";
280
+
wantedBy = [ "multi-user.target" ];
282
+
PYTHONPATH = pkg.pythonPath;
284
+
serviceConfig = defaultServiceConfig;
288
+
if cfg.nipap-www.unixSocket != null then
289
+
"unix:${cfg.nipap-www.unixSocket}"
291
+
"${escapedHost cfg.nipap-www.host}:${toString cfg.nipap-www.port}";
292
+
generateXMLRPC = cfg.nipap-www.xmlrpcURIFile == null;
293
+
xmlrpcURIFile = if generateXMLRPC then "${dataDir}/www_xmlrpc_uri" else cfg.nipap-www.xmlrpcURIFile;
296
+
test -f "${dataDir}/www_secret" || {
298
+
${pkg.python}/bin/python -c "import secrets; print(secrets.token_hex())" > "${dataDir}/www_secret"
300
+
export FLASK_SECRET_KEY="$(cat "${dataDir}/www_secret")"
302
+
# Ensure that we have an XMLRPC URI.
304
+
if generateXMLRPC then
306
+
test -f "${dataDir}/www_xmlrpc_uri" || {
308
+
www_password="$(${pkg.python}/bin/python -c "import secrets; print(secrets.token_hex())")"
309
+
${cfg.nipapd.package}/bin/nipap-passwd add --username nipap-www --password "''${www_password}" --name "User account for the web UI" --trusted
311
+
echo "http://nipap-www@${defaultAuthBackend}:''${www_password}@${escapedHost cfg.settings.nipapd.listen}:${toString cfg.settings.nipapd.port}" > "${xmlrpcURIFile}"
317
+
export FLASK_XMLRPC_URI="$(cat "${xmlrpcURIFile}")"
319
+
exec "${pkg.gunicorn}/bin/gunicorn" \
320
+
--preload --workers ${toString cfg.nipap-www.workers} \
321
+
--pythonpath "${pkg}/${pkg.python.sitePackages}" \
322
+
--bind ${bind} --umask ${cfg.nipap-www.umask} \
323
+
"nipapwww:create_app()"
330
+
meta.maintainers = with lib.maintainers; [ lukegb ];