merge: merged leyla/main
This commit is contained in:
parent
3a58722815
commit
0a8b3e1496
120 changed files with 2396 additions and 4519 deletions
24
modules/nixos-modules/server/actual/actual.nix
Normal file
24
modules/nixos-modules/server/actual/actual.nix
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
const = import ./const.nix;
|
||||
dataDirectory = const.dataDirectory;
|
||||
in {
|
||||
options.services.actual = {
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
description = "The port to listen on";
|
||||
default = 5006;
|
||||
};
|
||||
};
|
||||
config = lib.mkIf config.services.actual.enable {
|
||||
services.actual = {
|
||||
settings = {
|
||||
port = config.services.actual.port;
|
||||
dataDir = dataDirectory;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
dataDirectory = "/var/lib/actual/";
|
||||
dataDirectory = "/var/lib/private/actual";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,8 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
const = import ./const.nix;
|
||||
dataDirectory = const.dataDirectory;
|
||||
in {
|
||||
imports = [
|
||||
./actual.nix
|
||||
./proxy.nix
|
||||
./fail2ban.nix
|
||||
./impermanence.nix
|
||||
];
|
||||
|
||||
config = lib.mkIf config.services.actual.enable {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${dataDirectory} 2770 actual actual"
|
||||
];
|
||||
|
||||
services.actual = {
|
||||
settings = {
|
||||
ACTUAL_DATA_DIR = dataDirectory;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,22 @@
|
|||
const = import ./const.nix;
|
||||
dataDirectory = const.dataDirectory;
|
||||
in {
|
||||
config = lib.mkIf (config.services.actual.enable && config.host.impermanence.enable) {
|
||||
options.services.actual = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.actual.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.actual.impermanence.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.actual.settings.ACTUAL_DATA_DIR == dataDirectory;
|
||||
message = "actual data location does not match persistence";
|
||||
assertion = config.services.actual.settings.dataDir == dataDirectory;
|
||||
message = "actual data location does not match persistence\nconfig directory: ${config.services.actual.settings.dataDir}\npersistence directory: ${dataDirectory}";
|
||||
}
|
||||
{
|
||||
assertion = config.systemd.services.actual.serviceConfig.DynamicUser or false;
|
||||
message = "actual systemd service must have DynamicUser enabled to use private directory";
|
||||
}
|
||||
];
|
||||
environment.persistence."/persist/system/root" = {
|
||||
|
|
|
|||
|
|
@ -4,17 +4,30 @@
|
|||
...
|
||||
}: {
|
||||
options.services.actual = {
|
||||
subdomain = lib.mkOption {
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "actual";
|
||||
description = "subdomain of base domain that actual will be hosted at";
|
||||
description = "domain that actual will be hosted at";
|
||||
default = "actual.arpa";
|
||||
};
|
||||
extraDomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "extra domains that should be configured for actual";
|
||||
default = [];
|
||||
};
|
||||
reverseProxy.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.actual.enable && config.services.reverseProxy.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.actual.enable && config.host.reverse_proxy.enable) {
|
||||
host = {
|
||||
reverse_proxy.subdomains.${config.services.actual.subdomain} = {
|
||||
target = "http://localhost:${toString config.services.actual.settings.port}";
|
||||
config = lib.mkIf config.services.actual.reverseProxy.enable {
|
||||
services.reverseProxy.services.actual = {
|
||||
target = "http://localhost:${toString config.services.actual.settings.port}";
|
||||
domain = config.services.actual.domain;
|
||||
extraDomains = config.services.actual.extraDomains;
|
||||
|
||||
settings = {
|
||||
forwardHeaders.enable = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{...}: {
|
||||
imports = [
|
||||
./proxy.nix
|
||||
./impermanence.nix
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,14 @@
|
|||
}: let
|
||||
bazarr_data_directory = "/var/lib/bazarr";
|
||||
in {
|
||||
config = lib.mkIf (config.services.bazarr.enable && config.host.impermanence.enable) {
|
||||
options.services.bazarr = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.bazarr.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.bazarr.impermanence.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.bazarr.dataDir == bazarr_data_directory;
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
options.services.bazarr = {
|
||||
subdomain = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Subdomain for reverse proxy. If null, service will be local only.";
|
||||
};
|
||||
extraSubdomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
description = "Extra subdomains for reverse proxy.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.bazarr.enable && config.services.bazarr.subdomain != null) {
|
||||
host.reverse_proxy.subdomains.bazarr = {
|
||||
subdomain = config.services.bazarr.subdomain;
|
||||
extraSubdomains = config.services.bazarr.extraSubdomains;
|
||||
target = "http://127.0.0.1:6767";
|
||||
websockets.enable = true;
|
||||
forwardHeaders.enable = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -27,9 +27,19 @@ in {
|
|||
show_doc = lib.mkEnableOption "OpenAPI documentation (loads content from third party websites)";
|
||||
|
||||
downstreams = {
|
||||
loopback = {
|
||||
enable = lib.mkEnableOption "loopback downstream DNS server on localhost:53";
|
||||
openFirewall = lib.mkEnableOption "automatic port forwarding for the loopback downstream";
|
||||
host = {
|
||||
enable = lib.mkEnableOption "host downstream DNS server accessible from network on all interfaces";
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 53;
|
||||
description = "Port for the host downstream DNS server to listen on.";
|
||||
};
|
||||
openFirewall = lib.mkEnableOption "automatic port forwarding for the host downstream";
|
||||
disableSystemdResolved = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to automatically disable systemd-resolved when using port 53. Set to false if you want to handle the conflict manually.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -79,9 +89,44 @@ in {
|
|||
default = [];
|
||||
description = "List of additional upstream DNS server configurations.";
|
||||
};
|
||||
|
||||
blocklists = {
|
||||
ad_malware = {
|
||||
enable = lib.mkEnableOption "Host file for blocking ads and malware";
|
||||
url = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "http://sbc.io/hosts/hosts";
|
||||
description = "URL of the ad and malware blocklist host file";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
extraBlocklists = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
description = "Additional blocklist URLs to be added to the configuration";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# Assertions for proper configuration
|
||||
assertions = [
|
||||
{
|
||||
assertion = !(cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && config.services.resolved.enable && cfg.downstreams.host.disableSystemdResolved);
|
||||
message = "crab-hole host downstream cannot use port 53 while systemd-resolved is enabled. Either disable systemd-resolved or use a different port.";
|
||||
}
|
||||
{
|
||||
assertion = !(cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && !cfg.downstreams.host.disableSystemdResolved && config.services.resolved.enable);
|
||||
message = "crab-hole host downstream is configured to use port 53 but systemd-resolved is still enabled and disableSystemdResolved is false. Set disableSystemdResolved = true or manually disable systemd-resolved.";
|
||||
}
|
||||
];
|
||||
|
||||
# Automatically disable systemd-resolved if using port 53
|
||||
services.resolved.enable = lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && cfg.downstreams.host.disableSystemdResolved) (lib.mkForce false);
|
||||
|
||||
# Configure DNS nameservers when disabling systemd-resolved
|
||||
networking.nameservers = lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && cfg.downstreams.host.disableSystemdResolved) (lib.mkDefault ["127.0.0.1" "1.1.1.1" "8.8.8.8"]);
|
||||
|
||||
services.crab-hole.settings = lib.mkMerge [
|
||||
{
|
||||
api = {
|
||||
|
|
@ -91,13 +136,17 @@ in {
|
|||
};
|
||||
downstream = cfg.extraDownstreams;
|
||||
upstream.name_servers = cfg.extraUpstreams;
|
||||
blocklist.lists = cfg.extraBlocklists;
|
||||
}
|
||||
(lib.mkIf cfg.downstreams.loopback.enable {
|
||||
(lib.mkIf cfg.blocklists.ad_malware.enable {
|
||||
blocklist.lists = [cfg.blocklists.ad_malware.url];
|
||||
})
|
||||
(lib.mkIf cfg.downstreams.host.enable {
|
||||
downstream = [
|
||||
{
|
||||
protocol = "udp";
|
||||
listen = "localhost";
|
||||
port = 53;
|
||||
listen = "0.0.0.0";
|
||||
port = cfg.downstreams.host.port;
|
||||
}
|
||||
];
|
||||
})
|
||||
|
|
@ -136,8 +185,8 @@ in {
|
|||
(lib.mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [cfg.port];
|
||||
})
|
||||
(lib.mkIf (cfg.downstreams.loopback.enable && cfg.downstreams.loopback.openFirewall) {
|
||||
allowedUDPPorts = [53];
|
||||
(lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.openFirewall) {
|
||||
allowedUDPPorts = [cfg.downstreams.host.port];
|
||||
})
|
||||
];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,14 @@
|
|||
}: let
|
||||
workingDirectory = "/var/lib/private/crab-hole";
|
||||
in {
|
||||
config = lib.mkIf (config.services.immich.enable && config.host.impermanence.enable) {
|
||||
options.services.crab-hole = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.crab-hole.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.crab-hole.impermanence.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion =
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
{...}: {
|
||||
imports = [
|
||||
./reverse_proxy.nix
|
||||
./fail2ban.nix
|
||||
./postgres.nix
|
||||
./reverseProxy
|
||||
./fail2ban
|
||||
./postgres
|
||||
./network_storage
|
||||
./podman.nix
|
||||
|
||||
./actual
|
||||
./bazarr
|
||||
|
|
@ -18,7 +17,7 @@
|
|||
./lidarr
|
||||
./panoramax
|
||||
./paperless
|
||||
./qbittorent.nix
|
||||
./qbittorent
|
||||
./radarr
|
||||
./searx
|
||||
./sonarr
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
];
|
||||
};
|
||||
})
|
||||
]);
|
||||
}
|
||||
6
modules/nixos-modules/server/fail2ban/default.nix
Normal file
6
modules/nixos-modules/server/fail2ban/default.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{...}: {
|
||||
imports = [
|
||||
./fail2ban.nix
|
||||
./impermanence.nix
|
||||
];
|
||||
}
|
||||
51
modules/nixos-modules/server/fail2ban/fail2ban.nix
Normal file
51
modules/nixos-modules/server/fail2ban/fail2ban.nix
Normal 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 {};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
34
modules/nixos-modules/server/fail2ban/impermanence.nix
Normal file
34
modules/nixos-modules/server/fail2ban/impermanence.nix
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
dataFolder = "/var/lib/fail2ban";
|
||||
dataFile = "fail2ban.sqlite3";
|
||||
in {
|
||||
options.services.fail2ban = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.fail2ban.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.fail2ban.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";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
{...}: {
|
||||
imports = [
|
||||
./proxy.nix
|
||||
./impermanence.nix
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,14 @@
|
|||
config,
|
||||
...
|
||||
}: {
|
||||
config = lib.mkIf (config.services.flaresolverr.enable && config.host.impermanence.enable) {
|
||||
options.services.flaresolverr = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.flaresolverr.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.flaresolverr.impermanence.enable {
|
||||
# FlareSolverr typically doesn't need persistent storage as it's a proxy service
|
||||
# but we'll add basic structure in case it's needed for logs or configuration
|
||||
environment.persistence."/persist/system/root" = {
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
options.services.flaresolverr = {
|
||||
subdomain = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Subdomain for reverse proxy. If null, service will be local only.";
|
||||
};
|
||||
extraSubdomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
description = "Extra subdomains for reverse proxy.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.flaresolverr.enable && config.services.flaresolverr.subdomain != null) {
|
||||
host.reverse_proxy.subdomains.flaresolverr = {
|
||||
subdomain = config.services.flaresolverr.subdomain;
|
||||
extraSubdomains = config.services.flaresolverr.extraSubdomains;
|
||||
target = "http://127.0.0.1:${toString config.services.flaresolverr.port}";
|
||||
websockets.enable = true;
|
||||
forwardHeaders.enable = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -2,40 +2,31 @@
|
|||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
config = lib.mkIf config.services.forgejo.enable (
|
||||
lib.mkMerge [
|
||||
}: let
|
||||
usingPostgres = config.services.forgejo.database.type == "postgres";
|
||||
in {
|
||||
config = lib.mkIf config.services.forgejo.enable {
|
||||
assertions = [
|
||||
{
|
||||
host = {
|
||||
postgres = {
|
||||
enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.forgejo.settings.database.DB_TYPE == "postgres";
|
||||
message = "Forgejo database type must be postgres";
|
||||
}
|
||||
];
|
||||
assertion = !usingPostgres || config.services.postgresql.enable;
|
||||
message = "PostgreSQL must be enabled when Forgejo database type is postgres";
|
||||
}
|
||||
(lib.mkIf config.host.postgres.enable {
|
||||
host = {
|
||||
postgres = {
|
||||
extraUsers = {
|
||||
forgejo = {
|
||||
isClient = true;
|
||||
createUser = true;
|
||||
};
|
||||
};
|
||||
extraDatabases = {
|
||||
forgejo = {
|
||||
name = "forgejo";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
]
|
||||
);
|
||||
{
|
||||
assertion = !(usingPostgres && config.services.forgejo.database.createDatabase) || (builtins.any (db: db == "forgejo") config.services.postgresql.ensureDatabases);
|
||||
message = "Forgejo built-in database creation failed - expected 'forgejo' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}";
|
||||
}
|
||||
{
|
||||
assertion = !(usingPostgres && config.services.forgejo.database.createDatabase) || (builtins.any (user: user.name == "forgejo") config.services.postgresql.ensureUsers);
|
||||
message = "Forgejo built-in user creation failed - expected user 'forgejo' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}";
|
||||
}
|
||||
];
|
||||
|
||||
services.forgejo.database.createDatabase = lib.mkDefault usingPostgres;
|
||||
|
||||
systemd.services.forgejo = lib.mkIf usingPostgres {
|
||||
requires = [
|
||||
config.systemd.services.postgresql.name
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,9 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
const = import ./const.nix;
|
||||
httpPort = const.httpPort;
|
||||
sshPort = const.sshPort;
|
||||
db_user = "forgejo";
|
||||
in {
|
||||
imports = [
|
||||
./forgejo.nix
|
||||
./proxy.nix
|
||||
./database.nix
|
||||
./fail2ban.nix
|
||||
./impermanence.nix
|
||||
];
|
||||
|
||||
config = lib.mkIf config.services.forgejo.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.forgejo.settings.server.BUILTIN_SSH_SERVER_USER == config.users.users.git.name;
|
||||
message = "Forgejo BUILTIN_SSH_SERVER_USER hardcoded value does not match expected git user name";
|
||||
}
|
||||
];
|
||||
|
||||
services.forgejo = {
|
||||
database = {
|
||||
type = "postgres";
|
||||
socket = "/run/postgresql";
|
||||
};
|
||||
lfs.enable = true;
|
||||
settings = {
|
||||
server = {
|
||||
DOMAIN = "${config.services.forgejo.subdomain}.${config.host.reverse_proxy.hostname}";
|
||||
HTTP_PORT = httpPort;
|
||||
START_SSH_SERVER = true;
|
||||
SSH_LISTEN_PORT = sshPort;
|
||||
SSH_PORT = 22;
|
||||
BUILTIN_SSH_SERVER_USER = "git";
|
||||
ROOT_URL = "https://git.jan-leila.com";
|
||||
};
|
||||
service = {
|
||||
DISABLE_REGISTRATION = true;
|
||||
};
|
||||
database = {
|
||||
DB_TYPE = "postgres";
|
||||
NAME = db_user;
|
||||
USER = db_user;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,16 @@
|
|||
pkgs,
|
||||
...
|
||||
}: {
|
||||
config = lib.mkIf (config.services.forgejo.enable && config.services.fail2ban.enable) {
|
||||
options.services.forgejo = {
|
||||
fail2ban = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.forgejo.enable && config.services.fail2ban.enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.forgejo.fail2ban.enable {
|
||||
environment.etc = {
|
||||
"fail2ban/filter.d/forgejo.local".text = lib.mkIf config.services.forgejo.enable (
|
||||
pkgs.lib.mkDefault (pkgs.lib.mkAfter ''
|
||||
|
|
|
|||
46
modules/nixos-modules/server/forgejo/forgejo.nix
Normal file
46
modules/nixos-modules/server/forgejo/forgejo.nix
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
const = import ./const.nix;
|
||||
httpPort = const.httpPort;
|
||||
sshPort = const.sshPort;
|
||||
db_user = "forgejo";
|
||||
in {
|
||||
config = lib.mkIf config.services.forgejo.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.forgejo.settings.server.BUILTIN_SSH_SERVER_USER == config.users.users.git.name;
|
||||
message = "Forgejo BUILTIN_SSH_SERVER_USER hardcoded value does not match expected git user name";
|
||||
}
|
||||
];
|
||||
|
||||
services.forgejo = {
|
||||
database = {
|
||||
type = "postgres";
|
||||
socket = "/run/postgresql";
|
||||
};
|
||||
lfs.enable = true;
|
||||
settings = {
|
||||
server = {
|
||||
DOMAIN = config.services.forgejo.reverseProxy.domain;
|
||||
HTTP_PORT = httpPort;
|
||||
START_SSH_SERVER = true;
|
||||
SSH_LISTEN_PORT = sshPort;
|
||||
SSH_PORT = 22;
|
||||
BUILTIN_SSH_SERVER_USER = "git";
|
||||
ROOT_URL = "https://git.jan-leila.com";
|
||||
};
|
||||
service = {
|
||||
DISABLE_REGISTRATION = true;
|
||||
};
|
||||
database = {
|
||||
DB_TYPE = "postgres";
|
||||
NAME = db_user;
|
||||
USER = db_user;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -5,7 +5,14 @@
|
|||
}: let
|
||||
stateDir = "/var/lib/forgejo";
|
||||
in {
|
||||
config = lib.mkIf (config.services.forgejo.enable && config.host.impermanence.enable) {
|
||||
options.services.forgejo = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.forgejo.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.forgejo.impermanence.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.forgejo.stateDir == stateDir;
|
||||
|
|
|
|||
|
|
@ -7,16 +7,33 @@
|
|||
httpPort = const.httpPort;
|
||||
in {
|
||||
options.services.forgejo = {
|
||||
subdomain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "subdomain of base domain that forgejo will be hosted at";
|
||||
default = "forgejo";
|
||||
reverseProxy = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.forgejo.enable && config.services.reverseProxy.enable;
|
||||
};
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "domain that forgejo will be hosted at";
|
||||
default = "git.jan-leila.com";
|
||||
};
|
||||
extraDomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "extra domains that should be configured for forgejo";
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.forgejo.enable && config.host.reverse_proxy.enable) {
|
||||
host.reverse_proxy.subdomains.${config.services.forgejo.subdomain} = {
|
||||
config = lib.mkIf config.services.forgejo.reverseProxy.enable {
|
||||
services.reverseProxy.services.forgejo = {
|
||||
target = "http://localhost:${toString httpPort}";
|
||||
domain = config.services.forgejo.reverseProxy.domain;
|
||||
extraDomains = config.services.forgejo.reverseProxy.extraDomains;
|
||||
|
||||
settings = {
|
||||
forwardHeaders.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
|
|
|
|||
|
|
@ -2,55 +2,52 @@
|
|||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
dbUser = "hass";
|
||||
in {
|
||||
config = lib.mkIf config.services.home-assistant.enable (
|
||||
lib.mkMerge [
|
||||
}: {
|
||||
options.services.home-assistant = {
|
||||
postgres = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Use PostgreSQL instead of SQLite";
|
||||
};
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "hass";
|
||||
description = "Database user name";
|
||||
};
|
||||
database = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "hass";
|
||||
description = "Database name";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.home-assistant.enable {
|
||||
assertions = [
|
||||
{
|
||||
host = {
|
||||
postgres = {
|
||||
enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.home-assistant.database == "postgres";
|
||||
message = "Home Assistant database type must be postgres";
|
||||
}
|
||||
];
|
||||
assertion = !config.services.home-assistant.postgres.enable || config.services.postgresql.enable;
|
||||
message = "PostgreSQL must be enabled when using postgres database for Home Assistant";
|
||||
}
|
||||
(lib.mkIf config.host.postgres.enable {
|
||||
host = {
|
||||
postgres = {
|
||||
extraUsers = {
|
||||
${dbUser} = {
|
||||
isClient = true;
|
||||
createUser = true;
|
||||
};
|
||||
};
|
||||
extraDatabases = {
|
||||
${dbUser} = {
|
||||
name = dbUser;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
];
|
||||
|
||||
services.home-assistant = {
|
||||
extraPackages = python3Packages:
|
||||
with python3Packages; [
|
||||
psycopg2
|
||||
];
|
||||
};
|
||||
services.postgresql.databases.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable {
|
||||
enable = true;
|
||||
user = config.services.home-assistant.postgres.user;
|
||||
database = config.services.home-assistant.postgres.database;
|
||||
};
|
||||
|
||||
systemd.services.home-assistant = {
|
||||
requires = [
|
||||
config.systemd.services.postgresql.name
|
||||
];
|
||||
};
|
||||
})
|
||||
]
|
||||
);
|
||||
services.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable {
|
||||
extraPackages = python3Packages:
|
||||
with python3Packages; [
|
||||
psycopg2
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable {
|
||||
requires = [
|
||||
config.systemd.services.postgresql.name
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,112 +1,10 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
imports = [
|
||||
./home-assistant.nix
|
||||
./proxy.nix
|
||||
./database.nix
|
||||
./fail2ban.nix
|
||||
./impermanence.nix
|
||||
./extensions
|
||||
];
|
||||
|
||||
options.services.home-assistant = {
|
||||
database = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"builtin"
|
||||
"postgres"
|
||||
];
|
||||
description = "what database do we want to use";
|
||||
default = "builtin";
|
||||
};
|
||||
|
||||
extensions = {
|
||||
sonos = {
|
||||
enable = lib.mkEnableOption "enable the sonos plugin";
|
||||
port = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 1400;
|
||||
description = "what port to use for sonos discovery";
|
||||
};
|
||||
};
|
||||
jellyfin = {
|
||||
enable = lib.mkEnableOption "enable the jellyfin plugin";
|
||||
};
|
||||
wyoming = {
|
||||
enable = lib.mkEnableOption "enable wyoming";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.home-assistant.enable (lib.mkMerge [
|
||||
{
|
||||
services.home-assistant = {
|
||||
configDir = "/var/lib/hass";
|
||||
extraComponents = [
|
||||
"default_config"
|
||||
"esphome"
|
||||
"met"
|
||||
"radio_browser"
|
||||
"isal"
|
||||
"zha"
|
||||
"webostv"
|
||||
"tailscale"
|
||||
"syncthing"
|
||||
"analytics_insights"
|
||||
"unifi"
|
||||
"openweathermap"
|
||||
"ollama"
|
||||
"mobile_app"
|
||||
"logbook"
|
||||
"ssdp"
|
||||
"usb"
|
||||
"webhook"
|
||||
"bluetooth"
|
||||
"dhcp"
|
||||
"energy"
|
||||
"history"
|
||||
"backup"
|
||||
"assist_pipeline"
|
||||
"conversation"
|
||||
"sun"
|
||||
"zeroconf"
|
||||
"cpuspeed"
|
||||
];
|
||||
config = {
|
||||
http = {
|
||||
server_port = 8123;
|
||||
use_x_forwarded_for = true;
|
||||
trusted_proxies = ["127.0.0.1" "::1"];
|
||||
ip_ban_enabled = true;
|
||||
login_attempts_threshold = 10;
|
||||
};
|
||||
homeassistant = {
|
||||
external_url = "https://${config.services.home-assistant.subdomain}.${config.host.reverse_proxy.hostname}";
|
||||
# internal_url = "http://192.168.1.2:8123";
|
||||
};
|
||||
recorder.db_url = "postgresql://@/${config.services.home-assistant.configDir}";
|
||||
"automation manual" = [];
|
||||
"automation ui" = "!include automations.yaml";
|
||||
mobile_app = {};
|
||||
};
|
||||
extraPackages = python3Packages:
|
||||
with python3Packages; [
|
||||
hassil
|
||||
numpy
|
||||
gtts
|
||||
];
|
||||
};
|
||||
|
||||
# TODO: configure /var/lib/hass/secrets.yaml via sops
|
||||
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
1900
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"f ${config.services.home-assistant.configDir}/automations.yaml 0755 hass hass"
|
||||
];
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,36 +3,46 @@
|
|||
pkgs,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
lib.mkIf (config.services.fail2ban.enable && config.services.home-assistant.enable) {
|
||||
environment.etc = {
|
||||
"fail2ban/filter.d/hass.local".text = (
|
||||
pkgs.lib.mkDefault (pkgs.lib.mkAfter ''
|
||||
[INCLUDES]
|
||||
before = common.conf
|
||||
|
||||
[Definition]
|
||||
failregex = ^%(__prefix_line)s.*Login attempt or request with invalid authentication from <HOST>.*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
[Init]
|
||||
datepattern = ^%%Y-%%m-%%d %%H:%%M:%%S
|
||||
'')
|
||||
);
|
||||
}: {
|
||||
options.services.home-assistant = {
|
||||
fail2ban = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.fail2ban.enable && config.services.home-assistant.enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.fail2ban = {
|
||||
jails = {
|
||||
home-assistant-iptables.settings = {
|
||||
enabled = true;
|
||||
filter = "hass";
|
||||
action = ''iptables-multiport[name=HTTP, port="http,https"]'';
|
||||
logpath = "${config.services.home-assistant.configDir}/*.log";
|
||||
backend = "auto";
|
||||
findtime = 600;
|
||||
bantime = 600;
|
||||
maxretry = 5;
|
||||
config = lib.mkIf config.services.home-assistant.fail2ban.enable {
|
||||
environment.etc = {
|
||||
"fail2ban/filter.d/hass.local".text = (
|
||||
pkgs.lib.mkDefault (pkgs.lib.mkAfter ''
|
||||
[INCLUDES]
|
||||
before = common.conf
|
||||
|
||||
[Definition]
|
||||
failregex = ^%(__prefix_line)s.*Login attempt or request with invalid authentication from <HOST>.*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
[Init]
|
||||
datepattern = ^%%Y-%%m-%%d %%H:%%M:%%S
|
||||
'')
|
||||
);
|
||||
};
|
||||
|
||||
services.fail2ban = {
|
||||
jails = {
|
||||
home-assistant-iptables.settings = {
|
||||
enabled = true;
|
||||
filter = "hass";
|
||||
action = ''iptables-multiport[name=HTTP, port="http,https"]'';
|
||||
logpath = "${config.services.home-assistant.configDir}/*.log";
|
||||
backend = "auto";
|
||||
findtime = 600;
|
||||
bantime = 600;
|
||||
maxretry = 5;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
104
modules/nixos-modules/server/home-assistant/home-assistant.nix
Normal file
104
modules/nixos-modules/server/home-assistant/home-assistant.nix
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
options.services.home-assistant = {
|
||||
database = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"builtin"
|
||||
"postgres"
|
||||
];
|
||||
description = "what database do we want to use";
|
||||
default = "builtin";
|
||||
};
|
||||
|
||||
extensions = {
|
||||
sonos = {
|
||||
enable = lib.mkEnableOption "enable the sonos plugin";
|
||||
port = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 1400;
|
||||
description = "what port to use for sonos discovery";
|
||||
};
|
||||
};
|
||||
jellyfin = {
|
||||
enable = lib.mkEnableOption "enable the jellyfin plugin";
|
||||
};
|
||||
wyoming = {
|
||||
enable = lib.mkEnableOption "enable wyoming";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.home-assistant.enable (lib.mkMerge [
|
||||
{
|
||||
services.home-assistant = {
|
||||
configDir = "/var/lib/hass";
|
||||
extraComponents = [
|
||||
"default_config"
|
||||
"esphome"
|
||||
"met"
|
||||
"radio_browser"
|
||||
"isal"
|
||||
"zha"
|
||||
"webostv"
|
||||
"tailscale"
|
||||
"syncthing"
|
||||
"analytics_insights"
|
||||
"unifi"
|
||||
"openweathermap"
|
||||
"ollama"
|
||||
"mobile_app"
|
||||
"logbook"
|
||||
"ssdp"
|
||||
"usb"
|
||||
"webhook"
|
||||
"bluetooth"
|
||||
"dhcp"
|
||||
"energy"
|
||||
"history"
|
||||
"backup"
|
||||
"assist_pipeline"
|
||||
"conversation"
|
||||
"sun"
|
||||
"zeroconf"
|
||||
"cpuspeed"
|
||||
];
|
||||
config = {
|
||||
http = {
|
||||
server_port = 8123;
|
||||
use_x_forwarded_for = true;
|
||||
trusted_proxies = ["127.0.0.1" "::1"];
|
||||
ip_ban_enabled = true;
|
||||
login_attempts_threshold = 10;
|
||||
};
|
||||
homeassistant = {
|
||||
external_url = "https://${config.services.home-assistant.domain}";
|
||||
# internal_url = "http://192.168.1.2:8123";
|
||||
};
|
||||
recorder.db_url = "postgresql://@/${config.services.home-assistant.configDir}";
|
||||
"automation manual" = [];
|
||||
"automation ui" = "!include automations.yaml";
|
||||
mobile_app = {};
|
||||
};
|
||||
extraPackages = python3Packages:
|
||||
with python3Packages; [
|
||||
hassil
|
||||
numpy
|
||||
gtts
|
||||
];
|
||||
};
|
||||
|
||||
# TODO: configure /var/lib/hass/secrets.yaml via sops
|
||||
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
1900
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"f ${config.services.home-assistant.configDir}/automations.yaml 0755 hass hass"
|
||||
];
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
|
@ -4,29 +4,39 @@
|
|||
...
|
||||
}: {
|
||||
options.services.home-assistant = {
|
||||
subdomain = lib.mkOption {
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "subdomain of base domain that home-assistant will be hosted at";
|
||||
default = "home-assistant";
|
||||
description = "domain that home-assistant will be hosted at";
|
||||
default = "home-assistant.arpa";
|
||||
};
|
||||
extraDomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "extra domains that should be configured for home-assistant";
|
||||
default = [];
|
||||
};
|
||||
reverseProxy = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.reverseProxy.enable && config.services.home-assistant.enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.host.reverse_proxy.enable && config.services.home-assistant.enable) {
|
||||
host = {
|
||||
reverse_proxy.subdomains.${config.services.home-assistant.subdomain} = {
|
||||
target = "http://localhost:${toString config.services.home-assistant.config.http.server_port}";
|
||||
config = lib.mkIf config.services.home-assistant.reverseProxy.enable {
|
||||
services.reverseProxy.services.home-assistant = {
|
||||
target = "http://localhost:${toString config.services.home-assistant.config.http.server_port}";
|
||||
domain = config.services.home-assistant.domain;
|
||||
extraDomains = config.services.home-assistant.extraDomains;
|
||||
|
||||
websockets.enable = true;
|
||||
settings = {
|
||||
proxyWebsockets.enable = true;
|
||||
forwardHeaders.enable = true;
|
||||
|
||||
extraConfig = ''
|
||||
add_header Upgrade $http_upgrade;
|
||||
add_header Connection \"upgrade\";
|
||||
|
||||
proxy_buffering off;
|
||||
|
||||
proxy_read_timeout 90;
|
||||
'';
|
||||
# Custom timeout settings
|
||||
proxyHeaders = {
|
||||
enable = true;
|
||||
timeout = 90;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,24 +3,28 @@
|
|||
config,
|
||||
...
|
||||
}: {
|
||||
config = lib.mkIf config.services.immich.enable (lib.mkMerge [
|
||||
{
|
||||
host = {
|
||||
postgres = {
|
||||
enable = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
(lib.mkIf config.host.postgres.enable {
|
||||
host = {
|
||||
postgres = {
|
||||
extraUsers = {
|
||||
${config.services.immich.database.user} = {
|
||||
isClient = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
]);
|
||||
config = lib.mkIf config.services.immich.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = !config.services.immich.database.enable || config.services.postgresql.enable;
|
||||
message = "PostgreSQL must be enabled when using postgres database for Immich";
|
||||
}
|
||||
{
|
||||
assertion = !(config.services.immich.database.enable && config.services.immich.database.createDB) || (builtins.any (db: db == "immich") config.services.postgresql.ensureDatabases);
|
||||
message = "Immich built-in database creation failed - expected 'immich' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}";
|
||||
}
|
||||
{
|
||||
assertion = !(config.services.immich.database.enable && config.services.immich.database.createDB) || (builtins.any (user: user.name == "immich") config.services.postgresql.ensureUsers);
|
||||
message = "Immich built-in user creation failed - expected user 'immich' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}";
|
||||
}
|
||||
];
|
||||
|
||||
# Note: Immich has built-in database creation via services.immich.database.createDB we only add the systemd dependency
|
||||
|
||||
systemd.services.immich-server = lib.mkIf config.services.immich.database.enable {
|
||||
requires = [
|
||||
config.systemd.services.postgresql.name
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,16 @@
|
|||
pkgs,
|
||||
...
|
||||
}: {
|
||||
config = lib.mkIf (config.services.fail2ban.enable && config.services.immich.enable) {
|
||||
options.services.immich = {
|
||||
fail2ban = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.fail2ban.enable && config.services.immich.enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.immich.fail2ban.enable {
|
||||
environment.etc = {
|
||||
"fail2ban/filter.d/immich.local".text = pkgs.lib.mkDefault (pkgs.lib.mkAfter ''
|
||||
[Definition]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,14 @@
|
|||
}: let
|
||||
mediaLocation = "/var/lib/immich";
|
||||
in {
|
||||
config = lib.mkIf (config.services.immich.enable && config.host.impermanence.enable) {
|
||||
options.services.immich = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.immich.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.immich.impermanence.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.immich.mediaLocation == mediaLocation;
|
||||
|
|
|
|||
|
|
@ -4,31 +4,40 @@
|
|||
...
|
||||
}: {
|
||||
options.services.immich = {
|
||||
subdomain = lib.mkOption {
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "subdomain of base domain that immich will be hosted at";
|
||||
default = "immich";
|
||||
description = "domain that immich will be hosted at";
|
||||
default = "immich.arpa";
|
||||
};
|
||||
extraDomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "extra domains that should be configured for immich";
|
||||
default = [];
|
||||
};
|
||||
reverseProxy = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.immich.enable && config.services.reverseProxy.enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.immich.enable && config.host.reverse_proxy.enable) {
|
||||
host = {
|
||||
reverse_proxy.subdomains.${config.services.immich.subdomain} = {
|
||||
target = "http://localhost:${toString config.services.immich.port}";
|
||||
config = lib.mkIf config.services.immich.reverseProxy.enable {
|
||||
services.reverseProxy.services.immich = {
|
||||
target = "http://localhost:${toString config.services.immich.port}";
|
||||
domain = config.services.immich.domain;
|
||||
extraDomains = config.services.immich.extraDomains;
|
||||
|
||||
websockets.enable = true;
|
||||
settings = {
|
||||
proxyWebsockets.enable = true;
|
||||
forwardHeaders.enable = true;
|
||||
maxBodySize = 50000;
|
||||
|
||||
extraConfig = ''
|
||||
# allow large file uploads
|
||||
client_max_body_size 50000M;
|
||||
|
||||
# set timeout
|
||||
proxy_read_timeout 600s;
|
||||
proxy_send_timeout 600s;
|
||||
send_timeout 600s;
|
||||
proxy_redirect off;
|
||||
'';
|
||||
# Custom timeout settings
|
||||
proxyHeaders = {
|
||||
enable = true;
|
||||
timeout = 600;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{...}: {
|
||||
imports = [
|
||||
./proxy.nix
|
||||
./impermanence.nix
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,14 @@
|
|||
}: let
|
||||
jackett_data_directory = "/var/lib/jackett/.config/Jackett";
|
||||
in {
|
||||
config = lib.mkIf (config.services.jackett.enable && config.host.impermanence.enable) {
|
||||
options.services.jackett = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.jackett.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.jackett.impermanence.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.jackett.dataDir == jackett_data_directory;
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
options.services.jackett = {
|
||||
subdomain = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Subdomain for reverse proxy. If null, service will be local only.";
|
||||
};
|
||||
extraSubdomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
description = "Extra subdomains for reverse proxy.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.jackett.enable && config.services.jackett.subdomain != null) {
|
||||
host.reverse_proxy.subdomains.jackett = {
|
||||
subdomain = config.services.jackett.subdomain;
|
||||
extraSubdomains = config.services.jackett.extraSubdomains;
|
||||
target = "http://127.0.0.1:9117";
|
||||
websockets.enable = true;
|
||||
forwardHeaders.enable = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,38 +1,8 @@
|
|||
{
|
||||
lib,
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
jellyfinPort = 8096;
|
||||
dlanPort = 1900;
|
||||
in {
|
||||
imports = [
|
||||
./jellyfin.nix
|
||||
./proxy.nix
|
||||
./fail2ban.nix
|
||||
./impermanence.nix
|
||||
];
|
||||
|
||||
options.services.jellyfin = {
|
||||
media_directory = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "directory jellyfin media will be hosted at";
|
||||
default = "/srv/jellyfin/media";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.jellyfin.enable {
|
||||
environment.systemPackages = [
|
||||
pkgs.jellyfin
|
||||
pkgs.jellyfin-web
|
||||
pkgs.jellyfin-ffmpeg
|
||||
];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [jellyfinPort dlanPort];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${config.services.jellyfin.media_directory} 2770 jellyfin jellyfin_media"
|
||||
"A ${config.services.jellyfin.media_directory} - - - - u:jellyfin:rwX,g:jellyfin_media:rwX,o::-"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,14 @@
|
|||
jellyfin_data_directory = "/var/lib/jellyfin";
|
||||
jellyfin_cache_directory = "/var/cache/jellyfin";
|
||||
in {
|
||||
config = lib.mkIf (config.services.jellyfin.enable && config.host.impermanence.enable) {
|
||||
options.services.jellyfin = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.jellyfin.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.jellyfin.impermanence.enable {
|
||||
fileSystems."/persist/system/jellyfin".neededForBoot = true;
|
||||
|
||||
host.storage.pool.extraDatasets = {
|
||||
|
|
|
|||
32
modules/nixos-modules/server/jellyfin/jellyfin.nix
Normal file
32
modules/nixos-modules/server/jellyfin/jellyfin.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
lib,
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
jellyfinPort = 8096;
|
||||
dlanPort = 1900;
|
||||
in {
|
||||
options.services.jellyfin = {
|
||||
media_directory = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "directory jellyfin media will be hosted at";
|
||||
default = "/srv/jellyfin/media";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.jellyfin.enable {
|
||||
environment.systemPackages = [
|
||||
pkgs.jellyfin
|
||||
pkgs.jellyfin-web
|
||||
pkgs.jellyfin-ffmpeg
|
||||
];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [jellyfinPort dlanPort];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${config.services.jellyfin.media_directory} 2770 jellyfin jellyfin_media"
|
||||
"A ${config.services.jellyfin.media_directory} - - - - u:jellyfin:rwX,g:jellyfin_media:rwX,o::-"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
@ -6,33 +6,36 @@
|
|||
jellyfinPort = 8096;
|
||||
in {
|
||||
options.services.jellyfin = {
|
||||
subdomain = lib.mkOption {
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "subdomain of base domain that jellyfin will be hosted at";
|
||||
default = "jellyfin";
|
||||
description = "domain that jellyfin will be hosted at";
|
||||
default = "jellyfin.arpa";
|
||||
};
|
||||
extraSubdomains = lib.mkOption {
|
||||
extraDomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "ex subdomain of base domain that jellyfin will be hosted at";
|
||||
description = "extra domains that should be configured for jellyfin";
|
||||
default = [];
|
||||
};
|
||||
reverseProxy = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.jellyfin.enable && config.services.reverseProxy.enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.jellyfin.enable && config.host.reverse_proxy.enable) {
|
||||
host.reverse_proxy.subdomains.jellyfin = {
|
||||
config = lib.mkIf config.services.jellyfin.reverseProxy.enable {
|
||||
services.reverseProxy.services.jellyfin = {
|
||||
target = "http://localhost:${toString jellyfinPort}";
|
||||
domain = config.services.jellyfin.domain;
|
||||
extraDomains = config.services.jellyfin.extraDomains;
|
||||
|
||||
subdomain = config.services.jellyfin.subdomain;
|
||||
extraSubdomains = config.services.jellyfin.extraSubdomains;
|
||||
|
||||
forwardHeaders.enable = true;
|
||||
|
||||
extraConfig = ''
|
||||
client_max_body_size 20M;
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
proxy_buffering off;
|
||||
'';
|
||||
settings = {
|
||||
forwardHeaders.enable = true;
|
||||
maxBodySize = 20;
|
||||
noSniff.enable = true;
|
||||
proxyBuffering.enable = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{...}: {
|
||||
imports = [
|
||||
./proxy.nix
|
||||
./impermanence.nix
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,14 @@
|
|||
}: let
|
||||
lidarr_data_directory = "/var/lib/lidarr/.config/Lidarr";
|
||||
in {
|
||||
config = lib.mkIf (config.services.lidarr.enable && config.host.impermanence.enable) {
|
||||
options.services.lidarr = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.lidarr.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.lidarr.impermanence.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.lidarr.dataDir == lidarr_data_directory;
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
options.services.lidarr = {
|
||||
subdomain = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Subdomain for reverse proxy. If null, service will be local only.";
|
||||
};
|
||||
extraSubdomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
description = "Extra subdomains for reverse proxy.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.lidarr.enable && config.services.lidarr.subdomain != null) {
|
||||
host.reverse_proxy.subdomains.lidarr = {
|
||||
subdomain = config.services.lidarr.subdomain;
|
||||
extraSubdomains = config.services.lidarr.extraSubdomains;
|
||||
target = "http://127.0.0.1:8686";
|
||||
websockets.enable = true;
|
||||
forwardHeaders.enable = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,90 +1,6 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
export_directory = config.host.network_storage.export_directory;
|
||||
in {
|
||||
imports = [
|
||||
./network_storage.nix
|
||||
./nfs.nix
|
||||
];
|
||||
|
||||
options = {
|
||||
host.network_storage = {
|
||||
enable = lib.mkEnableOption "is this machine going to export network storage";
|
||||
export_directory = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "what are exports going to be stored in";
|
||||
default = "/exports";
|
||||
};
|
||||
directories = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.submodule ({config, ...}: {
|
||||
options = {
|
||||
folder = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what is the name of this export directory";
|
||||
};
|
||||
bind = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
description = "is this directory bound to anywhere";
|
||||
default = null;
|
||||
};
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what user owns this directory";
|
||||
default = "nouser";
|
||||
};
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what group owns this directory";
|
||||
default = "nogroup";
|
||||
};
|
||||
_directory = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.path;
|
||||
default = "${export_directory}/${config.folder}";
|
||||
};
|
||||
};
|
||||
}));
|
||||
description = "list of directory names to export";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.host.network_storage.enable (lib.mkMerge [
|
||||
{
|
||||
# create any folders that we need to have for our exports
|
||||
systemd.tmpfiles.rules =
|
||||
[
|
||||
"d ${config.host.network_storage.export_directory} 2775 nobody nogroup -"
|
||||
]
|
||||
++ (
|
||||
builtins.map (
|
||||
directory: "d ${directory._directory} 2770 ${directory.user} ${directory.group}"
|
||||
)
|
||||
config.host.network_storage.directories
|
||||
);
|
||||
|
||||
# set up any bind mounts that we need for our exports
|
||||
fileSystems = builtins.listToAttrs (
|
||||
builtins.map (directory:
|
||||
lib.attrsets.nameValuePair directory._directory {
|
||||
device = directory.bind;
|
||||
options = ["bind"];
|
||||
}) (
|
||||
builtins.filter (directory: directory.bind != null) config.host.network_storage.directories
|
||||
)
|
||||
);
|
||||
}
|
||||
# (lib.mkIf config.host.impermanence.enable {
|
||||
# environment.persistence."/persist/system/root" = {
|
||||
# enable = true;
|
||||
# hideMounts = true;
|
||||
# directories = [
|
||||
# config.host.network_storage.export_directory
|
||||
# ];
|
||||
# };
|
||||
# })
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
export_directory = config.host.network_storage.export_directory;
|
||||
in {
|
||||
options = {
|
||||
host.network_storage = {
|
||||
enable = lib.mkEnableOption "is this machine going to export network storage";
|
||||
export_directory = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "what are exports going to be stored in";
|
||||
default = "/exports";
|
||||
};
|
||||
directories = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.submodule ({config, ...}: {
|
||||
options = {
|
||||
folder = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what is the name of this export directory";
|
||||
};
|
||||
bind = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
description = "is this directory bound to anywhere";
|
||||
default = null;
|
||||
};
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what user owns this directory";
|
||||
default = "nouser";
|
||||
};
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what group owns this directory";
|
||||
default = "nogroup";
|
||||
};
|
||||
_directory = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.path;
|
||||
default = "${export_directory}/${config.folder}";
|
||||
};
|
||||
};
|
||||
}));
|
||||
description = "list of directory names to export";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.host.network_storage.enable (lib.mkMerge [
|
||||
{
|
||||
# create any folders that we need to have for our exports
|
||||
systemd.tmpfiles.rules =
|
||||
[
|
||||
"d ${config.host.network_storage.export_directory} 2775 nobody nogroup -"
|
||||
]
|
||||
++ (
|
||||
builtins.map (
|
||||
directory: "d ${directory._directory} 2770 ${directory.user} ${directory.group}"
|
||||
)
|
||||
config.host.network_storage.directories
|
||||
);
|
||||
|
||||
# set up any bind mounts that we need for our exports
|
||||
fileSystems = builtins.listToAttrs (
|
||||
builtins.map (directory:
|
||||
lib.attrsets.nameValuePair directory._directory {
|
||||
device = directory.bind;
|
||||
options = ["bind"];
|
||||
}) (
|
||||
builtins.filter (directory: directory.bind != null) config.host.network_storage.directories
|
||||
)
|
||||
);
|
||||
}
|
||||
# (lib.mkIf config.host.impermanence.enable {
|
||||
# environment.persistence."/persist/system/root" = {
|
||||
# enable = true;
|
||||
# hideMounts = true;
|
||||
# directories = [
|
||||
# config.host.network_storage.export_directory
|
||||
# ];
|
||||
# };
|
||||
# })
|
||||
]);
|
||||
}
|
||||
|
|
@ -3,32 +3,46 @@
|
|||
config,
|
||||
...
|
||||
}: {
|
||||
config = lib.mkIf config.services.panoramax.enable (lib.mkMerge [
|
||||
{
|
||||
host = {
|
||||
postgres = {
|
||||
enable = true;
|
||||
options.services.panoramax = {
|
||||
database = {
|
||||
postgres = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Use PostgreSQL instead of SQLite";
|
||||
};
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "panoramax";
|
||||
description = "Database user name";
|
||||
};
|
||||
database = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "panoramax";
|
||||
description = "Database name";
|
||||
};
|
||||
};
|
||||
}
|
||||
(
|
||||
lib.mkIf config.host.postgres.enable {
|
||||
host = {
|
||||
postgres = {
|
||||
extraUsers = {
|
||||
${config.services.panoramax.database.user} = {
|
||||
isClient = true;
|
||||
createUser = true;
|
||||
};
|
||||
};
|
||||
extraDatabases = {
|
||||
${config.services.panoramax.database.name} = {
|
||||
name = config.services.panoramax.database.user;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.panoramax.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = !config.services.panoramax.database.postgres.enable || config.services.postgresql.enable;
|
||||
message = "PostgreSQL must be enabled when using postgres database for Panoramax";
|
||||
}
|
||||
)
|
||||
]);
|
||||
];
|
||||
|
||||
services.postgresql.databases.panoramax = lib.mkIf config.services.panoramax.database.postgres.enable {
|
||||
enable = true;
|
||||
user = config.services.panoramax.database.postgres.user;
|
||||
database = config.services.panoramax.database.postgres.database;
|
||||
};
|
||||
|
||||
systemd.services.panoramax = lib.mkIf config.services.panoramax.database.postgres.enable {
|
||||
requires = [
|
||||
config.systemd.services.postgresql.name
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,14 @@
|
|||
config,
|
||||
...
|
||||
}: {
|
||||
config = lib.mkIf (config.services.panoramax.enable && config.host.impermanence.enable) {
|
||||
options.services.panoramax = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.panoramax.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.panoramax.impermanence.enable {
|
||||
# TODO: configure impermanence for panoramax data
|
||||
# This would typically include directories like:
|
||||
# - /var/lib/panoramax
|
||||
|
|
|
|||
|
|
@ -4,31 +4,35 @@
|
|||
...
|
||||
}: {
|
||||
options.services.panoramax = {
|
||||
subdomain = lib.mkOption {
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "subdomain of base domain that panoramax will be hosted at";
|
||||
default = "panoramax";
|
||||
description = "domain that panoramax will be hosted at";
|
||||
default = "panoramax.arpa";
|
||||
};
|
||||
extraDomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "extra domains that should be configured for panoramax";
|
||||
default = [];
|
||||
};
|
||||
reverseProxy = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.panoramax.enable && config.services.reverseProxy.enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.panoramax.enable && config.host.reverse_proxy.enable) {
|
||||
host = {
|
||||
reverse_proxy.subdomains.${config.services.panoramax.subdomain} = {
|
||||
target = "http://localhost:${toString config.services.panoramax.port}";
|
||||
config = lib.mkIf config.services.panoramax.reverseProxy.enable {
|
||||
services.reverseProxy.services.panoramax = {
|
||||
target = "http://localhost:${toString config.services.panoramax.port}";
|
||||
domain = config.services.panoramax.domain;
|
||||
extraDomains = config.services.panoramax.extraDomains;
|
||||
|
||||
websockets.enable = true;
|
||||
settings = {
|
||||
proxyWebsockets.enable = true;
|
||||
forwardHeaders.enable = true;
|
||||
|
||||
extraConfig = ''
|
||||
# allow large file uploads for panoramic images
|
||||
client_max_body_size 100M;
|
||||
|
||||
# set timeout for image processing
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
send_timeout 300s;
|
||||
proxy_redirect off;
|
||||
'';
|
||||
maxBodySize = 100000;
|
||||
timeout = 300;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,32 +3,28 @@
|
|||
lib,
|
||||
...
|
||||
}: {
|
||||
config = lib.mkIf config.services.paperless.enable (lib.mkMerge [
|
||||
{
|
||||
host = {
|
||||
postgres = {
|
||||
enable = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
(
|
||||
lib.mkIf config.host.postgres.enable {
|
||||
host = {
|
||||
postgres = {
|
||||
extraUsers = {
|
||||
${config.services.paperless.database.user} = {
|
||||
isClient = true;
|
||||
createUser = true;
|
||||
};
|
||||
};
|
||||
extraDatabases = {
|
||||
${config.services.paperless.database.user} = {
|
||||
name = config.services.paperless.database.user;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
config = lib.mkIf config.services.paperless.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = !config.services.paperless.database.createLocally || config.services.postgresql.enable;
|
||||
message = "PostgreSQL must be enabled when using local postgres database for Paperless";
|
||||
}
|
||||
)
|
||||
]);
|
||||
{
|
||||
assertion = !config.services.paperless.database.createLocally || (builtins.any (db: db == "paperless") config.services.postgresql.ensureDatabases);
|
||||
message = "Paperless built-in database creation failed - expected 'paperless' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}";
|
||||
}
|
||||
{
|
||||
assertion = !config.services.paperless.database.createLocally || (builtins.any (user: user.name == "paperless") config.services.postgresql.ensureUsers);
|
||||
message = "Paperless built-in user creation failed - expected user 'paperless' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}";
|
||||
}
|
||||
];
|
||||
|
||||
services.paperless.database.createLocally = lib.mkDefault true;
|
||||
|
||||
systemd.services.paperless-scheduler = lib.mkIf config.services.paperless.database.createLocally {
|
||||
requires = [
|
||||
config.systemd.services.postgresql.name
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,9 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: {
|
||||
imports = [
|
||||
./paperless.nix
|
||||
./proxy.nix
|
||||
./database.nix
|
||||
./fail2ban.nix
|
||||
./impermanence.nix
|
||||
];
|
||||
|
||||
options.services.paperless = {
|
||||
database = {
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what is the user and database that we are going to use for paperless";
|
||||
default = "paperless";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.paperless.enable {
|
||||
services.paperless = {
|
||||
domain = "${config.services.paperless.subdomain}.${config.host.reverse_proxy.hostname}";
|
||||
configureTika = true;
|
||||
settings = {
|
||||
PAPERLESS_DBENGINE = "postgresql";
|
||||
PAPERLESS_DBHOST = "/run/postgresql";
|
||||
PAPERLESS_DBNAME = config.services.paperless.database.user;
|
||||
PAPERLESS_DBUSER = config.services.paperless.database.user;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,14 @@
|
|||
}: let
|
||||
dataDir = "/var/lib/paperless";
|
||||
in {
|
||||
config = lib.mkIf (config.services.paperless.enable && config.host.impermanence.enable) {
|
||||
options.services.paperless = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.paperless.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.paperless.impermanence.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.paperless.dataDir == dataDir;
|
||||
|
|
|
|||
27
modules/nixos-modules/server/paperless/paperless.nix
Normal file
27
modules/nixos-modules/server/paperless/paperless.nix
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: {
|
||||
options.services.paperless = {
|
||||
database = {
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what is the user and database that we are going to use for paperless";
|
||||
default = "paperless";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.paperless.enable {
|
||||
services.paperless = {
|
||||
configureTika = true;
|
||||
settings = {
|
||||
PAPERLESS_DBENGINE = "postgresql";
|
||||
PAPERLESS_DBHOST = "/run/postgresql";
|
||||
PAPERLESS_DBNAME = config.services.paperless.database.user;
|
||||
PAPERLESS_DBUSER = config.services.paperless.database.user;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -4,25 +4,29 @@
|
|||
...
|
||||
}: {
|
||||
options.services.paperless = {
|
||||
subdomain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "subdomain of base domain that paperless will be hosted at";
|
||||
default = "paperless";
|
||||
extraDomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "extra domains that should be configured for paperless";
|
||||
default = [];
|
||||
};
|
||||
reverseProxy = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.paperless.enable && config.services.reverseProxy.enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.paperless.enable && config.host.reverse_proxy.enable) {
|
||||
host = {
|
||||
reverse_proxy.subdomains.${config.services.paperless.subdomain} = {
|
||||
target = "http://${config.services.paperless.address}:${toString config.services.paperless.port}";
|
||||
config = lib.mkIf config.services.paperless.reverseProxy.enable {
|
||||
services.reverseProxy.services.paperless = {
|
||||
target = "http://${config.services.paperless.address}:${toString config.services.paperless.port}";
|
||||
domain = config.services.paperless.domain;
|
||||
extraDomains = config.services.paperless.extraDomains;
|
||||
|
||||
websockets.enable = true;
|
||||
settings = {
|
||||
proxyWebsockets.enable = true;
|
||||
forwardHeaders.enable = true;
|
||||
|
||||
extraConfig = ''
|
||||
# allow large file uploads
|
||||
client_max_body_size 50000M;
|
||||
'';
|
||||
maxBodySize = 50000;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
options.host.podman = {
|
||||
enable = lib.mkEnableOption "should podman be enabled on this computer";
|
||||
macvlan = {
|
||||
subnet = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Subnet for macvlan address range";
|
||||
};
|
||||
gateway = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Gateway for macvlan";
|
||||
# TODO: see if we can default this to systemd network gateway
|
||||
};
|
||||
networkInterface = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Parent network interface for macvlan";
|
||||
# TODO: see if we can default this some interface?
|
||||
};
|
||||
};
|
||||
};
|
||||
config = lib.mkIf config.host.podman.enable {
|
||||
systemd = {
|
||||
services = {
|
||||
# "podman-network-macvlan" = {
|
||||
# path = [pkgs.podman];
|
||||
# serviceConfig = {
|
||||
# Type = "oneshot";
|
||||
# RemainAfterExit = true;
|
||||
# ExecStop = "podman network rm -f macvlan";
|
||||
# };
|
||||
# script = ''
|
||||
# podman network inspect macvlan || podman network create --driver macvlan --subnet ${config.host.podman.macvlan.subnet} --gateway ${config.host.podman.macvlan.gateway} --opt parent=${config.host.podman.macvlan.networkInterface} macvlan
|
||||
# '';
|
||||
# partOf = ["podman-compose-root.target"];
|
||||
# wantedBy = ["podman-compose-root.target"];
|
||||
# };
|
||||
};
|
||||
# disable computer sleeping
|
||||
targets = {
|
||||
# Root service
|
||||
# When started, this will automatically create all resources and start
|
||||
# the containers. When stopped, this will teardown all resources.
|
||||
"podman-compose-root" = {
|
||||
unitConfig = {
|
||||
Description = "Root target for podman targets.";
|
||||
};
|
||||
wantedBy = ["multi-user.target"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation = {
|
||||
# Runtime
|
||||
podman = {
|
||||
enable = true;
|
||||
autoPrune.enable = true;
|
||||
dockerCompat = true;
|
||||
# defaultNetwork.settings = {
|
||||
# # Required for container networking to be able to use names.
|
||||
# dns_enabled = true;
|
||||
# };
|
||||
};
|
||||
|
||||
oci-containers = {
|
||||
backend = "podman";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
];
|
||||
};
|
||||
})
|
||||
]);
|
||||
}
|
||||
6
modules/nixos-modules/server/postgres/default.nix
Normal file
6
modules/nixos-modules/server/postgres/default.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{...}: {
|
||||
imports = [
|
||||
./postgres.nix
|
||||
./impermanence.nix
|
||||
];
|
||||
}
|
||||
27
modules/nixos-modules/server/postgres/impermanence.nix
Normal file
27
modules/nixos-modules/server/postgres/impermanence.nix
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
dataDir = "/var/lib/postgresql/16";
|
||||
in {
|
||||
config = lib.mkIf (config.services.postgresql.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";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
122
modules/nixos-modules/server/postgres/postgres.nix
Normal file
122
modules/nixos-modules/server/postgres/postgres.nix
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
enabledDatabases = lib.filterAttrs (_: db: db.enable) config.services.postgresql.databases;
|
||||
extraDatabasesList = config.services.postgresql.extraDatabases;
|
||||
|
||||
serviceDatabaseUsers = lib.mapAttrsToList (_: db: {
|
||||
name = db.user;
|
||||
ensureDBOwnership = true;
|
||||
}) (lib.filterAttrs (_: db: db.ensureUser) enabledDatabases);
|
||||
|
||||
extraDatabaseUsers =
|
||||
builtins.map (dbName: {
|
||||
name = dbName;
|
||||
ensureDBOwnership = true;
|
||||
})
|
||||
extraDatabasesList;
|
||||
|
||||
serviceDatabases = lib.mapAttrsToList (_: db: db.database) enabledDatabases;
|
||||
extraDatabaseNames = extraDatabasesList;
|
||||
|
||||
serviceUserMappings = lib.mapAttrsToList (_: db: "user_map ${db.user} ${db.user}") enabledDatabases;
|
||||
extraUserMappings = builtins.map (dbName: "user_map ${dbName} ${dbName}") extraDatabasesList;
|
||||
|
||||
builtinServiceMappings = let
|
||||
forgejoMapping = lib.optional (config.services.forgejo.enable && config.services.forgejo.database.type == "postgres") "user_map forgejo forgejo";
|
||||
immichMapping = lib.optional (config.services.immich.enable && config.services.immich.database.enable) "user_map immich immich";
|
||||
paperlessMapping = lib.optional (config.services.paperless.enable && config.services.paperless.database.createLocally) "user_map paperless paperless";
|
||||
in
|
||||
forgejoMapping ++ immichMapping ++ paperlessMapping;
|
||||
in {
|
||||
options = {
|
||||
services.postgresql = {
|
||||
databases = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
|
||||
options = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to create this database and user";
|
||||
};
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = name;
|
||||
description = "Database user name";
|
||||
};
|
||||
database = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = name;
|
||||
description = "Database name";
|
||||
};
|
||||
ensureUser = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to ensure the user exists";
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = {};
|
||||
description = "Databases to create for services";
|
||||
};
|
||||
|
||||
extraDatabases = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
description = "Additional databases to create (user name will match database name)";
|
||||
example = ["custom_db" "test_db"];
|
||||
};
|
||||
|
||||
adminUsers = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
description = "System users who should have PostgreSQL superuser access";
|
||||
example = ["leyla" "admin"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.postgresql.enable {
|
||||
services = {
|
||||
postgresql = {
|
||||
package = pkgs.postgresql_16;
|
||||
|
||||
ensureUsers =
|
||||
[
|
||||
{name = "postgres";}
|
||||
]
|
||||
++ serviceDatabaseUsers ++ extraDatabaseUsers;
|
||||
|
||||
ensureDatabases = serviceDatabases ++ extraDatabaseNames;
|
||||
|
||||
identMap =
|
||||
''
|
||||
# ArbitraryMapName systemUser DBUser
|
||||
|
||||
# Administration Users
|
||||
superuser_map root postgres
|
||||
superuser_map postgres postgres
|
||||
''
|
||||
+ (
|
||||
lib.strings.concatLines (builtins.map (user: "superuser_map ${user} postgres") config.services.postgresql.adminUsers)
|
||||
)
|
||||
+ ''
|
||||
|
||||
# Client Users
|
||||
''
|
||||
+ (
|
||||
lib.strings.concatLines (serviceUserMappings ++ extraUserMappings ++ builtinServiceMappings)
|
||||
);
|
||||
|
||||
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
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
})
|
||||
]);
|
||||
}
|
||||
6
modules/nixos-modules/server/qbittorent/default.nix
Normal file
6
modules/nixos-modules/server/qbittorent/default.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{...}: {
|
||||
imports = [
|
||||
./qbittorent.nix
|
||||
./impermanence.nix
|
||||
];
|
||||
}
|
||||
61
modules/nixos-modules/server/qbittorent/impermanence.nix
Normal file
61
modules/nixos-modules/server/qbittorent/impermanence.nix
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
qbittorent_profile_directory = "/var/lib/qBittorrent/";
|
||||
in {
|
||||
options.services.qbittorrent = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.qbittorrent.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.qbittorrent.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";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
18
modules/nixos-modules/server/qbittorent/qbittorent.nix
Normal file
18
modules/nixos-modules/server/qbittorent/qbittorent.nix
Normal 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
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
{...}: {
|
||||
imports = [
|
||||
./proxy.nix
|
||||
./impermanence.nix
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,14 @@
|
|||
}: let
|
||||
radarr_data_directory = "/var/lib/radarr/.config/Radarr";
|
||||
in {
|
||||
config = lib.mkIf (config.services.radarr.enable && config.host.impermanence.enable) {
|
||||
options.services.radarr = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.radarr.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.radarr.impermanence.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.radarr.dataDir == radarr_data_directory;
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
options.services.radarr = {
|
||||
subdomain = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Subdomain for reverse proxy. If null, service will be local only.";
|
||||
};
|
||||
extraSubdomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
description = "Extra subdomains for reverse proxy.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.radarr.enable && config.services.radarr.subdomain != null) {
|
||||
host.reverse_proxy.subdomains.radarr = {
|
||||
subdomain = config.services.radarr.subdomain;
|
||||
extraSubdomains = config.services.radarr.extraSubdomains;
|
||||
target = "http://127.0.0.1:7878";
|
||||
websockets.enable = true;
|
||||
forwardHeaders.enable = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
6
modules/nixos-modules/server/reverseProxy/default.nix
Normal file
6
modules/nixos-modules/server/reverseProxy/default.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{...}: {
|
||||
imports = [
|
||||
./reverseProxy.nix
|
||||
./impermanence.nix
|
||||
];
|
||||
}
|
||||
21
modules/nixos-modules/server/reverseProxy/impermanence.nix
Normal file
21
modules/nixos-modules/server/reverseProxy/impermanence.nix
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
dataDir = "/var/lib/acme";
|
||||
in {
|
||||
config = lib.mkIf (config.host.impermanence.enable && config.services.reverseProxy.enable) {
|
||||
environment.persistence."/persist/system/root" = {
|
||||
enable = true;
|
||||
hideMounts = true;
|
||||
directories = [
|
||||
{
|
||||
directory = dataDir;
|
||||
user = "acme";
|
||||
group = "acme";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
176
modules/nixos-modules/server/reverseProxy/reverseProxy.nix
Normal file
176
modules/nixos-modules/server/reverseProxy/reverseProxy.nix
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
options.services.reverseProxy = {
|
||||
enable = lib.mkEnableOption "turn on the reverse proxy";
|
||||
openFirewall = lib.mkEnableOption "open the firewall";
|
||||
refuseUnmatchedDomains = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "refuse connections for domains that don't match any configured virtual hosts";
|
||||
default = true;
|
||||
};
|
||||
ports = {
|
||||
http = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
description = "HTTP port for the reverse proxy";
|
||||
default = 80;
|
||||
};
|
||||
https = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
description = "HTTPS port for the reverse proxy";
|
||||
default = 443;
|
||||
};
|
||||
};
|
||||
acme = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "enable ACME certificate management";
|
||||
default = true;
|
||||
};
|
||||
email = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "email address for ACME certificate registration";
|
||||
};
|
||||
};
|
||||
services = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
|
||||
options = {
|
||||
target = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what url will all traffic to this application be forwarded to";
|
||||
};
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what is the default subdomain to be used for this application to be used for";
|
||||
default = name;
|
||||
};
|
||||
extraDomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "extra domains that should be configured for this domain";
|
||||
default = [];
|
||||
};
|
||||
settings = {
|
||||
certificateRenewal.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "auto renew certificates";
|
||||
default = true;
|
||||
};
|
||||
forceSSL.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "auto renew certificates";
|
||||
default = true;
|
||||
};
|
||||
proxyHeaders = {
|
||||
enable = lib.mkEnableOption "should we proxy headers";
|
||||
timeout = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 60;
|
||||
};
|
||||
};
|
||||
proxyWebsockets.enable = lib.mkEnableOption "should the default config proxy websockets";
|
||||
forwardHeaders.enable = lib.mkEnableOption "should the default config contain forward headers";
|
||||
noSniff.enable = lib.mkEnableOption "should the no sniff flags be set";
|
||||
proxyBuffering.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "should proxy buffering be enabled";
|
||||
default = true;
|
||||
};
|
||||
maxBodySize = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.int;
|
||||
description = "";
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
};
|
||||
|
||||
config = let
|
||||
httpPort = config.services.reverseProxy.ports.http;
|
||||
httpsPort = config.services.reverseProxy.ports.https;
|
||||
in
|
||||
lib.mkIf config.services.reverseProxy.enable {
|
||||
security.acme = lib.mkIf config.services.reverseProxy.acme.enable {
|
||||
acceptTerms = true;
|
||||
defaults.email = config.services.reverseProxy.acme.email;
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts = lib.mkMerge (
|
||||
(lib.optionals config.services.reverseProxy.refuseUnmatchedDomains [
|
||||
{
|
||||
"_" = {
|
||||
default = true;
|
||||
serverName = "_";
|
||||
locations."/" = {
|
||||
extraConfig = ''
|
||||
return 444;
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
])
|
||||
++ lib.lists.flatten (
|
||||
lib.attrsets.mapAttrsToList (
|
||||
name: service: let
|
||||
hostConfig = {
|
||||
forceSSL = service.settings.forceSSL.enable;
|
||||
enableACME = service.settings.certificateRenewal.enable;
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = service.target;
|
||||
proxyWebsockets = service.settings.proxyWebsockets.enable;
|
||||
recommendedProxySettings = service.settings.forwardHeaders.enable;
|
||||
extraConfig = let
|
||||
# Client upload size configuration
|
||||
maxBodySizeConfig =
|
||||
lib.optionalString (service.settings.maxBodySize != null)
|
||||
"client_max_body_size ${toString service.settings.maxBodySize}M;";
|
||||
|
||||
# Security header configuration
|
||||
noSniffConfig =
|
||||
lib.optionalString service.settings.noSniff.enable
|
||||
"add_header X-Content-Type-Options nosniff;";
|
||||
|
||||
# Proxy buffering configuration
|
||||
proxyBufferingConfig =
|
||||
lib.optionalString (!service.settings.proxyBuffering.enable)
|
||||
"proxy_buffering off;";
|
||||
|
||||
# Proxy timeout configuration
|
||||
proxyTimeoutConfig =
|
||||
lib.optionalString service.settings.proxyHeaders.enable
|
||||
''
|
||||
proxy_read_timeout ${toString service.settings.proxyHeaders.timeout}s;
|
||||
proxy_connect_timeout ${toString service.settings.proxyHeaders.timeout}s;
|
||||
proxy_send_timeout ${toString service.settings.proxyHeaders.timeout}s;
|
||||
'';
|
||||
in
|
||||
maxBodySizeConfig + noSniffConfig + proxyBufferingConfig + proxyTimeoutConfig;
|
||||
};
|
||||
};
|
||||
};
|
||||
in (
|
||||
[
|
||||
{
|
||||
${service.domain} = hostConfig;
|
||||
}
|
||||
]
|
||||
++ builtins.map (domain: {${domain} = hostConfig;})
|
||||
service.extraDomains
|
||||
)
|
||||
)
|
||||
config.services.reverseProxy.services
|
||||
)
|
||||
);
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf config.services.reverseProxy.openFirewall [
|
||||
httpPort
|
||||
httpsPort
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
dataDir = "/var/lib/acme";
|
||||
httpPort = 80;
|
||||
httpsPort = 443;
|
||||
in {
|
||||
options.host.reverse_proxy = {
|
||||
enable = lib.mkEnableOption "turn on the reverse proxy";
|
||||
hostname = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what host name are we going to be proxying from";
|
||||
};
|
||||
forceSSL = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "force connections to use https";
|
||||
default = config.host.reverse_proxy.enableACME;
|
||||
};
|
||||
enableACME = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "auto renew certificates";
|
||||
default = true;
|
||||
};
|
||||
subdomains = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
|
||||
options = {
|
||||
subdomain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what is the default subdomain to be used for this application to be used for";
|
||||
default = name;
|
||||
};
|
||||
extraSubdomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "extra domains that should be configured for this domain";
|
||||
default = [];
|
||||
};
|
||||
|
||||
target = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what url will all traffic to this application be forwarded to";
|
||||
};
|
||||
|
||||
websockets.enable = lib.mkEnableOption "should the default config proxy websockets";
|
||||
|
||||
forwardHeaders.enable = lib.mkEnableOption "should the default config contain forward headers";
|
||||
|
||||
extraConfig = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
These lines go to the end of the upstream verbatim.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.host.reverse_proxy.enable (lib.mkMerge [
|
||||
{
|
||||
security.acme = lib.mkIf config.host.reverse_proxy.enableACME {
|
||||
acceptTerms = true;
|
||||
defaults.email = "jan-leila@protonmail.com";
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts = lib.mkMerge (
|
||||
lib.lists.flatten (
|
||||
lib.attrsets.mapAttrsToList (
|
||||
name: value: let
|
||||
hostConfig = {
|
||||
forceSSL = config.host.reverse_proxy.forceSSL;
|
||||
enableACME = config.host.reverse_proxy.enableACME;
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = value.target;
|
||||
proxyWebsockets = value.websockets.enable;
|
||||
recommendedProxySettings = value.forwardHeaders.enable;
|
||||
extraConfig =
|
||||
value.extraConfig;
|
||||
};
|
||||
};
|
||||
};
|
||||
in (
|
||||
[
|
||||
{
|
||||
${"${value.subdomain}.${config.host.reverse_proxy.hostname}"} = hostConfig;
|
||||
}
|
||||
]
|
||||
++ builtins.map (subdomain: {${"${subdomain}.${config.host.reverse_proxy.hostname}"} = hostConfig;})
|
||||
value.extraSubdomains
|
||||
)
|
||||
)
|
||||
config.host.reverse_proxy.subdomains
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
httpPort
|
||||
httpsPort
|
||||
];
|
||||
}
|
||||
(lib.mkIf config.host.impermanence.enable {
|
||||
# TODO: figure out how to write an assertion for this
|
||||
# assertions = [
|
||||
# {
|
||||
# assertion = security.acme.certs.<name>.directory == dataDir;
|
||||
# message = "postgres data directory does not match persistence";
|
||||
# }
|
||||
# ];
|
||||
environment.persistence."/persist/system/root" = {
|
||||
enable = true;
|
||||
hideMounts = true;
|
||||
directories = [
|
||||
{
|
||||
directory = dataDir;
|
||||
user = "acme";
|
||||
group = "acme";
|
||||
}
|
||||
];
|
||||
};
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
|
@ -1,63 +1,6 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
inputs,
|
||||
...
|
||||
}: {
|
||||
imports = [
|
||||
./searx.nix
|
||||
./proxy.nix
|
||||
];
|
||||
|
||||
config = lib.mkIf config.services.searx.enable {
|
||||
sops.secrets = {
|
||||
"services/searx" = {
|
||||
sopsFile = "${inputs.secrets}/defiant-services.yaml";
|
||||
};
|
||||
};
|
||||
|
||||
services.searx = {
|
||||
environmentFile = config.sops.secrets."services/searx".path;
|
||||
|
||||
# Rate limiting
|
||||
limiterSettings = {
|
||||
real_ip = {
|
||||
x_for = 1;
|
||||
ipv4_prefix = 32;
|
||||
ipv6_prefix = 56;
|
||||
};
|
||||
|
||||
botdetection = {
|
||||
ip_limit = {
|
||||
filter_link_local = true;
|
||||
link_token = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
settings = {
|
||||
server = {
|
||||
port = 8083;
|
||||
secret_key = "@SEARXNG_SECRET@";
|
||||
};
|
||||
|
||||
# Search engine settings
|
||||
search = {
|
||||
safe_search = 2;
|
||||
autocomplete_min = 2;
|
||||
autocomplete = "duckduckgo";
|
||||
};
|
||||
|
||||
# Enabled plugins
|
||||
enabled_plugins = [
|
||||
"Basic Calculator"
|
||||
"Hash plugin"
|
||||
"Tor check plugin"
|
||||
"Open Access DOI rewrite"
|
||||
"Hostnames plugin"
|
||||
"Unit converter plugin"
|
||||
"Tracker URL remover"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,18 +4,27 @@
|
|||
...
|
||||
}: {
|
||||
options.services.searx = {
|
||||
subdomain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "subdomain of base domain that searx will be hosted at";
|
||||
default = "searx";
|
||||
extraDomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "extra domains that should be configured for searx";
|
||||
default = [];
|
||||
};
|
||||
reverseProxy = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.searx.enable && config.services.reverseProxy.enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.searx.enable && config.host.reverse_proxy.enable) {
|
||||
host = {
|
||||
reverse_proxy.subdomains.searx = {
|
||||
subdomain = config.services.searx.subdomain;
|
||||
target = "http://localhost:${toString config.services.searx.settings.server.port}";
|
||||
config = lib.mkIf config.services.searx.reverseProxy.enable {
|
||||
services.reverseProxy.services.searx = {
|
||||
target = "http://localhost:${toString config.services.searx.settings.server.port}";
|
||||
domain = config.services.searx.domain;
|
||||
extraDomains = config.services.searx.extraDomains;
|
||||
|
||||
settings = {
|
||||
forwardHeaders.enable = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
59
modules/nixos-modules/server/searx/searx.nix
Normal file
59
modules/nixos-modules/server/searx/searx.nix
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
inputs,
|
||||
...
|
||||
}: {
|
||||
config = lib.mkIf config.services.searx.enable {
|
||||
sops.secrets = {
|
||||
"services/searx" = {
|
||||
sopsFile = "${inputs.secrets}/defiant-services.yaml";
|
||||
};
|
||||
};
|
||||
|
||||
services.searx = {
|
||||
environmentFile = config.sops.secrets."services/searx".path;
|
||||
|
||||
# Rate limiting
|
||||
limiterSettings = {
|
||||
real_ip = {
|
||||
x_for = 1;
|
||||
ipv4_prefix = 32;
|
||||
ipv6_prefix = 56;
|
||||
};
|
||||
|
||||
botdetection = {
|
||||
ip_limit = {
|
||||
filter_link_local = true;
|
||||
link_token = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
settings = {
|
||||
server = {
|
||||
port = 8083;
|
||||
secret_key = "@SEARXNG_SECRET@";
|
||||
};
|
||||
|
||||
# Search engine settings
|
||||
search = {
|
||||
safe_search = 2;
|
||||
autocomplete_min = 2;
|
||||
autocomplete = "duckduckgo";
|
||||
};
|
||||
|
||||
# Enabled plugins
|
||||
enabled_plugins = [
|
||||
"Basic Calculator"
|
||||
"Hash plugin"
|
||||
"Tor check plugin"
|
||||
"Open Access DOI rewrite"
|
||||
"Hostnames plugin"
|
||||
"Unit converter plugin"
|
||||
"Tracker URL remover"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
{...}: {
|
||||
imports = [
|
||||
./proxy.nix
|
||||
./impermanence.nix
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,14 @@
|
|||
}: let
|
||||
sonarr_data_directory = "/var/lib/sonarr/.config/NzbDrone";
|
||||
in {
|
||||
config = lib.mkIf (config.services.sonarr.enable && config.host.impermanence.enable) {
|
||||
options.services.sonarr = {
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.services.sonarr.enable && config.host.impermanence.enable;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.sonarr.impermanence.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.sonarr.dataDir == sonarr_data_directory;
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
options.services.sonarr = {
|
||||
subdomain = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Subdomain for reverse proxy. If null, service will be local only.";
|
||||
};
|
||||
extraSubdomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
description = "Extra subdomains for reverse proxy.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (config.services.sonarr.enable && config.services.sonarr.subdomain != null) {
|
||||
host.reverse_proxy.subdomains.sonarr = {
|
||||
subdomain = config.services.sonarr.subdomain;
|
||||
extraSubdomains = config.services.sonarr.extraSubdomains;
|
||||
target = "http://127.0.0.1:8989";
|
||||
websockets.enable = true;
|
||||
forwardHeaders.enable = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -37,9 +37,9 @@
|
|||
openwakeword = {
|
||||
enable = true;
|
||||
uri = "tcp://0.0.0.0:10400";
|
||||
preloadModels = [
|
||||
"ok_nabu"
|
||||
];
|
||||
# preloadModels = [
|
||||
# "ok_nabu"
|
||||
# ];
|
||||
# TODO: custom models
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue