modules: services: use new nginx wrapper

And when not possible, document why.

Note for the future: there is some repetition in some modules to
configure the correct value of the subdomain, which I happen to know
will line up correctly thanks to the nginx wrapper. A good way to
refactor this in the future would involve avoiding this repetition,
allowing use to query the correct domain in some way...
This commit is contained in:
Bruno BELANYI 2021-08-24 23:05:10 +02:00
parent a8514dcdf1
commit 77cf3430ae
19 changed files with 187 additions and 254 deletions

View file

@ -90,9 +90,6 @@ in
enable = true; enable = true;
password = my.secrets.nextcloud.password; password = my.secrets.nextcloud.password;
}; };
nginx = {
enable = true; # FIXME: remove this when done migrating
};
# The whole *arr software suite # The whole *arr software suite
pirate.enable = true; pirate.enable = true;
# Podcast automatic downloader # Podcast automatic downloader

View file

@ -4,28 +4,12 @@ let
cfg = config.my.services.blog; cfg = config.my.services.blog;
domain = config.networking.domain; domain = config.networking.domain;
makeHostInfo = name: { makeHostInfo = subdomain: {
name = "${name}.${domain}"; inherit subdomain;
value = "/var/www/${name}"; root = "/var/www/${subdomain}";
}; };
hostsInfo = [ hostsInfo = map makeHostInfo [ "cv" "dev" "key" ];
{
name = domain;
value = "/var/www/blog";
}
] ++ builtins.map makeHostInfo [ "cv" "dev" "key" ];
hosts = builtins.listToAttrs hostsInfo;
makeVirtualHost = with lib.attrsets;
name: root: nameValuePair "${name}" {
forceSSL = true;
useACMEHost = domain;
inherit root;
# Make my blog the default landing site
default = (name == domain);
};
in in
{ {
options.my.services.blog = { options.my.services.blog = {
@ -33,7 +17,17 @@ in
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
services.nginx.virtualHosts = with lib.attrsets; services.nginx.virtualHosts = {
mapAttrs' makeVirtualHost hosts; # This is not a subdomain, cannot use my nginx wrapper module
${domain} = {
forceSSL = true;
useACMEHost = domain;
root = "/var/www/blog";
default = true; # Redirect to my blog
};
};
# Those are all subdomains, no problem
my.services.nginx.virtualHosts = hostsInfo;
}; };
} }

View file

