From 143299ccf728a01a0d75895b48b73adb4aab63b0 Mon Sep 17 00:00:00 2001 From: Stefan Kempinger Date: Tue, 17 Feb 2026 00:24:14 +0100 Subject: [PATCH 1/3] 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). --- heimserver/configuration.nix | 174 ++++++++++++++++++++++++++++------- 1 file changed, 140 insertions(+), 34 deletions(-) diff --git a/heimserver/configuration.nix b/heimserver/configuration.nix index a7ad4c6..d72cc98 100644 --- a/heimserver/configuration.nix +++ b/heimserver/configuration.nix @@ -46,8 +46,6 @@ keyMap = "de"; # useXkbConfig = true; # use xkb.options in tty. }; - # i18n.defaultLocale = "en_US.UTF-8"; - # Networking networking = { hostName = "heimserver"; @@ -64,11 +62,13 @@ firewall.enable = true; firewall.allowedTCPPorts = [ 22 + 25 53 80 443 + 587 2222 # forgejo ssh - 8184 # forgejo frontend + 8084 # forgejo frontend 8123 # homeassistant 5580 # homeassistant matter 2283 # immich @@ -78,11 +78,13 @@ 8554 # frigate rtsp 8555 # frigate rtsp 2055 # ntopng sink - 8182 # ntopng frontend - 8183 # adguardhome frontend - 8185 # scrutiny frontend - 8186 # wud frontend - 8187 # paperless frontend + 8088 # ntopng frontend + 8083 # adguardhome frontend + 8085 # scrutiny frontend + 8089 # wud frontend + 8087 # paperless frontend + 8090 # mail + 8091 # mail jmap 8080 # homepage ]; firewall.allowedUDPPorts = [ @@ -98,6 +100,14 @@ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINGHadFhDCUU/ta3p1FQgpm7NExHkyHNrJbNJP6np5w9 kempinger@ins.jku.at" ]; }; + + users.users."stalwart-mail".extraGroups = [ + "acme" + ]; + + users.users."nginx".extraGroups = [ + "acme" + ]; users.users.immich.extraGroups = [ "video" @@ -143,7 +153,30 @@ security.acme = { acceptTerms = true; 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; @@ -163,15 +196,30 @@ }; virtualHosts."kempinger.at" = { root = "/srv/website/public_html"; - locations."/" = { - index = "index.html"; - }; + locations."/".index = "index.html"; 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} = { forceSSL = true; - enableACME = true; + useACMEHost = "kempinger.at"; extraConfig = '' client_max_body_size 512M; ''; @@ -179,8 +227,8 @@ "http://localhost:${toString config.services.forgejo.settings.server.HTTP_PORT}"; }; virtualHosts."bilder.kempinger.at" = { - enableACME = true; forceSSL = true; + useACMEHost = "bilder.kempinger.at"; locations."/" = { proxyPass = "http://[::1]:${toString config.services.immich.port}"; proxyWebsockets = true; @@ -205,7 +253,7 @@ DOMAIN = "git.kempinger.at"; # You need to specify this to remove the port from URLs in the web UI. ROOT_URL = "https://${config.services.forgejo.settings.server.DOMAIN}/"; - HTTP_PORT = 8184; + HTTP_PORT = 8084; DISABLE_SSH = false; SSH_PORT = 2222; START_SSH_SERVER = true; @@ -347,8 +395,6 @@ }; }; - #services.matter-server.enable = true; - virtualisation.oci-containers = { backend = "podman"; containers.homeassistant = { @@ -440,8 +486,8 @@ "/var/run/podman/podman.sock:/var/run/docker.sock" ]; environment = { - WUD_SERVER_PORT = "8186"; - WUD_TRIGGER_COMMAND_LOCAL_CMD="echo \${display_name} can be updated to \${update_kind_remote_value}"; + WUD_SERVER_PORT = "8089"; + WUD_TRIGGER_COMMAND_LOCAL_CMD = "echo \${display_name} can be updated to \${update_kind_remote_value}"; }; extraOptions = [ "--network=host" @@ -451,7 +497,7 @@ services.ntopng = { enable = true; - httpPort = 8182; + httpPort = 8088; interfaces = [ "tcp://0.0.0.0:5556" ]; extraConfig = '' --dns-mode 1 @@ -460,6 +506,9 @@ services.influxdb2 = { enable = true; + settings = { + http-bind-address = ":8086"; + }; # provision = { # enable = true; @@ -490,7 +539,7 @@ services.scrutiny = { enable = true; - settings.web.listen.port = 8185; + settings.web.listen.port = 8085; influxdb.enable = true; collector.schedule = "hourly"; settings.web.influxdb = { @@ -504,14 +553,14 @@ enable = true; # You can select any ip and port, just make sure to open firewalls where needed host = "0.0.0.0"; - port = 8183; + port = 8083; }; - + services.paperless = { enable = true; consumptionDirIsPublic = true; address = "0.0.0.0"; - port = 8187; + port = 8087; settings = { PAPERLESS_CONSUMER_IGNORE_PATTERN = [ ".DS_STORE/*" @@ -525,7 +574,6 @@ }; }; - services.homepage-dashboard = { enable = true; listenPort = 8080; @@ -628,7 +676,7 @@ icon = "forgejo.png"; widget = { 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 # Shows: repository count, issue count, pull requests }; @@ -640,12 +688,12 @@ "Network & Monitoring" = [ { "AdGuard Home" = { - href = "http://192.168.69.69:8183"; + href = "http://192.168.69.69:8083"; description = "DNS filtering & ad blocking"; icon = "adguard-home.png"; widget = { type = "adguard"; - url = "http://192.168.69.69:8183"; + url = "http://192.168.69.69:8083"; username = "{{HOMEPAGE_VAR_ADGUARD_USER}}"; password = "{{HOMEPAGE_VAR_ADGUARD_PASS}}"; # Shows: queries blocked, % blocked, queries processed @@ -667,29 +715,29 @@ } { "Scrutiny" = { - href = "http://192.168.69.69:8185"; + href = "http://192.168.69.69:8085"; description = "S.M.A.R.T Monitoring"; icon = "scrutiny.png"; widget = { type = "scrutiny"; - url = "http://192.168.69.69:8185"; + url = "http://192.168.69.69:8085"; }; }; } { "Whats Up Docker" = { - href = "http://192.168.69.69:8186"; + href = "http://192.168.69.69:8089"; description = "Docker Image Updates"; icon = "whats-up-docker.png"; widget = { type = "whatsupdocker"; - url = "http://192.168.69.69:8186"; + url = "http://192.168.69.69:8089"; }; }; } { "ntopng" = { - href = "http://192.168.69.69:8182"; + href = "http://192.168.69.69:8088"; description = "Network traffic analysis"; icon = "ntopng.png"; # No official widget, but could use iframe or custom API @@ -787,6 +835,64 @@ 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"; + # }; + }; + }; + nixpkgs.config.allowUnfree = true; # nixpkgs.overlays = [ From bd2d37d3316a4ace382d74fd8539112278723bdb Mon Sep 17 00:00:00 2001 From: Stefan Kempinger Date: Tue, 17 Feb 2026 00:25:45 +0100 Subject: [PATCH 2/3] Nest boot settings under boot attribute --- heimserver/configuration.nix | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/heimserver/configuration.nix b/heimserver/configuration.nix index d72cc98..f4580cf 100644 --- a/heimserver/configuration.nix +++ b/heimserver/configuration.nix @@ -20,20 +20,22 @@ time.timeZone = "Europe/Vienna"; # Bootloader and kernel - boot.loader.systemd-boot.enable = true; - boot.loader.efi.canTouchEfiVariables = true; - boot.kernelParams = [ - "vga=791" - "nomodeset" - ]; - boot.kernel.sysctl = { - "net.ipv4.ip_forward" = 1; - # "net.bridge.bridge-nf-call-iptables" = 1; - # "net.bridge.bridge-nf-call-ip6tables" = 1; - }; + boot = { + loader.systemd-boot.enable = true; + loader.efi.canTouchEfiVariables = true; + kernelParams = [ + "vga=791" + "nomodeset" + ]; + kernel.sysctl = { + "net.ipv4.ip_forward" = 1; + # "net.bridge.bridge-nf-call-iptables" = 1; + # "net.bridge.bridge-nf-call-ip6tables" = 1; + }; - boot.supportedFilesystems = [ "zfs" ]; - boot.zfs.forceImportRoot = false; + supportedFilesystems = [ "zfs" ]; + zfs.forceImportRoot = false; + }; hardware.graphics.enable = true; hardware.nvidia.open = true; @@ -58,7 +60,7 @@ } ]; defaultGateway = "192.168.69.1"; - #nameservers = [ "1.1.1.1" ]; + #nameservers = [ "127.0.0.1" ]; firewall.enable = true; firewall.allowedTCPPorts = [ 22 From 6ede343e56010240e6d5d9f3533a0a16a576f172 Mon Sep 17 00:00:00 2001 From: Stefan Kempinger Date: Tue, 17 Feb 2026 08:53:50 +0100 Subject: [PATCH 3/3] Enable Snowflake proxy with capacity 50 --- heimserver/configuration.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/heimserver/configuration.nix b/heimserver/configuration.nix index f4580cf..c244737 100644 --- a/heimserver/configuration.nix +++ b/heimserver/configuration.nix @@ -894,6 +894,11 @@ # }; }; }; + + services.snowflake-proxy = { + enable = true; + capacity = 50; + }; nixpkgs.config.allowUnfree = true;