Compare commits

..

3 commits

Author SHA1 Message Date
Stefan Kempinger
6ede343e56 Enable Snowflake proxy with capacity 50 2026-02-17 08:53:50 +01:00
Stefan Kempinger
bd2d37d331 Nest boot settings under boot attribute 2026-02-17 00:25:45 +01:00
Stefan Kempinger
143299ccf7 Move frontends to 80xx and add mail server
Reassign multiple service frontends from 81xx to 80xx ports (Forgejo,
ntopng, AdGuard, Scrutiny, Paperless, Whats Up Docker, etc.) and update
homepage links.

Configure ACME (webroot) and add certs for kempinger.at,
webadmin.kempinger.at,
and bilder.kempinger.at; update nginx virtual hosts to use ACME hosts
and
serve the ACME challenge path.

Add users stalwart-mail and nginx to the acme group and open
SMTP-related
firewall ports (25, 587) plus mail UI ports (8090, 8091).

Add and configure the Stalwart mail service (SMTP, submissions, IMAP,
JMAP)
and adjust related service ports/settings (ntopng, scrutiny, influxdb,
WUD).
2026-02-17 00:24:14 +01:00

View file

@ -20,20 +20,22 @@
time.timeZone = "Europe/Vienna"; time.timeZone = "Europe/Vienna";
# Bootloader and kernel # Bootloader and kernel
boot.loader.systemd-boot.enable = true; boot = {
boot.loader.efi.canTouchEfiVariables = true; loader.systemd-boot.enable = true;
boot.kernelParams = [ loader.efi.canTouchEfiVariables = true;
kernelParams = [
"vga=791" "vga=791"
"nomodeset" "nomodeset"
]; ];
boot.kernel.sysctl = { kernel.sysctl = {
"net.ipv4.ip_forward" = 1; "net.ipv4.ip_forward" = 1;
# "net.bridge.bridge-nf-call-iptables" = 1; # "net.bridge.bridge-nf-call-iptables" = 1;
# "net.bridge.bridge-nf-call-ip6tables" = 1; # "net.bridge.bridge-nf-call-ip6tables" = 1;
}; };
boot.supportedFilesystems = [ "zfs" ]; supportedFilesystems = [ "zfs" ];
boot.zfs.forceImportRoot = false; zfs.forceImportRoot = false;
};
hardware.graphics.enable = true; hardware.graphics.enable = true;
hardware.nvidia.open = true; hardware.nvidia.open = true;
@ -46,8 +48,6 @@
keyMap = "de"; keyMap = "de";
# useXkbConfig = true; # use xkb.options in tty. # useXkbConfig = true; # use xkb.options in tty.
}; };
# i18n.defaultLocale = "en_US.UTF-8";
# Networking # Networking
networking = { networking = {
hostName = "heimserver"; hostName = "heimserver";
@ -60,15 +60,17 @@
} }
]; ];
defaultGateway = "192.168.69.1"; defaultGateway = "192.168.69.1";
#nameservers = [ "1.1.1.1" ]; #nameservers = [ "127.0.0.1" ];
firewall.enable = true; firewall.enable = true;
firewall.allowedTCPPorts = [ firewall.allowedTCPPorts = [
22 22
25
53 53
80 80
443 443
587
2222 # forgejo ssh 2222 # forgejo ssh
8184 # forgejo frontend 8084 # forgejo frontend
8123 # homeassistant 8123 # homeassistant
5580 # homeassistant matter 5580 # homeassistant matter
2283 # immich 2283 # immich
@ -78,11 +80,13 @@
8554 # frigate rtsp 8554 # frigate rtsp
8555 # frigate rtsp 8555 # frigate rtsp
2055 # ntopng sink 2055 # ntopng sink
8182 # ntopng frontend 8088 # ntopng frontend
8183 # adguardhome frontend 8083 # adguardhome frontend
8185 # scrutiny frontend 8085 # scrutiny frontend
8186 # wud frontend 8089 # wud frontend
8187 # paperless frontend 8087 # paperless frontend
8090 # mail
8091 # mail jmap
8080 # homepage 8080 # homepage
]; ];
firewall.allowedUDPPorts = [ firewall.allowedUDPPorts = [
@ -99,6 +103,14 @@
]; ];
}; };
users.users."stalwart-mail".extraGroups = [
"acme"
];
users.users."nginx".extraGroups = [
"acme"
];
users.users.immich.extraGroups = [ users.users.immich.extraGroups = [
"video" "video"
"render" "render"
@ -143,7 +155,30 @@
security.acme = { security.acme = {
acceptTerms = true; acceptTerms = true;
defaults.email = "mail@kempinger.xyz"; defaults.email = "mail@kempinger.xyz";
certs."kempinger.at".domain = "*.kempinger.at"; defaults.webroot = "/var/lib/acme/acme-challenge/";
certs."kempinger.at" = {
domain = "kempinger.at";
extraDomainNames = [
"git.kempinger.at"
];
reloadServices = [
"nginx"
];
};
certs."webadmin.kempinger.at" = {
domain = "webadmin.kempinger.at";
extraDomainNames = [
"mta-sts.kempinger.at"
"autoconfig.kempinger.at"
"autodiscover.kempinger.at"
"mail.kempinger.at"
"imap.kempinger.at"
"mx1.kempinger.at"
];
};
certs."bilder.kempinger.at" = {
domain = "bilder.kempinger.at";
};
}; };
#services.resolved.enable = true; #services.resolved.enable = true;
@ -163,15 +198,30 @@
}; };
virtualHosts."kempinger.at" = { virtualHosts."kempinger.at" = {
root = "/srv/website/public_html"; root = "/srv/website/public_html";
locations."/" = { locations."/".index = "index.html";
index = "index.html";
};
forceSSL = true; forceSSL = true;
enableACME = true; useACMEHost = "kempinger.at";
locations."/.well-known/".root = "/var/lib/acme/acme-challenge/";
};
virtualHosts."webadmin.kempinger.at" = {
forceSSL = true;
useACMEHost = "webadmin.kempinger.at";
#acmeRoot = null;
serverAliases = [
"mta-sts.kempinger.at"
"autoconfig.kempinger.at"
"autodiscover.kempinger.at"
"mail.kempinger.at"
"imap.kempinger.at"
"mx1.kempinger.at"
];
locations."/" = {
proxyPass = "http://127.0.0.1:8090";
};
}; };
virtualHosts.${config.services.forgejo.settings.server.DOMAIN} = { virtualHosts.${config.services.forgejo.settings.server.DOMAIN} = {
forceSSL = true; forceSSL = true;
enableACME = true; useACMEHost = "kempinger.at";
extraConfig = '' extraConfig = ''
client_max_body_size 512M; client_max_body_size 512M;
''; '';
@ -179,8 +229,8 @@
"http://localhost:${toString config.services.forgejo.settings.server.HTTP_PORT}"; "http://localhost:${toString config.services.forgejo.settings.server.HTTP_PORT}";
}; };
virtualHosts."bilder.kempinger.at" = { virtualHosts."bilder.kempinger.at" = {
enableACME = true;
forceSSL = true; forceSSL = true;
useACMEHost = "bilder.kempinger.at";
locations."/" = { locations."/" = {
proxyPass = "http://[::1]:${toString config.services.immich.port}"; proxyPass = "http://[::1]:${toString config.services.immich.port}";
proxyWebsockets = true; proxyWebsockets = true;
@ -205,7 +255,7 @@
DOMAIN = "git.kempinger.at"; DOMAIN = "git.kempinger.at";
# You need to specify this to remove the port from URLs in the web UI. # You need to specify this to remove the port from URLs in the web UI.
ROOT_URL = "https://${config.services.forgejo.settings.server.DOMAIN}/"; ROOT_URL = "https://${config.services.forgejo.settings.server.DOMAIN}/";
HTTP_PORT = 8184; HTTP_PORT = 8084;
DISABLE_SSH = false; DISABLE_SSH = false;
SSH_PORT = 2222; SSH_PORT = 2222;
START_SSH_SERVER = true; START_SSH_SERVER = true;
@ -347,8 +397,6 @@
}; };
}; };
#services.matter-server.enable = true;
virtualisation.oci-containers = { virtualisation.oci-containers = {
backend = "podman"; backend = "podman";
containers.homeassistant = { containers.homeassistant = {
@ -440,7 +488,7 @@
"/var/run/podman/podman.sock:/var/run/docker.sock" "/var/run/podman/podman.sock:/var/run/docker.sock"
]; ];
environment = { environment = {
WUD_SERVER_PORT = "8186"; WUD_SERVER_PORT = "8089";
WUD_TRIGGER_COMMAND_LOCAL_CMD = "echo \${display_name} can be updated to \${update_kind_remote_value}"; WUD_TRIGGER_COMMAND_LOCAL_CMD = "echo \${display_name} can be updated to \${update_kind_remote_value}";
}; };
extraOptions = [ extraOptions = [
@ -451,7 +499,7 @@
services.ntopng = { services.ntopng = {
enable = true; enable = true;
httpPort = 8182; httpPort = 8088;
interfaces = [ "tcp://0.0.0.0:5556" ]; interfaces = [ "tcp://0.0.0.0:5556" ];
extraConfig = '' extraConfig = ''
--dns-mode 1 --dns-mode 1
@ -460,6 +508,9 @@
services.influxdb2 = { services.influxdb2 = {
enable = true; enable = true;
settings = {
http-bind-address = ":8086";
};
# provision = { # provision = {
# enable = true; # enable = true;
@ -490,7 +541,7 @@
services.scrutiny = { services.scrutiny = {
enable = true; enable = true;
settings.web.listen.port = 8185; settings.web.listen.port = 8085;
influxdb.enable = true; influxdb.enable = true;
collector.schedule = "hourly"; collector.schedule = "hourly";
settings.web.influxdb = { settings.web.influxdb = {
@ -504,14 +555,14 @@
enable = true; enable = true;
# You can select any ip and port, just make sure to open firewalls where needed # You can select any ip and port, just make sure to open firewalls where needed
host = "0.0.0.0"; host = "0.0.0.0";
port = 8183; port = 8083;
}; };
services.paperless = { services.paperless = {
enable = true; enable = true;
consumptionDirIsPublic = true; consumptionDirIsPublic = true;
address = "0.0.0.0"; address = "0.0.0.0";
port = 8187; port = 8087;
settings = { settings = {
PAPERLESS_CONSUMER_IGNORE_PATTERN = [ PAPERLESS_CONSUMER_IGNORE_PATTERN = [
".DS_STORE/*" ".DS_STORE/*"
@ -525,7 +576,6 @@
}; };
}; };
services.homepage-dashboard = { services.homepage-dashboard = {
enable = true; enable = true;
listenPort = 8080; listenPort = 8080;
@ -628,7 +678,7 @@
icon = "forgejo.png"; icon = "forgejo.png";
widget = { widget = {
type = "gitea"; # Forgejo uses Gitea API type = "gitea"; # Forgejo uses Gitea API
url = "http://192.168.69.69:8184"; url = "http://192.168.69.69:8084";
key = "{{HOMEPAGE_VAR_FORGEJO_TOKEN}}"; # Create in Forgejo settings key = "{{HOMEPAGE_VAR_FORGEJO_TOKEN}}"; # Create in Forgejo settings
# Shows: repository count, issue count, pull requests # Shows: repository count, issue count, pull requests
}; };
@ -640,12 +690,12 @@
"Network & Monitoring" = [ "Network & Monitoring" = [
{ {
"AdGuard Home" = { "AdGuard Home" = {
href = "http://192.168.69.69:8183"; href = "http://192.168.69.69:8083";
description = "DNS filtering & ad blocking"; description = "DNS filtering & ad blocking";
icon = "adguard-home.png"; icon = "adguard-home.png";
widget = { widget = {
type = "adguard"; type = "adguard";
url = "http://192.168.69.69:8183"; url = "http://192.168.69.69:8083";
username = "{{HOMEPAGE_VAR_ADGUARD_USER}}"; username = "{{HOMEPAGE_VAR_ADGUARD_USER}}";
password = "{{HOMEPAGE_VAR_ADGUARD_PASS}}"; password = "{{HOMEPAGE_VAR_ADGUARD_PASS}}";
# Shows: queries blocked, % blocked, queries processed # Shows: queries blocked, % blocked, queries processed
@ -667,29 +717,29 @@
} }
{ {
"Scrutiny" = { "Scrutiny" = {
href = "http://192.168.69.69:8185"; href = "http://192.168.69.69:8085";
description = "S.M.A.R.T Monitoring"; description = "S.M.A.R.T Monitoring";
icon = "scrutiny.png"; icon = "scrutiny.png";
widget = { widget = {
type = "scrutiny"; type = "scrutiny";
url = "http://192.168.69.69:8185"; url = "http://192.168.69.69:8085";
}; };
}; };
} }
{ {
"Whats Up Docker" = { "Whats Up Docker" = {
href = "http://192.168.69.69:8186"; href = "http://192.168.69.69:8089";
description = "Docker Image Updates"; description = "Docker Image Updates";
icon = "whats-up-docker.png"; icon = "whats-up-docker.png";
widget = { widget = {
type = "whatsupdocker"; type = "whatsupdocker";
url = "http://192.168.69.69:8186"; url = "http://192.168.69.69:8089";
}; };
}; };
} }
{ {
"ntopng" = { "ntopng" = {
href = "http://192.168.69.69:8182"; href = "http://192.168.69.69:8088";
description = "Network traffic analysis"; description = "Network traffic analysis";
icon = "ntopng.png"; icon = "ntopng.png";
# No official widget, but could use iframe or custom API # No official widget, but could use iframe or custom API
@ -787,6 +837,69 @@
environmentFile = "/var/lib/homepage-dashboard/secrets.env"; environmentFile = "/var/lib/homepage-dashboard/secrets.env";
}; };
services.stalwart = {
enable = true;
openFirewall = true;
settings = {
server = {
hostname = "mx1.kempinger.at";
tls = {
enable = true;
implicit = true;
};
listener = {
smtp = {
protocol = "smtp";
bind = "192.168.69.69:25";
};
submissions = {
bind = "192.168.69.69:587";
protocol = "smtp";
tls.implicit = true;
};
imaps = {
bind = "[::]:993";
protocol = "imap";
tls.implicit = true;
};
jmap = {
bind = "0.0.0.0:8091";
url = "https://mail.kempinger.at";
protocol = "http";
};
management = {
bind = [ "127.0.0.1:8090" ];
protocol = "http";
};
};
};
resolver.type = "custom";
resolver.custom = [ "udp://127.0.0.1:53" ];
certificate."default" = {
cert = "%{file:${config.security.acme.certs."webadmin.kempinger.at".directory}/fullchain.pem}%";
private-key = "%{file:${config.security.acme.certs."webadmin.kempinger.at".directory}/key.pem}%";
};
lookup.default = {
hostname = "mx1.kempinger.at";
domain = "kempinger.at";
};
session.rcpt.directory = "'internal'";
directory."imap".lookup.domains = [ "kempinger.at" ];
# authentication.fallback-admin = {
# user = "admin";
# secret = "bcrypt-hash";
# };
};
};
services.snowflake-proxy = {
enable = true;
capacity = 50;
};
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
# nixpkgs.overlays = [ # nixpkgs.overlays = [