@ -1,8 +1,6 @@
{ config, lib, ... }: { config, lib, ... }:
let let
cfg = config.my.services.calibre-web; cfg = config.my.services.calibre-web;
domain = config.networking.domain;
calibreDomain = "library.${domain}";
in in
{ {
options.my.services.calibre-web = with lib; { options.my.services.calibre-web = with lib; {
@ -39,12 +37,12 @@ in
}; };
}; };
services.nginx.virtualHosts."${calibreDomain}" = { my.services.nginx.virtualHosts = [
forceSSL = true; {
useACMEHost = domain; subdomain = "library";
inherit (cfg) port;
locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}/"; }
}; ];
my.services.backup = { my.services.backup = {
paths = [ paths = [

View file

@ -6,9 +6,6 @@
let let
cfg = config.my.services.drone; cfg = config.my.services.drone;
domain = config.networking.domain;
droneDomain = "drone.${domain}";
hasRunner = (name: builtins.elem name cfg.runners); hasRunner = (name: builtins.elem name cfg.runners);
execPkg = pkgs.drone-runner-exec; execPkg = pkgs.drone-runner-exec;
@ -59,7 +56,7 @@ in
]; ];
Environment = [ Environment = [
"DRONE_DATABASE_DATASOURCE=postgres:///drone?host=/run/postgresql" "DRONE_DATABASE_DATASOURCE=postgres:///drone?host=/run/postgresql"
"DRONE_SERVER_HOST=${droneDomain}" "DRONE_SERVER_HOST=drone.${config.networking.domain}"
"DRONE_SERVER_PROTO=https" "DRONE_SERVER_PROTO=https"
"DRONE_DATABASE_DRIVER=postgres" "DRONE_DATABASE_DRIVER=postgres"
"DRONE_SERVER_PORT=:${toString cfg.port}" "DRONE_SERVER_PORT=:${toString cfg.port}"
@ -91,12 +88,12 @@ in
}]; }];
}; };
services.nginx.virtualHosts."${droneDomain}" = { my.services.nginx.virtualHosts = [
forceSSL = true; {
useACMEHost = domain; subdomain = "drone";
inherit (cfg) port;
locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}"; }
}; ];
# Docker runner # Docker runner
systemd.services.drone-runner-docker = lib.mkIf (hasRunner "docker") { systemd.services.drone-runner-docker = lib.mkIf (hasRunner "docker") {
@ -107,7 +104,7 @@ in
confinement.enable = true; confinement.enable = true;
serviceConfig = { serviceConfig = {
Environment = [ Environment = [
"DRONE_SERVER_HOST=${droneDomain}" "DRONE_SERVER_HOST=drone.${config.networking.domain}"
"DRONE_SERVER_PROTO=https" "DRONE_SERVER_PROTO=https"
"DRONE_RUNNER_CAPACITY=10" "DRONE_RUNNER_CAPACITY=10"
"CLIENT_DRONE_RPC_HOST=127.0.0.1:${toString cfg.port}" "CLIENT_DRONE_RPC_HOST=127.0.0.1:${toString cfg.port}"
@ -156,7 +153,7 @@ in
]; ];
serviceConfig = { serviceConfig = {
Environment = [ Environment = [
"DRONE_SERVER_HOST=${droneDomain}" "DRONE_SERVER_HOST=drone.${config.networking.domain}"
"DRONE_SERVER_PROTO=https" "DRONE_SERVER_PROTO=https"
"DRONE_RUNNER_CAPACITY=10" "DRONE_RUNNER_CAPACITY=10"
"CLIENT_DRONE_RPC_HOST=127.0.0.1:${toString cfg.port}" "CLIENT_DRONE_RPC_HOST=127.0.0.1:${toString cfg.port}"

View file

@ -2,9 +2,6 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
cfg = config.my.services.flood; cfg = config.my.services.flood;
domain = config.networking.domain;
webuiDomain = "flood.${domain}";
in in
{ {
options.my.services.flood = with lib; { options.my.services.flood = with lib; {
@ -43,11 +40,11 @@ in
}; };
}; };
services.nginx.virtualHosts."${webuiDomain}" = { my.services.nginx.virtualHosts = [
forceSSL = true; {
useACMEHost = domain; subdomain = "flood";
inherit (cfg) port;
locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}"; }
}; ];
}; };
} }

View file

@ -2,8 +2,6 @@
{ config, lib, ... }: { config, lib, ... }:
let let
cfg = config.my.services.gitea; cfg = config.my.services.gitea;
domain = config.networking.domain;
giteaDomain = "gitea.${config.networking.domain}";
in in
{ {
options.my.services.gitea = with lib; { options.my.services.gitea = with lib; {
@ -17,34 +15,38 @@ in
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
services.gitea = { services.gitea =
enable = true; let
giteaDomain = "gitea.${config.networking.domain}";
in
{
enable = true;
appName = "Ambroisie's forge"; appName = "Ambroisie's forge";
httpPort = cfg.port; httpPort = cfg.port;
domain = giteaDomain; domain = giteaDomain;
rootUrl = "https://${giteaDomain}"; rootUrl = "https://${giteaDomain}";
user = "git"; user = "git";
lfs.enable = true; lfs.enable = true;
useWizard = false; useWizard = false;
disableRegistration = true; disableRegistration = true;
# only send cookies via HTTPS # only send cookies via HTTPS
cookieSecure = true; cookieSecure = true;
database = { database = {
type = "postgres"; # Automatic setup type = "postgres"; # Automatic setup
user = "git"; # User needs to be the same as gitea user user = "git"; # User needs to be the same as gitea user
};
# NixOS module uses `gitea dump` to backup repositories and the database,
# but it produces a single .zip file that's not very backup friendly.
# I configure my backup system manually below.
dump.enable = false;
}; };
# NixOS module uses `gitea dump` to backup repositories and the database,
# but it produces a single .zip file that's not very backup friendly.
# I configure my backup system manually below.
dump.enable = false;
};
users.users.git = { users.users.git = {
description = "Gitea Service"; description = "Gitea Service";
home = config.services.gitea.stateDir; home = config.services.gitea.stateDir;
@ -60,12 +62,12 @@ in
users.groups.git = { }; users.groups.git = { };
# Proxy to Gitea # Proxy to Gitea
services.nginx.virtualHosts."${giteaDomain}" = { my.services.nginx.virtualHosts = [
forceSSL = true; {
useACMEHost = domain; subdomain = "gitea";
inherit (cfg) port;
locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}/"; }
}; ];
my.services.backup = { my.services.backup = {
paths = [ paths = [

View file

@ -3,10 +3,6 @@
let let
cfg = config.my.services.indexers; cfg = config.my.services.indexers;
domain = config.networking.domain;
jackettDomain = "jackett.${config.networking.domain}";
nzbhydraDomain = "nzbhydra.${config.networking.domain}";
jackettPort = 9117; jackettPort = 9117;
nzbhydraPort = 5076; nzbhydraPort = 5076;
in in
@ -29,25 +25,19 @@ in
}; };
}; };
services.nginx.virtualHosts."${jackettDomain}" =
lib.mkIf cfg.jackett.enable {
forceSSL = true;
useACMEHost = domain;
locations."/".proxyPass = "http://127.0.0.1:${toString jackettPort}/";
};
services.nzbhydra2 = lib.mkIf cfg.nzbhydra.enable { services.nzbhydra2 = lib.mkIf cfg.nzbhydra.enable {
enable = true; enable = true;
}; };
services.nginx.virtualHosts."${nzbhydraDomain}" = my.services.nginx.virtualHosts = [
lib.mkIf cfg.nzbhydra.enable { {
forceSSL = true; subdomain = "jackett";
useACMEHost = domain; port = jackettPort;
}
locations."/".proxyPass = "http://127.0.0.1:${toString nzbhydraPort}/"; {
}; subdomain = "nzbhydra";
port = nzbhydraPort;
}
];
}; };
} }

View file

@ -2,8 +2,6 @@
{ config, lib, ... }: { config, lib, ... }:
let let
cfg = config.my.services.jellyfin; cfg = config.my.services.jellyfin;
domain = config.networking.domain;
jellyfinDomain = "jellyfin.${config.networking.domain}";
in in
{ {
options.my.services.jellyfin = { options.my.services.jellyfin = {
@ -16,22 +14,23 @@ in
group = "media"; group = "media";
}; };
# Proxy to Jellyfin my.services.nginx.virtualHosts = [
services.nginx.virtualHosts."${jellyfinDomain}" = { {
forceSSL = true; subdomain = "jellyfin";
useACMEHost = domain; port = 8096;
extraConfig = {
locations."/" = { locations."/" = {
proxyPass = "http://127.0.0.1:8096/"; extraConfig = ''
extraConfig = '' proxy_buffering off;
proxy_buffering off; '';
''; };
}; # Too bad for the repetition...
locations."/socket" = {
locations."/socket" = { proxyPass = "http://127.0.0.1:8096/";
proxyPass = "http://127.0.0.1:8096/"; proxyWebsockets = true;
proxyWebsockets = true; };
}; };
}; }
];
}; };
} }

View file

@ -4,9 +4,6 @@ let
cfg = config.my.services.lohr; cfg = config.my.services.lohr;
settingsFormat = pkgs.formats.yaml { }; settingsFormat = pkgs.formats.yaml { };
domain = config.networking.domain;
lohrDomain = "lohr.${config.networking.domain}";
lohrPkg = pkgs.ambroisie.lohr; lohrPkg = pkgs.ambroisie.lohr;
in in
{ {
@ -75,13 +72,11 @@ in
}; };
users.groups.lohr = { }; users.groups.lohr = { };
services.nginx.virtualHosts."${lohrDomain}" = { my.services.nginx.virtualHosts = [
forceSSL = true; {
useACMEHost = domain; subdomain = "lohr";
inherit (cfg) port;
locations."/" = { }
proxyPass = "http://127.0.0.1:${toString cfg.port}/"; ];
};
};
}; };
} }

