refactor: split configurations for fail2ban, postgres, and qbittorent into folders

This commit is contained in:
Leyla Becker 2025-10-24 13:08:13 -05:00
parent ad04be6534
commit 0f5507c328
13 changed files with 296 additions and 263 deletions

View file

@ -1,8 +1,8 @@
{...}: {
imports = [
./reverseProxy
./fail2ban.nix
./postgres.nix
./fail2ban
./postgres
./network_storage
./actual
@ -17,7 +17,7 @@
./lidarr
./panoramax
./paperless
./qbittorent.nix
./qbittorent
./radarr
./searx
./sonarr

View file

@ -1,74 +0,0 @@
{
lib,
pkgs,
config,
...
}: let
dataFolder = "/var/lib/fail2ban";
dataFile = "fail2ban.sqlite3";
in {
config = lib.mkIf config.services.fail2ban.enable (lib.mkMerge [
{
environment.etc = {
"fail2ban/filter.d/nginx.local".text = lib.mkIf config.services.nginx.enable (
pkgs.lib.mkDefault (pkgs.lib.mkAfter ''
[Definition]
failregex = "limiting requests, excess:.* by zone.*client: <HOST>"
'')
);
};
services.fail2ban = {
maxretry = 5;
ignoreIP = [
# Whitelist local networks
"10.0.0.0/8"
"172.16.0.0/12"
"192.168.0.0/16"
# tail scale tailnet
"100.64.0.0/10"
"fd7a:115c:a1e0::/48"
];
bantime = "24h"; # Ban IPs for one day on the first ban
bantime-increment = {
enable = true; # Enable increment of bantime after each violation
formula = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)";
maxtime = "168h"; # Do not ban for more than 1 week
overalljails = true; # Calculate the ban time based on all the violations
};
jails = {
nginx-iptables.settings = lib.mkIf config.services.nginx.enable {
enabled = true;
filter = "nginx";
action = ''iptables-multiport[name=HTTP, port="http,https"]'';
backend = "auto";
findtime = 600;
bantime = 600;
maxretry = 5;
};
# TODO; figure out if there is any fail2ban things we can do on searx
# searx-iptables.settings = lib.mkIf config.services.searx.enable {};
};
};
}
(lib.mkIf config.host.impermanence.enable {
assertions = [
{
assertion = config.services.fail2ban.daemonSettings.Definition.dbfile == "${dataFolder}/${dataFile}";
message = "fail2ban data file does not match persistence";
}
];
environment.persistence."/persist/system/root" = {
directories = [
{
directory = dataFolder;
user = "fail2ban";
group = "fail2ban";
}
];
};
})
]);
}

View file

@ -0,0 +1,6 @@
{...}: {
imports = [
./fail2ban.nix
./impermanence.nix
];
}

View file

@ -0,0 +1,51 @@
{
lib,
pkgs,
config,
...
}: {
config = lib.mkIf config.services.fail2ban.enable {
environment.etc = {
"fail2ban/filter.d/nginx.local".text = lib.mkIf config.services.nginx.enable (
pkgs.lib.mkDefault (pkgs.lib.mkAfter ''
[Definition]
failregex = "limiting requests, excess:.* by zone.*client: <HOST>"
'')
);
};
services.fail2ban = {
maxretry = 5;
ignoreIP = [
# Whitelist local networks
"10.0.0.0/8"
"172.16.0.0/12"
"192.168.0.0/16"
# tail scale tailnet
"100.64.0.0/10"
"fd7a:115c:a1e0::/48"
];
bantime = "24h"; # Ban IPs for one day on the first ban
bantime-increment = {
enable = true; # Enable increment of bantime after each violation
formula = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)";
maxtime = "168h"; # Do not ban for more than 1 week
overalljails = true; # Calculate the ban time based on all the violations
};
jails = {
nginx-iptables.settings = lib.mkIf config.services.nginx.enable {
enabled = true;
filter = "nginx";
action = ''iptables-multiport[name=HTTP, port="http,https"]'';
backend = "auto";
findtime = 600;
bantime = 600;
maxretry = 5;
};
# TODO; figure out if there is any fail2ban things we can do on searx
# searx-iptables.settings = lib.mkIf config.services.searx.enable {};
};
};
};
}

View file

