2024-12-10 20:26:33 +01:00
|
|
|
{
|
|
|
|
config,
|
|
|
|
lib,
|
|
|
|
pkgs,
|
|
|
|
...
|
|
|
|
}:
|
2022-08-30 13:15:41 +02:00
|
|
|
let
|
|
|
|
cfg = config.services.listmonk;
|
|
|
|
tomlFormat = pkgs.formats.toml { };
|
|
|
|
cfgFile = tomlFormat.generate "listmonk.toml" cfg.settings;
|
|
|
|
# Escaping is done according to https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS
|
2024-12-10 20:26:33 +01:00
|
|
|
setDatabaseOption =
|
|
|
|
key: value:
|
2022-08-30 13:15:41 +02:00
|
|
|
"UPDATE settings SET value = '${
|
2022-12-12 03:36:03 +02:00
|
|
|
lib.replaceStrings [ "'" ] [ "''" ] (builtins.toJSON value)
|
2022-08-30 13:15:41 +02:00
|
|
|
}' WHERE key = '${key}';";
|
2024-12-10 20:26:33 +01:00
|
|
|
updateDatabaseConfigSQL = pkgs.writeText "update-database-config.sql" (
|
|
|
|
lib.concatStringsSep "\n" (
|
|
|
|
lib.mapAttrsToList setDatabaseOption (
|
|
|
|
if (cfg.database.settings != null) then cfg.database.settings else { }
|
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
updateDatabaseConfigScript = pkgs.writeShellScriptBin "update-database-config.sh" ''
|
|
|
|
${
|
|
|
|
if cfg.database.mutableSettings then
|
|
|
|
''
|
|
|
|
if [ ! -f /var/lib/listmonk/.db_settings_initialized ]; then
|
|
|
|
${pkgs.postgresql}/bin/psql -d listmonk -f ${updateDatabaseConfigSQL} ;
|
|
|
|
touch /var/lib/listmonk/.db_settings_initialized
|
|
|
|
fi
|
|
|
|
''
|
2022-08-30 13:15:41 +02:00
|
|
|
else
|
2024-12-10 20:26:33 +01:00
|
|
|
"${pkgs.postgresql}/bin/psql -d listmonk -f ${updateDatabaseConfigSQL}"
|
|
|
|
}
|
|
|
|
'';
|
2022-08-30 13:15:41 +02:00
|
|
|
|
2024-08-24 22:05:33 +02:00
|
|
|
databaseSettingsOpts = with lib.types; {
|
2024-12-10 20:26:33 +01:00
|
|
|
freeformType = oneOf [
|
|
|
|
(listOf str)
|
|
|
|
(listOf (attrsOf anything))
|
|
|
|
str
|
|
|
|
int
|
|
|
|
bool
|
|
|
|
];
|
2022-08-30 13:15:41 +02:00
|
|
|
|
|
|
|
options = {
|
2024-08-24 22:05:33 +02:00
|
|
|
"app.notify_emails" = lib.mkOption {
|
2022-08-30 13:15:41 +02:00
|
|
|
type = listOf str;
|
|
|
|
default = [ ];
|
2024-04-13 14:54:15 +02:00
|
|
|
description = "Administrator emails for system notifications";
|
2022-08-30 13:15:41 +02:00
|
|
|
};
|
|
|
|
|
2024-08-24 22:05:33 +02:00
|
|
|
"privacy.exportable" = lib.mkOption {
|
2022-08-30 13:15:41 +02:00
|
|
|
type = listOf str;
|
2024-12-10 20:26:33 +01:00
|
|
|
default = [
|
|
|
|
"profile"
|
|
|
|
"subscriptions"
|
|
|
|
"campaign_views"
|
|
|
|
"link_clicks"
|
|
|
|
];
|
|
|
|
description = "List of fields which can be exported through an automatic export request";
|
2022-08-30 13:15:41 +02:00
|
|
|
};
|
|
|
|
|
2024-08-24 22:05:33 +02:00
|
|
|
"privacy.domain_blocklist" = lib.mkOption {
|
2022-08-30 13:15:41 +02:00
|
|
|
type = listOf str;
|
|
|
|
default = [ ];
|
2024-12-10 20:26:33 +01:00
|
|
|
description = "E-mail addresses with these domains are disallowed from subscribing.";
|
2022-08-30 13:15:41 +02:00
|
|
|
};
|
|
|
|
|
2024-08-24 22:05:33 +02:00
|
|
|
smtp = lib.mkOption {
|
2022-08-30 13:15:41 +02:00
|
|
|
type = listOf (submodule {
|
2024-08-24 22:05:33 +02:00
|
|
|
freeformType = with lib.types; attrsOf anything;
|
2022-08-30 13:15:41 +02:00
|
|
|
|
|
|
|
options = {
|
2024-08-24 22:05:33 +02:00
|
|
|
enabled = lib.mkEnableOption "this SMTP server for listmonk";
|
|
|
|
host = lib.mkOption {
|
|
|
|
type = lib.types.str;
|
2024-04-13 14:54:15 +02:00
|
|
|
description = "Hostname for the SMTP server";
|
2022-08-30 13:15:41 +02:00
|
|
|
};
|
2024-08-24 22:05:33 +02:00
|
|
|
port = lib.mkOption {
|
|
|
|
type = lib.types.port;
|
2024-04-13 14:54:15 +02:00
|
|
|
description = "Port for the SMTP server";
|
2022-08-30 13:15:41 +02:00
|
|
|
};
|
2024-08-24 22:05:33 +02:00
|
|
|
max_conns = lib.mkOption {
|
|
|
|
type = lib.types.int;
|
2024-12-10 20:26:33 +01:00
|
|
|
description = "Maximum number of simultaneous connections, defaults to 1";
|
2022-08-30 13:15:41 +02:00
|
|
|
default = 1;
|
|
|
|
};
|
2024-08-24 22:05:33 +02:00
|
|
|
tls_type = lib.mkOption {
|
2024-12-10 20:26:33 +01:00
|
|
|
type = lib.types.enum [
|
|
|
|
"none"
|
|
|
|
"STARTTLS"
|
|
|
|
"TLS"
|
|
|
|
];
|
2024-04-13 14:54:15 +02:00
|
|
|
description = "Type of TLS authentication with the SMTP server";
|
2022-08-30 13:15:41 +02:00
|
|
|
};
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2024-04-13 14:54:15 +02:00
|
|
|
description = "List of outgoing SMTP servers";
|
2022-08-30 13:15:41 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
# TODO: refine this type based on the smtp one.
|
2024-08-24 22:05:33 +02:00
|
|
|
"bounce.mailboxes" = lib.mkOption {
|
2024-12-10 20:26:33 +01:00
|
|
|
type = listOf (submodule {
|
|
|
|
freeformType = with lib.types; listOf (attrsOf anything);
|
|
|
|
});
|
2022-08-30 13:15:41 +02:00
|
|
|
default = [ ];
|
2024-04-13 14:54:15 +02:00
|
|
|
description = "List of bounce mailboxes";
|
2022-08-30 13:15:41 +02:00
|
|
|
};
|
|
|
|
|
2024-08-24 22:05:33 +02:00
|
|
|
messengers = lib.mkOption {
|
2022-08-30 13:15:41 +02:00
|
|
|
type = listOf str;
|
|
|
|
default = [ ];
|
2024-12-10 20:26:33 +01:00
|
|
|
description = "List of messengers, see: <https://github.com/knadh/listmonk/blob/master/models/settings.go#L64-L74> for options.";
|
2022-08-30 13:15:41 +02:00
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
2024-12-10 20:26:33 +01:00
|
|
|
in
|
|
|
|
{
|
2022-08-30 13:15:41 +02:00
|
|
|
###### interface
|
|
|
|
options = {
|
|
|
|
services.listmonk = {
|
2024-08-24 22:05:33 +02:00
|
|
|
enable = lib.mkEnableOption "Listmonk, this module assumes a reverse proxy to be set";
|
2022-08-30 13:15:41 +02:00
|
|
|
database = {
|
2024-08-24 22:05:33 +02:00
|
|
|
createLocally = lib.mkOption {
|
|
|
|
type = lib.types.bool;
|
2022-08-30 13:15:41 +02:00
|
|
|
default = false;
|
2024-12-10 20:26:33 +01:00
|
|
|
description = "Create the PostgreSQL database and database user locally.";
|
2022-08-30 13:15:41 +02:00
|
|
|
};
|
|
|
|
|
2024-08-24 22:05:33 +02:00
|
|
|
settings = lib.mkOption {
|
2022-08-30 13:15:41 +02:00
|
|
|
default = null;
|
2024-08-24 22:05:33 +02:00
|
|
|
type = with lib.types; nullOr (submodule databaseSettingsOpts);
|
2024-12-10 20:26:33 +01:00
|
|
|
description = "Dynamic settings in the PostgreSQL database, set by a SQL script, see <https://github.com/knadh/listmonk/blob/master/schema.sql#L177-L230> for details.";
|
2022-08-30 13:15:41 +02:00
|
|
|
};
|
2024-08-24 22:05:33 +02:00
|
|
|
mutableSettings = lib.mkOption {
|
|
|
|
type = lib.types.bool;
|
2022-08-30 13:15:41 +02:00
|
|
|
default = true;
|
2024-04-13 14:54:15 +02:00
|
|
|
description = ''
|
2022-08-30 13:15:41 +02:00
|
|
|
Database settings will be reset to the value set in this module if this is not enabled.
|
|
|
|
Enable this if you want to persist changes you have done in the application.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
2024-12-10 20:26:33 +01:00
|
|
|
package = lib.mkPackageOption pkgs "listmonk" { };
|
2024-08-24 22:05:33 +02:00
|
|
|
settings = lib.mkOption {
|
|
|
|
type = lib.types.submodule { freeformType = tomlFormat.type; };
|
2024-04-13 14:54:15 +02:00
|
|
|
description = ''
|
2022-08-30 13:15:41 +02:00
|
|
|
Static settings set in the config.toml, see <https://github.com/knadh/listmonk/blob/master/config.toml.sample> for details.
|
|
|
|
You can set secrets using the secretFile option with environment variables following <https://listmonk.app/docs/configuration/#environment-variables>.
|
|
|
|
'';
|
|
|
|
};
|
2024-08-24 22:05:33 +02:00
|
|
|
secretFile = lib.mkOption {
|
|
|
|
type = lib.types.nullOr lib.types.str;
|
2022-08-30 13:15:41 +02:00
|
|
|
default = null;
|
2024-12-10 20:26:33 +01:00
|
|
|
description = "A file containing secrets as environment variables. See <https://listmonk.app/docs/configuration/#environment-variables> for details on supported values.";
|
2022-08-30 13:15:41 +02:00
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
###### implementation
|
2024-08-24 22:05:33 +02:00
|
|
|
config = lib.mkIf cfg.enable {
|
2022-08-30 13:15:41 +02:00
|
|
|
# Default parameters from https://github.com/knadh/listmonk/blob/master/config.toml.sample
|
2024-08-24 22:05:33 +02:00
|
|
|
services.listmonk.settings."app".address = lib.mkDefault "localhost:9000";
|
|
|
|
services.listmonk.settings."db" = lib.mkMerge [
|
2022-08-30 13:15:41 +02:00
|
|
|
({
|
2024-08-24 22:05:33 +02:00
|
|
|
max_open = lib.mkDefault 25;
|
|
|
|
max_idle = lib.mkDefault 25;
|
|
|
|
max_lifetime = lib.mkDefault "300s";
|
2022-08-30 13:15:41 +02:00
|
|
|
})
|
2024-08-24 22:05:33 +02:00
|
|
|
(lib.mkIf cfg.database.createLocally {
|
|
|
|
host = lib.mkDefault "/run/postgresql";
|
|
|
|
port = lib.mkDefault 5432;
|
|
|
|
user = lib.mkDefault "listmonk";
|
|
|
|
database = lib.mkDefault "listmonk";
|
2022-08-30 13:15:41 +02:00
|
|
|
})
|
|
|
|
];
|
|
|
|
|
2024-08-24 22:05:33 +02:00
|
|
|
services.postgresql = lib.mkIf cfg.database.createLocally {
|
2022-08-30 13:15:41 +02:00
|
|
|
enable = true;
|
|
|
|
|
2024-12-10 20:26:33 +01:00
|
|
|
ensureUsers = [
|
|
|
|
{
|
|
|
|
name = "listmonk";
|
|
|
|
ensureDBOwnership = true;
|
|
|
|
}
|
|
|
|
];
|
2022-08-30 13:15:41 +02:00
|
|
|
|
|
|
|
ensureDatabases = [ "listmonk" ];
|
|
|
|
};
|
|
|
|
|
|
|
|
systemd.services.listmonk = {
|
|
|
|
description = "Listmonk - newsletter and mailing list manager";
|
2024-12-10 20:26:33 +01:00
|
|
|
after = [ "network.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service";
|
2022-08-30 13:15:41 +02:00
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
serviceConfig = {
|
|
|
|
Type = "exec";
|
2024-08-24 22:05:33 +02:00
|
|
|
EnvironmentFile = lib.mkIf (cfg.secretFile != null) [ cfg.secretFile ];
|
2022-08-30 13:15:41 +02:00
|
|
|
ExecStartPre = [
|
|
|
|
# StateDirectory cannot be used when DynamicUser = true is set this way.
|
|
|
|
# Indeed, it will try to create all the folders and realize one of them already exist.
|
|
|
|
# Therefore, we have to create it ourselves.
|
|
|
|
''${pkgs.coreutils}/bin/mkdir -p "''${STATE_DIRECTORY}/listmonk/uploads"''
|
2024-02-17 19:46:53 +01:00
|
|
|
# setup database if not already done
|
|
|
|
"${cfg.package}/bin/listmonk --config ${cfgFile} --idempotent --install --yes"
|
|
|
|
# apply db migrations (setup and migrations can not be done in one step
|
|
|
|
# with "--install --upgrade" listmonk ignores the upgrade)
|
|
|
|
"${cfg.package}/bin/listmonk --config ${cfgFile} --upgrade --yes"
|
2022-08-30 13:15:41 +02:00
|
|
|
"${updateDatabaseConfigScript}/bin/update-database-config.sh"
|
|
|
|
];
|
|
|
|
ExecStart = "${cfg.package}/bin/listmonk --config ${cfgFile}";
|
|
|
|
|
|
|
|
Restart = "on-failure";
|
|
|
|
|
|
|
|
StateDirectory = [ "listmonk" ];
|
|
|
|
|
|
|
|
User = "listmonk";
|
|
|
|
Group = "listmonk";
|
|
|
|
DynamicUser = true;
|
|
|
|
NoNewPrivileges = true;
|
|
|
|
CapabilityBoundingSet = "";
|
2024-01-12 20:14:52 +01:00
|
|
|
SystemCallArchitectures = "native";
|
2024-12-10 20:26:33 +01:00
|
|
|
SystemCallFilter = [
|
|
|
|
"@system-service"
|
|
|
|
"~@privileged"
|
|
|
|
];
|
2024-01-12 20:14:52 +01:00
|
|
|
PrivateDevices = true;
|
2022-08-30 13:15:41 +02:00
|
|
|
ProtectControlGroups = true;
|
|
|
|
ProtectKernelTunables = true;
|
|
|
|
ProtectHome = true;
|
|
|
|
RestrictNamespaces = true;
|
|
|
|
RestrictRealtime = true;
|
|
|
|
UMask = "0027";
|
|
|
|
MemoryDenyWriteExecute = true;
|
|
|
|
LockPersonality = true;
|
2024-12-10 20:26:33 +01:00
|
|
|
RestrictAddressFamilies = [
|
|
|
|
"AF_INET"
|
|
|
|
"AF_INET6"
|
|
|
|
"AF_UNIX"
|
|
|
|
];
|
2022-08-30 13:15:41 +02:00
|
|
|
ProtectKernelModules = true;
|
|
|
|
PrivateUsers = true;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|