View file

@ -118,6 +118,35 @@ in
''; '';
}; };
my.services.nginx.virtualHosts = [
# Element Web app deployment
{
subdomain = "chat";
root = pkgs.element-web.override {
conf = {
default_server_config = {
"m.homeserver" = {
"base_url" = "https://matrix.${domain}";
"server_name" = domain;
};
"m.identity_server" = {
"base_url" = "https://vector.im";
};
};
showLabsSettings = true;
defaultCountryCode = "FR"; # cocorico
roomDirectory = {
"servers" = [
"matrix.org"
"mozilla.org"
];
};
};
};
}
];
# Those are too complicated to use my wrapper...
services.nginx.virtualHosts = { services.nginx.virtualHosts = {
"matrix.${domain}" = { "matrix.${domain}" = {
onlySSL = true; onlySSL = true;
@ -192,34 +221,6 @@ in
return 200 '${builtins.toJSON client}'; return 200 '${builtins.toJSON client}';
''; '';
}; };
# Element Web app deployment
"chat.${domain}" = {
useACMEHost = domain;
forceSSL = true;
root = pkgs.element-web.override {
conf = {
default_server_config = {
"m.homeserver" = {
"base_url" = "https://matrix.${domain}";
"server_name" = domain;
};
"m.identity_server" = {
"base_url" = "https://vector.im";
};
};
showLabsSettings = true;
defaultCountryCode = "FR"; # cocorico
roomDirectory = {
"servers" = [
"matrix.org"
"mozilla.org"
];
};
};
};
};
}; };
# For administration tools. # For administration tools.