@ -0,0 +1,27 @@
{
lib,
config,
...
}: let
dataFolder = "/var/lib/fail2ban";
dataFile = "fail2ban.sqlite3";
in {
config = lib.mkIf (config.services.fail2ban.enable && config.host.impermanence.enable) {
assertions = [
{
assertion = config.services.fail2ban.daemonSettings.Definition.dbfile == "${dataFolder}/${dataFile}";
message = "fail2ban data file does not match persistence";
}
];
environment.persistence."/persist/system/root" = {
directories = [
{
directory = dataFolder;
user = "fail2ban";
group = "fail2ban";
}
];
};
};
}

View file

@ -1,121 +0,0 @@
{
config,
lib,
pkgs,
...
}: let
dataDir = "/var/lib/postgresql/16";
adminUsers = lib.lists.filter (user: user.isAdmin) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers);
clientUsers = lib.lists.filter (user: user.isClient) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers);
createUsers = lib.lists.filter (user: user.createUser) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers);
createDatabases = lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraDatabases;
in {
options = {
host.postgres = {
enable = lib.mkEnableOption "enable postgres";
extraUsers = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
};
isAdmin = lib.mkOption {
type = lib.types.bool;
default = false;
};
isClient = lib.mkOption {
type = lib.types.bool;
default = false;
};
createUser = lib.mkOption {
type = lib.types.bool;
default = false;
};
};
}));
default = {};
};
extraDatabases = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
};
};
}));
default = {};
};
};
};
config = lib.mkIf config.host.postgres.enable (lib.mkMerge [
{
services = {
postgresql = {
enable = true;
package = pkgs.postgresql_16;
ensureUsers =
[
{
name = "postgres";
}
]
++ (
builtins.map (user: {
name = user.name;
ensureDBOwnership = true;
})
createUsers
);
ensureDatabases = builtins.map (database: database.name) createDatabases;
identMap =
''
# ArbitraryMapName systemUser DBUser
# Administration Users
superuser_map root postgres
superuser_map postgres postgres
''
+ (
lib.strings.concatLines (builtins.map (user: "superuser_map ${user.name} postgres") adminUsers)
)
+ ''
# Client Users
''
+ (
lib.strings.concatLines (builtins.map (user: "user_map ${user.name} ${user.name}") clientUsers)
);
# configuration here lets users access the db that matches their name and lets user postgres access everything
authentication = pkgs.lib.mkOverride 10 ''
# type database DBuser origin-address auth-method optional_ident_map
local all postgres peer map=superuser_map
local sameuser all peer map=user_map
'';
};
};
}
(lib.mkIf config.host.impermanence.enable {
assertions = [
{
assertion = config.services.postgresql.dataDir == dataDir;
message = "postgres data directory does not match persistence";
}
];
environment.persistence."/persist/system/root" = {
enable = true;
hideMounts = true;
directories = [
{
directory = dataDir;
user = "postgres";
group = "postgres";
}
];
};
})
]);
}

View file

@ -0,0 +1,6 @@
{...}: {
imports = [
./postgres.nix
./impermanence.nix
];
}

View file

@ -0,0 +1,27 @@
{
config,
lib,
...
}: let
dataDir = "/var/lib/postgresql/16";
in {
config = lib.mkIf (config.host.postgres.enable && config.host.impermanence.enable) {
assertions = [
{
assertion = config.services.postgresql.dataDir == dataDir;
message = "postgres data directory does not match persistence";
}
];
environment.persistence."/persist/system/root" = {
enable = true;
hideMounts = true;
directories = [
{
directory = dataDir;
user = "postgres";
group = "postgres";
}
];
};
};
}

View file

@ -0,0 +1,98 @@
{
config,
lib,
pkgs,
...
}: let
adminUsers = lib.lists.filter (user: user.isAdmin) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers);
clientUsers = lib.lists.filter (user: user.isClient) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers);
createUsers = lib.lists.filter (user: user.createUser) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers);
createDatabases = lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraDatabases;
in {
options = {
host.postgres = {
enable = lib.mkEnableOption "enable postgres";
extraUsers = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
};
isAdmin = lib.mkOption {
type = lib.types.bool;
default = false;
};
isClient = lib.mkOption {
type = lib.types.bool;
default = false;
};
createUser = lib.mkOption {
type = lib.types.bool;
default = false;
};
};
}));
default = {};
};
extraDatabases = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
};
};
}));
default = {};
};
};
};
config = lib.mkIf config.host.postgres.enable {
services = {
postgresql = {
enable = true;
package = pkgs.postgresql_16;
ensureUsers =
[
{
name = "postgres";
}
]
++ (
builtins.map (user: {
name = user.name;
ensureDBOwnership = true;
})
createUsers
);
ensureDatabases = builtins.map (database: database.name) createDatabases;
identMap =
''
# ArbitraryMapName systemUser DBUser
# Administration Users
superuser_map root postgres
superuser_map postgres postgres
''
+ (
lib.strings.concatLines (builtins.map (user: "superuser_map ${user.name} postgres") adminUsers)
)
+ ''
# Client Users
''
+ (
lib.strings.concatLines (builtins.map (user: "user_map ${user.name} ${user.name}") clientUsers)
);
# configuration here lets users access the db that matches their name and lets user postgres access everything
authentication = pkgs.lib.mkOverride 10 ''
# type database DBuser origin-address auth-method optional_ident_map
local all postgres peer map=superuser_map
local sameuser all peer map=user_map
'';
};
};
};
}

View file

@ -1,65 +0,0 @@
{
lib,
config,
...
}: let
qbittorent_profile_directory = "/var/lib/qBittorrent/";
in {
options.services.qbittorrent = {
mediaDir = lib.mkOption {
type = lib.types.path;
description = lib.mdDoc ''
The directory to create to store qbittorrent media.
'';
};
};
config = lib.mkIf config.services.qbittorrent.enable (lib.mkMerge [
(lib.mkIf config.host.impermanence.enable {
fileSystems."/persist/system/qbittorrent".neededForBoot = true;
host.storage.pool.extraDatasets = {
# sops age key needs to be available to pre persist for user generation
"persist/system/qbittorrent" = {
type = "zfs_fs";
mountpoint = "/persist/system/qbittorrent";
options = {
canmount = "on";
};
};
};
assertions = [
{
assertion = config.services.qbittorrent.profileDir == qbittorent_profile_directory;
message = "qbittorrent data directory does not match persistence";
}
];
environment.persistence = {
"/persist/system/root" = {
directories = [
{
directory = qbittorent_profile_directory;
user = "qbittorrent";
group = "qbittorrent";
}
];
};
"/persist/system/qbittorrent" = {
enable = true;
hideMounts = true;
directories = [
{
directory = config.services.qbittorrent.mediaDir;
user = "qbittorrent";
group = "qbittorrent";
mode = "1775";
}
];
};
};
})
]);
}

View file

@ -0,0 +1,6 @@
{...}: {
imports = [
./qbittorent.nix
./impermanence.nix
];
}

View file

@ -0,0 +1,54 @@
{
lib,
config,
...
}: let
qbittorent_profile_directory = "/var/lib/qBittorrent/";
in {
config = lib.mkIf (config.services.qbittorrent.enable && config.host.impermanence.enable) {
fileSystems."/persist/system/qbittorrent".neededForBoot = true;
host.storage.pool.extraDatasets = {
# sops age key needs to be available to pre persist for user generation
"persist/system/qbittorrent" = {
type = "zfs_fs";
mountpoint = "/persist/system/qbittorrent";
options = {
canmount = "on";
};
};
};
assertions = [
{
assertion = config.services.qbittorrent.profileDir == qbittorent_profile_directory;
message = "qbittorrent data directory does not match persistence";
}
];
environment.persistence = {
"/persist/system/root" = {
directories = [
{
directory = qbittorent_profile_directory;
user = "qbittorrent";
group = "qbittorrent";
}
];
};
"/persist/system/qbittorrent" = {
enable = true;
hideMounts = true;
directories = [
{
directory = config.services.qbittorrent.mediaDir;
user = "qbittorrent";
group = "qbittorrent";
mode = "1775";
}
];
};
};
};
}

View file

@ -0,0 +1,18 @@
{
lib,
config,
...
}: {
options.services.qbittorrent = {
mediaDir = lib.mkOption {
type = lib.types.path;
description = lib.mdDoc ''
The directory to create to store qbittorrent media.
'';
};
};
config = lib.mkIf config.services.qbittorrent.enable {
# Main qbittorrent configuration goes here if needed
};
}