NixOS-Configuration/heimserver/configuration.nix
Stefan Kempinger 1ffb260e88 Enable Scrutiny, InfluxDB2 and homepage dashboard
Open firewall ports for Scrutiny (8185) and homepage (8080).
Add binutils to systemPackages and enable services.influxdb2.
Configure Scrutiny to use InfluxDB.
Enable homepage-dashboard with widgets, bookmarks and an
environmentFile for secrets.
Fix docker pull string formatting and minor whitespace cleanup.
2026-01-25 23:42:34 +01:00

782 lines
21 KiB
Nix

# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page, on
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
{
config,
lib,
pkgs,
specialArgs,
...
}:
{
imports = [
# Include the results of the hardware scan.
./hardware-configuration.nix
];
# System basics
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.supportedFilesystems = [ "zfs" ];
boot.zfs.forceImportRoot = false;
hardware.graphics.enable = true;
hardware.nvidia.open = true;
hardware.nvidia-container-toolkit.enable = true;
services.xserver.videoDrivers = [ "nvidia" ];
# Console and locale
console = {
font = "Lat2-Terminus16";
keyMap = "de";
# useXkbConfig = true; # use xkb.options in tty.
};
# i18n.defaultLocale = "en_US.UTF-8";
# Networking
networking = {
hostName = "heimserver";
useDHCP = false;
hostId = "5506a8e7";
interfaces.eth0.ipv4.addresses = [
{
address = "192.168.69.69";
prefixLength = 24;
}
];
defaultGateway = "192.168.69.1";
#nameservers = [ "1.1.1.1" ];
firewall.enable = true;
firewall.allowedTCPPorts = [
22
53
80
443
2222 # forgejo ssh
8184 # forgejo frontend
8123 # homeassistant
5580 # homeassistant matter
2283 # immich
3003 # immich ml
1984 # frigate go2rtc
8971 # frigate
8554 # frigate rtsp
8555 # frigate rtsp
2055 # ntopng sink
8182 # ntopng frontend
8183 # adguardhome frontend
8185 # scrutiny frontend
8080 # homepage
];
firewall.allowedUDPPorts = [
53
8555 # frigate rtsp
2055 # ntopng sink
];
};
# Users
users.users.root = {
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINGHadFhDCUU/ta3p1FQgpm7NExHkyHNrJbNJP6np5w9 kempinger@ins.jku.at"
];
};
users.users.immich.extraGroups = [
"video"
"render"
];
# Security
# security.sudo.wheelNeedsPassword = false;
# Packages
environment.systemPackages = with pkgs; [
nano
wget
curl
git
htop
docker-compose
nixd
nixfmt
systemd
inetutils
smartmontools
parted
borgbackup
lshw
binutils
influxdb2
nil
];
# Enable SSH for root
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
PermitRootLogin = "prohibit-password"; # Allow root with SSH keys only
};
};
hardware.bluetooth.enable = true;
services.blueman.enable = true;
security.acme = {
acceptTerms = true;
defaults.email = "mail@kempinger.xyz";
certs."kempinger.at".domain = "*.kempinger.at";
};
#services.resolved.enable = true;
services.nginx = {
enable = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
recommendedProxySettings = true;
virtualHosts."192.168.69.69" = {
default = true;
root = "/srv/website/public_html";
locations."/" = {
index = "index.html";
};
};
virtualHosts."kempinger.at" = {
root = "/srv/website/public_html";
locations."/" = {
index = "index.html";
};
forceSSL = true;
enableACME = true;
};
virtualHosts.${config.services.forgejo.settings.server.DOMAIN} = {
forceSSL = true;
enableACME = true;
extraConfig = ''
client_max_body_size 512M;
'';
locations."/".proxyPass =
"http://localhost:${toString config.services.forgejo.settings.server.HTTP_PORT}";
};
virtualHosts."bilder.kempinger.at" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://[::1]:${toString config.services.immich.port}";
proxyWebsockets = true;
recommendedProxySettings = true;
extraConfig = ''
client_max_body_size 50000M;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
'';
};
};
};
services.forgejo = {
enable = true;
database.type = "postgres";
# Enable support for Git Large File Storage
lfs.enable = true;
settings = {
server = {
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;
DISABLE_SSH = false;
SSH_PORT = 2222;
START_SSH_SERVER = true;
};
# You can temporarily allow registration to create an admin user.
service.DISABLE_REGISTRATION = true;
# Add support for actions, based on act: https://github.com/nektos/act
actions = {
ENABLED = true;
DEFAULT_ACTIONS_URL = "github";
};
# Sending emails is completely optional
# You can send a test email from the web UI at:
# Profile Picture > Site Administration > Configuration > Mailer Configuration
# mailer = {
# ENABLED = true;
# SMTP_ADDR = "mail.kempinger.at";
# FROM = "noreply@${srv.DOMAIN}";
# USER = "noreply@${srv.DOMAIN}";
# };
};
dump = {
enable = true;
backupDir = "/backup/forgejo";
age = "6 months";
interval = "weekly";
};
};
# systemd.services.forgejo.preStart =
# ''
# ${lib.getExe cfg.package} admin user create --admin --email "root@localhost" --username crazychaoz --password temp123 || true
# '';
# services.borgbackup.jobs."forgejo" = {
# paths = config.services.forgejo.repositoryRoot;
# repo = "/backup/forgejo";
# startAt = "Sat 04:00";
# compression = "zstd";
# encryption.mode = "none";
# prune.keep = {
# last = 2;
# };
# };
services.immich = {
enable = true;
accelerationDevices = null;
port = 2283;
};
services.borgbackup.jobs."immich" = {
paths = config.services.immich.mediaLocation;
repo = "/backup/immich";
startAt = "Sat 04:00";
compression = "zstd";
encryption.mode = "none";
prune.keep = {
last = 2;
};
};
services.fail2ban = {
enable = true;
# Common global settings
maxretry = 3;
bantime = "48h";
jails = {
"immich-web-auth" = {
settings = {
enabled = true;
filter = "immich";
backend = "systemd"; # Crucial: Reads from journalctl
# Optimizes performance by only looking at logs with this identifier
# Based on your log: "heimserver immich[...]" -> identifier is "immich"
journalmatch = "_SYSTEMD_UNIT=immich-server.service + SYSLOG_IDENTIFIER=immich";
action = "iptables-allports";
maxretry = 2;
findtime = 600;
};
};
"forgejo-auth" = {
settings = {
enabled = true;
filter = "forgejo";
backend = "systemd"; # Crucial: Reads from journalctl
# Optimizes performance by only looking at logs with this identifier
# Based on your log: "heimserver immich[...]" -> identifier is "immich"
journalmatch = "_SYSTEMD_UNIT=forgejo.service + SYSLOG_IDENTIFIER=forgejo";
action = "iptables-allports";
maxretry = 2;
findtime = 600;
};
};
};
};
environment.etc."fail2ban/filter.d/immich.local".text = ''
[Definition]
# Matches: ... Failed login attempt for user ... from ip address <HOST>
# The <HOST> macro automatically grabs the IP at the end
failregex = immich.*Failed login attempt for user .* from ip address <HOST>
ignoreregex =
'';
environment.etc."fail2ban/filter.d/forgejo.local".text = ''
[Definition]
# Matches: ... Failed login attempt for user ... from ip address <HOST>
# The <HOST> macro automatically grabs the IP at the end
failregex = forgejo.*Failed authentication attempt from <HOST>:.*
ignoreregex =
'';
environment.etc."magic-update-script.sh".text = ''
#!/usr/bin/env bash
set -euo pipefail
echo "Pulling latest container images..."
${lib.concatMapStringsSep "\n" (
name: "docker pull ${config.virtualisation.oci-containers.containers.${name}.image}"
) (builtins.attrNames config.virtualisation.oci-containers.containers)}
echo "All images updated successfully!"
'';
# Virtualisation
virtualisation = {
containers.enable = true;
podman = {
enable = true;
dockerCompat = true;
defaultNetwork.settings.dns_enabled = true; # Required for containers under podman-compose to be able to talk to each other.
};
};
#services.matter-server.enable = true;
virtualisation.oci-containers = {
backend = "podman";
containers.homeassistant = {
#autoStart = true;
volumes = [
"home-assistant:/config"
"/run/dbus:/run/dbus:ro"
"/backup/home-assistant:/config/backups"
];
environment.TZ = "Europe/Berlin";
# Note: The image will not be updated on rebuilds, unless the version label changes
image = "ghcr.io/home-assistant/home-assistant:stable";
extraOptions = [
# Use the host network namespace for all sockets
"--network=host"
# Pass devices into the container, so Home Assistant can discover and make use of them
#"--device=/dev/ttyACM0:/dev/ttyACM0"
"--privileged"
];
};
containers.matter-server = {
#autoStart = true;
volumes = [
"matter-server:/config"
"/run/dbus:/run/dbus:ro"
];
environment.TZ = "Europe/Berlin";
# Note: The image will not be updated on rebuilds, unless the version label changes
image = "ghcr.io/home-assistant-libs/python-matter-server:stable";
extraOptions = [
"--network=host"
"--privileged"
];
};
containers.mosquitto = {
#autoStart = true;
volumes = [
"mosquitto:/mosquitto"
"/run/dbus:/run/dbus:ro"
"/etc/localtime:/etc/localtime:ro"
];
# Note: The image will not be updated on rebuilds, unless the version label changes
image = "eclipse-mosquitto";
extraOptions = [
"--network=host"
];
};
containers.frigate = {
#autoStart = true;
volumes = [
"frigate:/config"
"/run/dbus:/run/dbus:ro"
"/etc/localtime:/etc/localtime:ro"
"/root/frigate-models:/config/model_cache"
];
environment.FRIGATE_RTSP_PASSWORD = "password123";
# Note: The image will not be updated on rebuilds, unless the version label changes
image = "ghcr.io/blakeblackshear/frigate:stable-tensorrt";
devices = [
"nvidia.com/gpu=all"
];
extraOptions = [
"--shm-size=512m"
"--network=host"
"--privileged"
"--mount=type=tmpfs,target=/tmp/cache,tmpfs-size=4000000000"
];
};
containers.netflow2ng = {
# Note: The image will not be updated on rebuilds, unless the version label changes
image = "synfinatic/netflow2ng:v0.1.0";
cmd = [
"-a"
"0.0.0.0:2055"
"-m"
"0.0.0.0:8181"
"-z"
"tcp://127.0.0.1:5556"
"--tlv"
];
extraOptions = [
"--network=host"
];
};
};
services.ntopng = {
enable = true;
httpPort = 8182;
interfaces = [ "tcp://0.0.0.0:5556" ];
extraConfig = ''
--dns-mode 1
--local-networks "185.27.122.0/24=WAN,192.168.69.0/24=LAN,192.168.42.0/24=Wireguard"'';
};
services.influxdb2 = {
enable = true;
# provision = {
# enable = true;
# initialSetup = {
# organization = "default";
# bucket = "default";
# passwordFile = pkgs.writeText "admin-pw" "ExAmPl3PA55W0rD";
# tokenFile = pkgs.writeText "admin-token" "verysecureadmintoken";
# };
# };
};
services.geoipupdate = {
enable = true;
settings = {
AccountID = 1284637;
DatabaseDirectory = "/var/lib/GeoIP";
LicenseKey = {
_secret = "/root/maxmind_license_key";
};
EditionIDs = [
"GeoLite2-ASN"
"GeoLite2-City"
"GeoLite2-Country"
];
};
};
services.scrutiny = {
enable = true;
settings.web.listen.port = 8185;
influxdb.enable = true;
collector.schedule = "hourly";
settings.web.influxdb = {
bucket = "default";
org = "default";
token = "verysecureadmintoken";
};
};
services.adguardhome = {
enable = true;
# You can select any ip and port, just make sure to open firewalls where needed
host = "0.0.0.0";
port = 8183;
};
services.homepage-dashboard = {
enable = true;
listenPort = 8080;
allowedHosts = "192.168.69.69:8080";
settings = {
title = "Heimserver";
theme = "dark";
color = "slate";
layout = {
"Home Automation" = {
style = "row";
columns = 3;
};
"Media & Storage" = {
style = "row";
columns = 2;
};
"Development" = {
style = "row";
columns = 2;
};
"Network & Monitoring" = {
style = "row";
columns = 3;
};
};
};
services = [
{
"Home Automation" = [
{
"Home Assistant" = {
href = "http://192.168.69.69:8123";
description = "Home automation platform";
icon = "home-assistant.png";
widget = {
type = "homeassistant";
url = "http://192.168.69.69:8123";
key = "{{HOMEPAGE_VAR_HASS_TOKEN}}"; # Create long-lived token in HA
# Shows: entities count, automations, sensors
custom = [
{
state = "sensor.indoor_outdoor_meter_5109_temperature";
label = "Außentemperatur";
}
{
state = "weather.forecast_home";
label = "Windgeschwindigkeit";
value = "{attributes.wind_speed} {attributes.wind_speed_unit}";
}
{
state = "sensor.load_power";
label = "Stromverbrauch";
}
];
};
};
}
{
"Frigate" = {
href = "https://192.168.69.69:8971";
description = "NVR with AI detection";
icon = "frigate.png";
widget = {
type = "frigate";
url = "http://127.0.0.1:5000";
# Shows: camera status, detection counts, storage usage
};
};
}
];
}
{
"Media & Storage" = [
{
"Immich" = {
href = "https://bilder.kempinger.at";
description = "Photo & video management";
icon = "immich.png";
widget = {
type = "immich";
url = "https://bilder.kempinger.at";
key = "{{HOMEPAGE_VAR_IMMICH_API_KEY}}"; # Generate in Immich settings
version = 2;
# Shows: photo count, video count, storage usage, user count
};
};
}
];
}
{
"Development" = [
{
"Forgejo" = {
href = "https://git.kempinger.at";
description = "Git repository hosting";
icon = "forgejo.png";
widget = {
type = "gitea"; # Forgejo uses Gitea API
url = "http://192.168.69.69:8184";
key = "{{HOMEPAGE_VAR_FORGEJO_TOKEN}}"; # Create in Forgejo settings
# Shows: repository count, issue count, pull requests
};
};
}
{
"Github" = {
href = "https://github.com/CrazyChaoz";
description = "Github";
icon = "github.png";
};
}
];
}
{
"Network & Monitoring" = [
{
"AdGuard Home" = {
href = "http://192.168.69.69:8183";
description = "DNS filtering & ad blocking";
icon = "adguard-home.png";
widget = {
type = "adguard";
url = "http://192.168.69.69:8183";
username = "{{HOMEPAGE_VAR_ADGUARD_USER}}";
password = "{{HOMEPAGE_VAR_ADGUARD_PASS}}";
# Shows: queries blocked, % blocked, queries processed
};
};
}
{
"Mikrotik" = {
href = "https://192.168.69.1";
description = "My Router";
icon = "mikrotik.png";
widget = {
type = "mikrotik";
url = "https://192.168.69.1:443";
username = "{{HOMEPAGE_VAR_USER}}";
password = "{{HOMEPAGE_VAR_PASS}}";
};
};
}
{
"Scrutiny" = {
href = "http://192.168.69.69:8185";
description = "S.M.A.R.T Monitoring";
icon = "scrutiny.png";
widget = {
type = "scrutiny";
url = "http://192.168.69.69:8185";
};
};
}
{
"ntopng" = {
href = "http://192.168.69.69:8182";
description = "Network traffic analysis";
icon = "ntopng.png";
# No official widget, but could use iframe or custom API
};
}
];
}
];
widgets = [
{
resources = {
info = true;
cpu = true;
memory = true;
"gpu:0" = true;
disk = "/";
uptime = true;
units = "metric";
refresh = 3000; # Refresh every 3 seconds
};
}
{
search = {
provider = "duckduckgo";
target = "_blank";
};
}
# Optional: Add datetime widget
{
datetime = {
text_size = "xl";
format = {
dateStyle = "long";
timeStyle = "short";
hour12 = false;
};
};
}
];
bookmarks = [
{
"Services" = [
{
"Website" = [
{
abbr = "WEB";
href = "https://kempinger.at";
}
];
}
];
}
{
"Documentation" = [
{
"NixOS" = [
{
abbr = "NX";
href = "https://nixos.org/manual/nixos/stable/";
}
];
}
{
"Home Assistant" = [
{
abbr = "HA";
href = "https://www.home-assistant.io/docs/";
}
];
}
{
"Frigate" = [
{
abbr = "FR";
href = "https://docs.frigate.video/";
}
];
}
];
}
];
# Environment variables for API keys (more secure than hardcoding)
environmentFile = "/var/lib/homepage-dashboard/secrets.env";
};
nixpkgs.config.allowUnfree = true;
nixpkgs.overlays = [
(final: prev: {
onnxruntime = prev.onnxruntime.override { cudaSupport = true; };
})
];
# Nix settings
nix.settings.experimental-features = [
"nix-command"
"flakes"
];
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 7d";
};
# Documentation for stateVersion
# This option defines the first version of NixOS you have installed on this particular machine,
# and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
#
# Most users should NEVER change this value after the initial install, for any reason,
# even if you've upgraded your system to a new NixOS release.
#
# This value does NOT affect the Nixpkgs version your packages and OS are pulled from,
# so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how
# to actually do that.
#
# This value being lower than the current NixOS release does NOT mean your system is
# out of date, out of support, or vulnerable.
#
# Do NOT change this value unless you have manually inspected all the changes it would make to your configuration,
# and migrated your data accordingly.
#
# For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
system.configurationRevision = lib.mkIf (specialArgs.inputs.self ? rev) specialArgs.inputs.self.rev;
system.stateVersion = "25.05"; # Did you read the comment?
}