View file

@ -2,9 +2,6 @@
{ config, lib, ... }: { config, lib, ... }:
let let
cfg = config.my.services.miniflux; cfg = config.my.services.miniflux;
domain = config.networking.domain;
minifluxDomain = "reader.${config.networking.domain}";
in in
{ {
options.my.services.miniflux = with lib; { options.my.services.miniflux = with lib; {
@ -23,7 +20,7 @@ in
description = "Password of the admin user"; description = "Password of the admin user";
}; };
privatePort = mkOption { port = mkOption {
type = types.port; type = types.port;
default = 9876; default = 9876;
example = 8080; example = 8080;
@ -45,8 +42,8 @@ in
config = { config = {
# Virtual hosts settings # Virtual hosts settings
BASE_URL = "https://${minifluxDomain}"; BASE_URL = "https://reader.${config.networking.domain}";
LISTEN_ADDR = "localhost:${toString cfg.privatePort}"; LISTEN_ADDR = "localhost:${toString cfg.port}";
# I want fast updates # I want fast updates
POLLING_FREQUENCY = "30"; POLLING_FREQUENCY = "30";
BATCH_SIZE = "50"; BATCH_SIZE = "50";
@ -56,12 +53,11 @@ in
}; };
}; };
# Proxy to Jellyfin my.services.nginx.virtualHosts = [
services.nginx.virtualHosts."${minifluxDomain}" = { {
forceSSL = true; subdomain = "reader";
useACMEHost = domain; inherit (cfg) port;
}
locations."/".proxyPass = "http://127.0.0.1:${toString cfg.privatePort}/"; ];
};
}; };
} }

View file

@ -2,9 +2,6 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
cfg = config.my.services.monitoring; cfg = config.my.services.monitoring;
domain = config.networking.domain;
grafanaDomain = "monitoring.${config.networking.domain}";
in in
{ {
options.my.services.monitoring = with lib; { options.my.services.monitoring = with lib; {
@ -52,7 +49,7 @@ in
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
services.grafana = { services.grafana = {
enable = true; enable = true;
domain = grafanaDomain; domain = "monitoring.${config.networking.domain}";
port = cfg.grafana.port; port = cfg.grafana.port;
addr = "127.0.0.1"; # Proxied through Nginx addr = "127.0.0.1"; # Proxied through Nginx
@ -115,16 +112,11 @@ in
]; ];
}; };
services.nginx = { my.services.nginx.virtualHosts = [
virtualHosts.${grafanaDomain} = { {
forceSSL = true; subdomain = "monitoring";
useACMEHost = domain; inherit (cfg.grafana) port;
}
locations."/" = { ];
proxyPass = "http://127.0.0.1:${toString cfg.grafana.port}";
proxyWebsockets = true;
};
};
};
}; };
} }

