mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-07-19 16:40:32 +03:00

This avoids restarting the postgresql server, when only ensureDatabases or ensureUsers have been changed. It will also allow to properly wait for recovery to finish later. To wait for "postgresql is ready" in other services, we now provide a postgresql.target. Resolves #400018 Co-authored-by: Marcel <me@m4rc3l.de>
331 lines
11 KiB
Nix
331 lines
11 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
cfg = config.services.nipap;
|
|
iniFmt = pkgs.formats.ini { };
|
|
|
|
configFile = iniFmt.generate "nipap.conf" cfg.settings;
|
|
|
|
defaultUser = "nipap";
|
|
defaultAuthBackend = "local";
|
|
dataDir = "/var/lib/nipap";
|
|
|
|
defaultServiceConfig = {
|
|
WorkingDirectory = dataDir;
|
|
User = cfg.user;
|
|
Group = config.users.users."${cfg.user}".group;
|
|
Restart = "on-failure";
|
|
RestartSec = 30;
|
|
};
|
|
|
|
escapedHost = host: if lib.hasInfix ":" host then "[${host}]" else host;
|
|
in
|
|
{
|
|
options.services.nipap = {
|
|
enable = lib.mkEnableOption "global Neat IP Address Planner (NIPAP) configuration";
|
|
|
|
user = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "User to use for running NIPAP services.";
|
|
default = defaultUser;
|
|
};
|
|
|
|
settings = lib.mkOption {
|
|
description = ''
|
|
Configuration options to set in /etc/nipap/nipap.conf.
|
|
'';
|
|
|
|
default = { };
|
|
|
|
type = lib.types.submodule {
|
|
freeformType = iniFmt.type;
|
|
|
|
options = {
|
|
nipapd = {
|
|
listen = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "::1";
|
|
description = "IP address to bind nipapd to.";
|
|
};
|
|
port = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 1337;
|
|
description = "Port to bind nipapd to.";
|
|
};
|
|
|
|
foreground = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Remain in foreground rather than forking to background.";
|
|
};
|
|
debug = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Enable debug logging.";
|
|
};
|
|
|
|
db_host = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
description = "PostgreSQL host to connect to. Empty means use UNIX socket.";
|
|
};
|
|
db_name = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = cfg.user;
|
|
defaultText = defaultUser;
|
|
description = "Name of database to use on PostgreSQL server.";
|
|
};
|
|
};
|
|
|
|
auth = {
|
|
default_backend = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = defaultAuthBackend;
|
|
description = "Name of auth backend to use by default.";
|
|
};
|
|
auth_cache_timeout = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = 3600;
|
|
description = "Seconds to store cached auth entries for.";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
authBackendSettings = lib.mkOption {
|
|
description = ''
|
|
auth.backends options to set in /etc/nipap/nipap.conf.
|
|
'';
|
|
|
|
default = {
|
|
"${defaultAuthBackend}" = {
|
|
type = "SqliteAuth";
|
|
db_path = "${dataDir}/local_auth.db";
|
|
};
|
|
};
|
|
|
|
type = lib.types.submodule {
|
|
freeformType = iniFmt.type;
|
|
};
|
|
};
|
|
|
|
nipapd = {
|
|
enable = lib.mkEnableOption "nipapd server";
|
|
package = lib.mkPackageOption pkgs "nipap" { };
|
|
|
|
database.createLocally = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Create a nipap database automatically.";
|
|
};
|
|
};
|
|
|
|
nipap-www = {
|
|
enable = lib.mkEnableOption "nipap-www server";
|
|
package = lib.mkPackageOption pkgs "nipap-www" { };
|
|
|
|
xmlrpcURIFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
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.";
|
|
};
|
|
|
|
workers = lib.mkOption {
|
|
type = lib.types.int;
|
|
default = 4;
|
|
description = "Number of worker processes for Gunicorn to fork.";
|
|
};
|
|
umask = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "0";
|
|
description = "umask for files written by Gunicorn, including UNIX socket.";
|
|
};
|
|
|
|
unixSocket = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "Path to UNIX socket to bind to.";
|
|
example = "/run/nipap/nipap-www.sock";
|
|
};
|
|
host = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = "::";
|
|
description = "Host to bind to.";
|
|
};
|
|
port = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.port;
|
|
default = 21337;
|
|
description = "Port to bind to.";
|
|
};
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable (
|
|
lib.mkMerge [
|
|
{
|
|
systemd.tmpfiles.rules = [
|
|
"d '${dataDir}' - ${cfg.user} ${config.users.users."${cfg.user}".group} - -"
|
|
];
|
|
|
|
environment.etc."nipap/nipap.conf" = {
|
|
source = configFile;
|
|
};
|
|
|
|
services.nipap.settings = lib.attrsets.mapAttrs' (name: value: {
|
|
name = "auth.backends.${name}";
|
|
inherit value;
|
|
}) cfg.authBackendSettings;
|
|
|
|
services.nipap.nipapd.enable = lib.mkDefault true;
|
|
services.nipap.nipap-www.enable = lib.mkDefault true;
|
|
|
|
environment.systemPackages = [
|
|
cfg.nipapd.package
|
|
];
|
|
}
|
|
(lib.mkIf (cfg.user == defaultUser) {
|
|
users.users."${defaultUser}" = {
|
|
isSystemUser = true;
|
|
group = defaultUser;
|
|
home = dataDir;
|
|
};
|
|
users.groups."${defaultUser}" = { };
|
|
})
|
|
(lib.mkIf (cfg.nipapd.enable && cfg.nipapd.database.createLocally) {
|
|
services.postgresql = {
|
|
enable = true;
|
|
extensions = ps: with ps; [ ip4r ];
|
|
ensureUsers = [
|
|
{
|
|
name = cfg.user;
|
|
}
|
|
];
|
|
ensureDatabases = [ cfg.settings.nipapd.db_name ];
|
|
};
|
|
|
|
systemd.services.postgresql.serviceConfig.ExecStartPost =
|
|
let
|
|
sqlFile = pkgs.writeText "nipapd-setup.sql" ''
|
|
CREATE EXTENSION IF NOT EXISTS ip4r;
|
|
|
|
ALTER SCHEMA public OWNER TO "${cfg.user}";
|
|
ALTER DATABASE "${cfg.settings.nipapd.db_name}" OWNER TO "${cfg.user}";
|
|
'';
|
|
in
|
|
[
|
|
''
|
|
${lib.getExe' config.services.postgresql.finalPackage "psql"} -d "${cfg.settings.nipapd.db_name}" -f "${sqlFile}"
|
|
''
|
|
];
|
|
})
|
|
(lib.mkIf cfg.nipapd.enable {
|
|
systemd.services.nipapd =
|
|
let
|
|
pkg = cfg.nipapd.package;
|
|
in
|
|
{
|
|
description = "Neat IP Address Planner";
|
|
after = [
|
|
"network.target"
|
|
"systemd-tmpfiles-setup.service"
|
|
] ++ lib.optional (cfg.settings.nipapd.db_host == "") "postgresql.target";
|
|
requires = lib.optional (cfg.settings.nipapd.db_host == "") "postgresql.target";
|
|
wantedBy = [ "multi-user.target" ];
|
|
preStart = lib.optionalString (cfg.settings.auth.default_backend == defaultAuthBackend) ''
|
|
# Create/upgrade local auth database
|
|
umask 077
|
|
${pkg}/bin/nipap-passwd create-database >/dev/null 2>&1
|
|
${pkg}/bin/nipap-passwd upgrade-database >/dev/null 2>&1
|
|
'';
|
|
serviceConfig = defaultServiceConfig // {
|
|
KillSignal = "SIGINT";
|
|
ExecStart = ''
|
|
${pkg}/bin/nipapd \
|
|
--auto-install-db \
|
|
--auto-upgrade-db \
|
|
--foreground \
|
|
--no-pid-file
|
|
'';
|
|
};
|
|
};
|
|
})
|
|
(lib.mkIf cfg.nipap-www.enable {
|
|
assertions = [
|
|
{
|
|
assertion =
|
|
cfg.nipap-www.xmlrpcURIFile == null -> cfg.settings.auth.default_backend == defaultAuthBackend;
|
|
message = "If no XMLRPC URI secret file is specified, then the default auth backend must be in use to automatically generate credentials.";
|
|
}
|
|
];
|
|
|
|
# Ensure that _something_ exists in the [www] group.
|
|
services.nipap.settings.www = lib.mkDefault { };
|
|
|
|
systemd.services.nipap-www =
|
|
let
|
|
pkg = cfg.nipap-www.package;
|
|
in
|
|
{
|
|
description = "Neat IP Address Planner web server";
|
|
after = [
|
|
"network.target"
|
|
"systemd-tmpfiles-setup.service"
|
|
] ++ lib.optional cfg.nipapd.enable "nipapd.service";
|
|
wantedBy = [ "multi-user.target" ];
|
|
environment = {
|
|
PYTHONPATH = pkg.pythonPath;
|
|
};
|
|
serviceConfig = defaultServiceConfig;
|
|
script =
|
|
let
|
|
bind =
|
|
if cfg.nipap-www.unixSocket != null then
|
|
"unix:${cfg.nipap-www.unixSocket}"
|
|
else
|
|
"${escapedHost cfg.nipap-www.host}:${toString cfg.nipap-www.port}";
|
|
generateXMLRPC = cfg.nipap-www.xmlrpcURIFile == null;
|
|
xmlrpcURIFile = if generateXMLRPC then "${dataDir}/www_xmlrpc_uri" else cfg.nipap-www.xmlrpcURIFile;
|
|
in
|
|
''
|
|
test -f "${dataDir}/www_secret" || {
|
|
umask 0077
|
|
${pkg.python}/bin/python -c "import secrets; print(secrets.token_hex())" > "${dataDir}/www_secret"
|
|
}
|
|
export FLASK_SECRET_KEY="$(cat "${dataDir}/www_secret")"
|
|
|
|
# Ensure that we have an XMLRPC URI.
|
|
${
|
|
if generateXMLRPC then
|
|
''
|
|
test -f "${dataDir}/www_xmlrpc_uri" || {
|
|
umask 0077
|
|
www_password="$(${pkg.python}/bin/python -c "import secrets; print(secrets.token_hex())")"
|
|
${cfg.nipapd.package}/bin/nipap-passwd add --username nipap-www --password "''${www_password}" --name "User account for the web UI" --trusted
|
|
|
|
echo "http://nipap-www@${defaultAuthBackend}:''${www_password}@${escapedHost cfg.settings.nipapd.listen}:${toString cfg.settings.nipapd.port}" > "${xmlrpcURIFile}"
|
|
}
|
|
''
|
|
else
|
|
""
|
|
}
|
|
export FLASK_XMLRPC_URI="$(cat "${xmlrpcURIFile}")"
|
|
|
|
exec "${pkg.gunicorn}/bin/gunicorn" \
|
|
--preload --workers ${toString cfg.nipap-www.workers} \
|
|
--pythonpath "${pkg}/${pkg.python.sitePackages}" \
|
|
--bind ${bind} --umask ${cfg.nipap-www.umask} \
|
|
"nipapwww:create_app()"
|
|
'';
|
|
};
|
|
})
|
|
]
|
|
);
|
|
|
|
meta.maintainers = with lib.maintainers; [ lukegb ];
|
|
}
|