0
0
Fork 0
mirror of https://github.com/NixOS/nixpkgs.git synced 2025-07-19 16:40:32 +03:00
nixpkgs/nixos/modules/services/web-apps/nipap.nix
Wolfgang Walther 41c5662cbe
nixos/postgresql: move postStart into separate unit
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>
2025-06-24 15:26:47 +02:00

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 ];
}