View file

@ -2,8 +2,6 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
cfg = config.my.services.navidrome; cfg = config.my.services.navidrome;
domain = config.networking.domain;
navidromeDomain = "music.${config.networking.domain}";
in in
{ {
options.my.services.navidrome = with lib; { options.my.services.navidrome = with lib; {
@ -23,7 +21,7 @@ in
''; '';
}; };
privatePort = mkOption { port = mkOption {
type = types.port; type = types.port;
default = 4533; default = 4533;
example = 8080; example = 8080;
@ -42,21 +40,18 @@ in
enable = true; enable = true;
settings = cfg.settings // { settings = cfg.settings // {
Port = cfg.privatePort; Port = cfg.port;
Address = "127.0.0.1"; # Behind reverse proxy, so only loopback Address = "127.0.0.1"; # Behind reverse proxy, so only loopback
MusicFolder = cfg.musicFolder; MusicFolder = cfg.musicFolder;
LogLevel = "info"; LogLevel = "info";
}; };
}; };
services.nginx.virtualHosts."${navidromeDomain}" = { my.services.nginx.virtualHosts = [
forceSSL = true; {
useACMEHost = domain; subdomain = "music";
inherit (cfg) port;
locations."/" = { }
proxyPass = "http://127.0.0.1:${toString cfg.privatePort}/"; ];
proxyWebsockets = true;
};
};
}; };
} }

View file

