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";
# Bootloader and kernel
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.kernelParams = [
boot = {
loader.systemd-boot.enable = true;
loader.efi.canTouchEfiVariables = true;
kernelParams = [
"vga=791"
"nomodeset"
];
boot.kernel.sysctl = {
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;
@ -46,8 +48,6 @@
keyMap = "de";
# useXkbConfig = true; # use xkb.options in tty.
};
# i18n.defaultLocale = "en_US.UTF-8";
# Networking
networking = {
hostName = "heimserver";
@ -60,15 +60,17 @@
}
];
defaultGateway = "192.168.69.1";
#nameservers = [ "1.1.1.1" ];
#nameservers = [ "127.0.0.1" ];
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 +80,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 = [
@ -99,6 +103,14 @@
];
};
users.users."stalwart-mail".extraGroups = [
"acme"
];
users.users."nginx".extraGroups = [
"acme"
];
users.users.immich.extraGroups = [
"video"
"render"
@ -143,7 +155,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 +198,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 +229,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 +255,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 +397,6 @@
};
};
#services.matter-server.enable = true;
virtualisation.oci-containers = {
backend = "podman";
containers.homeassistant = {
@ -440,7 +488,7 @@
"/var/run/podman/podman.sock:/var/run/docker.sock"
];
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}";
};
extraOptions = [
@ -451,7 +499,7 @@
services.ntopng = {
enable = true;
httpPort = 8182;
httpPort = 8088;
interfaces = [ "tcp://0.0.0.0:5556" ];
extraConfig = ''
--dns-mode 1
@ -460,6 +508,9 @@
services.influxdb2 = {
enable = true;
settings = {
http-bind-address = ":8086";
};
# provision = {
# enable = true;
@ -490,7 +541,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 +555,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 +576,6 @@
};
};
services.homepage-dashboard = {
enable = true;
listenPort = 8080;
@ -628,7 +678,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 +690,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 +717,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 +837,69 @@
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.overlays = [