@ -2,8 +2,6 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
cfg = config.my.services.nextcloud; cfg = config.my.services.nextcloud;
domain = config.networking.domain;
nextcloudDomain = "nextcloud.${config.networking.domain}";
in in
{ {
options.my.services.nextcloud = with lib; { options.my.services.nextcloud = with lib; {
@ -31,7 +29,7 @@ in
services.nextcloud = { services.nextcloud = {
enable = true; enable = true;
package = pkgs.nextcloud22; package = pkgs.nextcloud22;
hostName = nextcloudDomain; hostName = "nextcloud.${config.networking.domain}";
home = "/var/lib/nextcloud"; home = "/var/lib/nextcloud";
maxUploadSize = cfg.maxSize; maxUploadSize = cfg.maxSize;
config = { config = {
@ -59,11 +57,10 @@ in
after = [ "postgresql.service" ]; after = [ "postgresql.service" ];
}; };
services.nginx.virtualHosts."${nextcloudDomain}" = { # The service above configures the domain, no need for my wrapper
services.nginx.virtualHosts."nextcloud.${config.networking.domain}" = {
forceSSL = true; forceSSL = true;
useACMEHost = domain; useACMEHost = config.networking.domain;
locations."/".proxyPass = "http://127.0.0.1:3000/";
}; };
my.services.backup = { my.services.backup = {

View file

@ -5,7 +5,6 @@
{ config, lib, ... }: { config, lib, ... }:
let let
cfg = config.my.services.pirate; cfg = config.my.services.pirate;
domain = config.networking.domain;
ports = { ports = {
sonarr = 8989; sonarr = 8989;
@ -22,15 +21,8 @@ let
}) })
ports); ports);
redirections = with lib.attrsets; redirections = lib.flip lib.mapAttrsToList ports
(mapAttrs' (subdomain: port: { inherit subdomain port; });
(service: port: nameValuePair "${service}.${domain}" {
forceSSL = true;
useACMEHost = domain;
locations."/".proxyPass = "http://127.0.0.1:${builtins.toString port}/";
})
ports);
in in
{ {
options.my.services.pirate = { options.my.services.pirate = {
@ -38,6 +30,7 @@ in
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
services = managers // { nginx.virtualHosts = redirections; }; services = managers;
my.services.nginx.virtualHosts = redirections;
}; };
} }

View file

@ -2,9 +2,6 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
cfg = config.my.services.podgrab; cfg = config.my.services.podgrab;
domain = config.networking.domain;
podgrabDomain = "podgrab.${domain}";
in in
{ {
options.my.services.podgrab = with lib; { options.my.services.podgrab = with lib; {
@ -34,11 +31,11 @@ in
inherit (cfg) passwordFile port; inherit (cfg) passwordFile port;
}; };
services.nginx.virtualHosts."${podgrabDomain}" = { my.services.nginx.virtualHosts = [
forceSSL = true; {
useACMEHost = domain; subdomain = "podgrab";
inherit (cfg) port;
locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}"; }
}; ];
}; };
} }

View file

@ -2,8 +2,6 @@
{ config, lib, ... }: { config, lib, ... }:
let let
cfg = config.my.services.rss-bridge; cfg = config.my.services.rss-bridge;
domain = config.networking.domain;
rss-bridgeDomain = "rss-bridge.${config.networking.domain}";
in in
{ {
options.my.services.rss-bridge = { options.my.services.rss-bridge = {
@ -14,12 +12,13 @@ in
services.rss-bridge = { services.rss-bridge = {
enable = true; enable = true;
whitelist = [ "*" ]; # Whitelist all whitelist = [ "*" ]; # Whitelist all
virtualHost = rss-bridgeDomain; # Setup virtual host virtualHost = "rss-bridge.${config.networking.domain}";
}; };
services.nginx.virtualHosts."${rss-bridgeDomain}" = { # The service above configures the domain, no need for my wrapper
services.nginx.virtualHosts."rss-bridge.${config.networking.domain}" = {
forceSSL = true; forceSSL = true;
useACMEHost = domain; useACMEHost = config.networking.domain;
}; };
}; };
} }

View file

@ -2,9 +2,6 @@
{ config, lib, ... }: { config, lib, ... }:
let let
cfg = config.my.services.sabnzbd; cfg = config.my.services.sabnzbd;
domain = config.networking.domain;
sabnzbdDomain = "sabnzbd.${domain}";
port = 9090; # NOTE: not declaratively set... port = 9090; # NOTE: not declaratively set...
in in
{ {
@ -18,11 +15,11 @@ in
group = "media"; group = "media";
}; };
services.nginx.virtualHosts."${sabnzbdDomain}" = { my.services.nginx.virtualHosts = [
forceSSL = true; {
useACMEHost = domain; subdomain = "sabnzbd";
inherit port;
locations."/".proxyPass = "http://127.0.0.1:${toString port}"; }
}; ];
}; };
} }

View file

@ -6,9 +6,6 @@
{ config, lib, ... }: { config, lib, ... }:
let let
cfg = config.my.services.transmission; cfg = config.my.services.transmission;
domain = config.networking.domain;
webuiDomain = "transmission.${domain}";
in in
{ {
options.my.services.transmission = with lib; { options.my.services.transmission = with lib; {
@ -34,7 +31,7 @@ in
description = "Download base directory"; description = "Download base directory";
}; };
privatePort = mkOption { port = mkOption {
type = types.port; type = types.port;
default = 9091; default = 9091;
example = 8080; example = 8080;
@ -63,7 +60,7 @@ in
peer-port = cfg.peerPort; peer-port = cfg.peerPort;
rpc-enabled = true; rpc-enabled = true;
rpc-port = cfg.privatePort; rpc-port = cfg.port;
rpc-authentication-required = true; rpc-authentication-required = true;
rpc-username = cfg.username; rpc-username = cfg.username;
@ -77,12 +74,12 @@ in
# Default transmission webui, I prefer combustion but its development # Default transmission webui, I prefer combustion but its development
# seems to have stalled # seems to have stalled
services.nginx.virtualHosts."${webuiDomain}" = { my.services.nginx.virtualHosts = [
forceSSL = true; {
useACMEHost = domain; subdomain = "transmission";
inherit (cfg) port;
locations."/".proxyPass = "http://127.0.0.1:${toString cfg.privatePort}"; }
}; ];
networking.firewall = { networking.firewall = {
allowedTCPPorts = [ cfg.peerPort ]; allowedTCPPorts = [ cfg.peerPort ];