wg-easy needs iptable_nat and iptable_filter to set up masquerading for VPN traffic. These modules must be loaded at boot for the container to access iptables.
532 lines
15 KiB
Nix
532 lines
15 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, paths, self, keys, ... }:
|
|
|
|
{
|
|
# NAS Mounting
|
|
hoardingcow-mount.enable = true;
|
|
|
|
# Flakesss
|
|
nix.settings.experimental-features = [ "nix-command" "flakes" "flake-self-attrs" ];
|
|
nix.settings.trusted-users = [ "root" "gortium" ];
|
|
|
|
# Garbage collection
|
|
nix.gc = {
|
|
automatic = true;
|
|
dates = "daily"; # You can also use "daily" or a cron-like spec
|
|
options = "--delete-older-than 30d";
|
|
};
|
|
|
|
nix.settings = {
|
|
keep-derivations = true;
|
|
keep-outputs = true;
|
|
auto-optimise-store = true;
|
|
};
|
|
|
|
# Use the systemd-boot EFI boot loader.
|
|
boot.loader.systemd-boot.enable = true;
|
|
boot.loader.efi.canTouchEfiVariables = false;
|
|
|
|
# 1. Force the kernel to ignore BIOS resource locks
|
|
boot.kernelParams = [
|
|
"acpi_enforce_resources=lax"
|
|
"nct6775.force_id=0xd120" # This forces the driver to ignore BIOS locks for NCT6116
|
|
"transparent_hugepage=always" # because mucho ram
|
|
];
|
|
# 2. Load the specific drivers found by sensors-detect
|
|
boot.kernelModules = [ "nct6775" "lm96163" "iptable_nat" "iptable_filter" ];
|
|
# 3. Force the nct6775 driver to recognize the chip if it's stubborn
|
|
boot.extraModprobeConfig = ''
|
|
options nct6775 force_id=0xd280
|
|
'';
|
|
|
|
boot.blacklistedKernelModules = [ "eeepc_wmi" ];
|
|
networking.hostName = "lazyworkhorse"; # Define your hostname.
|
|
# Pick only one of the below networking options.
|
|
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
|
|
networking.networkmanager.enable = true; # Easiest to use and most distros use this by default.
|
|
networking.hostId = "deadbeef";
|
|
|
|
# Set your time zone.
|
|
time.timeZone = "America/Montreal";
|
|
|
|
# Locales
|
|
i18n.defaultLocale = "en_CA.UTF-8";
|
|
i18n.supportedLocales = [
|
|
"en_CA.UTF-8/UTF-8"
|
|
];
|
|
i18n.extraLocaleSettings = {
|
|
LC_ADDRESS = "en_CA.UTF-8";
|
|
LC_IDENTIFICATION = "en_CA.UTF-8";
|
|
LC_MEASUREMENT = "en_CA.UTF-8";
|
|
LC_MONETARY = "en_CA.UTF-8";
|
|
LC_NAME = "en_CA.UTF-8";
|
|
LC_NUMERIC = "en_CA.UTF-8";
|
|
LC_PAPER = "en_CA.UTF-8";
|
|
LC_TELEPHONE = "en_CA.UTF-8";
|
|
LC_TIME = "en_CA.UTF-8";
|
|
LC_CTYPE = "en_CA.UTF-8";
|
|
};
|
|
|
|
programs.zsh = {
|
|
enable = true;
|
|
autosuggestions.enable = true;
|
|
syntaxHighlighting.enable = true;
|
|
enableCompletion = true;
|
|
|
|
setOptions = [ "HIST_IGNORE_ALL_DUPS" "SHARE_HISTORY" ];
|
|
};
|
|
# Configure network proxy if necessary
|
|
# networking.proxy.default = "http://user:password@proxy:port/";
|
|
# networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
|
|
|
|
# Select internationalisation properties.
|
|
# i18n.defaultLocale = "en_US.UTF-8";
|
|
# console = {
|
|
# font = "Lat2-Terminus16";
|
|
# keyMap = "us";
|
|
# useXkbConfig = true; # use xkb.options in tty.
|
|
# };
|
|
|
|
# Configure keymap in X11
|
|
# services.xserver.xkb.layout = "us";
|
|
# services.xserver.xkb.options = "eurosign:e,caps:escape";
|
|
|
|
# Enable CUPS to print documents.
|
|
services.printing.enable = true;
|
|
|
|
# Enable sound.
|
|
# services.pulseaudio.enable = true;
|
|
# OR
|
|
services.pipewire = {
|
|
enable = true;
|
|
pulse.enable = true;
|
|
};
|
|
|
|
# Nix Helper cli tool
|
|
environment.sessionVariables = {
|
|
NH_FLAKE = paths.flake;
|
|
};
|
|
|
|
# Enable touchpad support (enabled default in most desktopManager).
|
|
# services.libinput.enable = true;
|
|
|
|
# nvim please
|
|
environment.variables.EDITOR = "nvim";
|
|
|
|
# List packages installed in system profile.
|
|
# You can use https://Search.nixos.org/ to find more packages (and options).
|
|
environment.systemPackages = with pkgs; [
|
|
neovim
|
|
docker-compose
|
|
wget
|
|
age
|
|
agenix
|
|
git
|
|
nh
|
|
lm_sensors
|
|
rocmPackages.rocminfo
|
|
rocmPackages.rocm-smi
|
|
nvtopPackages.amd
|
|
clinfo
|
|
ncurses
|
|
kitty.terminfo
|
|
nodejs_22
|
|
uv
|
|
openclaw
|
|
(python3.withPackages (ps: with ps; [
|
|
openai-whisper
|
|
]))
|
|
];
|
|
|
|
# Some programs need SUID wrappers, can be configured further or are
|
|
# started in user sessions.
|
|
# programs.mtr.enable = true;
|
|
# programs.gnupg.agent = {
|
|
# enable = true;
|
|
# enableSSHSupport = true;
|
|
# };
|
|
|
|
# List services that you want to enable:
|
|
|
|
# Enable the OpenSSH daemon
|
|
services.openssh = {
|
|
enable = true;
|
|
ports = [ 2424 ];
|
|
settings = {
|
|
PasswordAuthentication = false;
|
|
KbdInteractiveAuthentication = false;
|
|
# Additional hardening settings below in SERVER HARDENING section
|
|
};
|
|
hostKeys = [
|
|
{
|
|
path = "/etc/ssh/ssh_host_ed25519_key";
|
|
type = "ed25519";
|
|
}
|
|
];
|
|
};
|
|
|
|
services.dockerStacks = {
|
|
versioncontrol = {
|
|
path = self + "/assets/compose/versioncontrol";
|
|
ports = [ 2222 ];
|
|
};
|
|
|
|
network = {
|
|
path = self + "/assets/compose/network";
|
|
envFile = config.age.secrets.containers_env.path;
|
|
ports = [ 80 443 ];
|
|
};
|
|
|
|
passwordmanager = {
|
|
path = self + "/assets/compose/passwordmanager";
|
|
};
|
|
|
|
ai = {
|
|
path = self + "/assets/compose/ai";
|
|
envFile = config.age.secrets.containers_env.path;
|
|
};
|
|
|
|
cloudstorage = {
|
|
path = self + "/assets/compose/cloudstorage";
|
|
envFile = config.age.secrets.containers_env.path;
|
|
};
|
|
|
|
homeautomation = {
|
|
path = self + "/assets/compose/homeautomation";
|
|
envFile = config.age.secrets.containers_env.path;
|
|
};
|
|
|
|
authentification = {
|
|
path = self + "/assets/compose/authentification";
|
|
};
|
|
|
|
backup = {
|
|
path = self + "/assets/compose/backup";
|
|
envFile = config.age.secrets.containers_env.path;
|
|
};
|
|
|
|
coms = {
|
|
path = self + "/assets/compose/coms";
|
|
envFile = config.age.secrets.containers_env.path;
|
|
};
|
|
|
|
finance = {
|
|
path = self + "/assets/compose/finance";
|
|
};
|
|
|
|
homepage = {
|
|
path = self + "/assets/compose/homepage";
|
|
};
|
|
|
|
vpn = {
|
|
path = self + "/assets/compose/vpn";
|
|
envFile = config.age.secrets.containers_env.path;
|
|
};
|
|
|
|
# tak = {
|
|
# path = self + "/assets/compose/tak";
|
|
# };
|
|
};
|
|
|
|
services.opencode = {
|
|
enable = true;
|
|
port = 4099;
|
|
ollamaUrl = "http://127.0.0.1:11434/v1";
|
|
};
|
|
|
|
# Private host ssh key managed by agenix
|
|
age = {
|
|
identityPaths = paths.identities;
|
|
secrets = {
|
|
containers_env = {
|
|
file = ../../secrets/containers.env.age;
|
|
path = "/run/secrets/containers.env";
|
|
owner = "root";
|
|
group = "root";
|
|
mode = "0400";
|
|
};
|
|
lazyworkhorse_host_ssh_key = {
|
|
file = ../../secrets/lazyworkhorse_host_ssh_key.age;
|
|
owner = "root";
|
|
group = "root";
|
|
mode = "0600";
|
|
path = "/etc/ssh/ssh_host_ed25519_key";
|
|
};
|
|
ai_ssh_key = {
|
|
file = ../../secrets/ai_ssh_key.age;
|
|
owner = "root";
|
|
group = "root";
|
|
mode = "0600";
|
|
path = "/home/ai-worker/.ssh/ai_ssh_key";
|
|
};
|
|
openclaw_gateway_token = {
|
|
file = ../../secrets/openclaw_gateway_token.age;
|
|
owner = "root";
|
|
group = "ai-worker";
|
|
mode = "0440";
|
|
path = "/run/secrets/openclaw_gateway_token";
|
|
};
|
|
};
|
|
};
|
|
|
|
# OpenClaw Node service (host-side execution for Docker gateway)
|
|
services.openclaw-node = {
|
|
enable = true;
|
|
user = "ai-worker";
|
|
gatewayHost = "127.0.0.1";
|
|
gatewayPort = 18789;
|
|
gatewayTokenFile = "/run/secrets/openclaw_gateway_token";
|
|
displayName = "lazyworkhorse-host";
|
|
};
|
|
|
|
# Public host ssh key (kept in sync with the private one)
|
|
environment.etc."ssh/ssh_host_ed25519_key.pub".text =
|
|
"${keys.hosts.lazyworkhorse.main}";
|
|
|
|
services.fstrim.enable = true;
|
|
|
|
services.zfs.autoSnapshot.enable = true;
|
|
services.zfs.autoScrub.enable = true;
|
|
|
|
# Mi50 config
|
|
hardware.graphics = {
|
|
enable = true;
|
|
enable32Bit = true; # Useful for some compatibility layers
|
|
extraPackages = with pkgs; [
|
|
rocmPackages.clr.icd # OpenCL/HIP runtime
|
|
];
|
|
};
|
|
nixpkgs.config.rocmTargets = [ "gfx906" ];
|
|
environment.variables = {
|
|
# This "tricks" ROCm into supporting the MI50 if using newer versions
|
|
HSA_OVERRIDE_GFX_VERSION = "9.0.6";
|
|
# Ensures the system sees both GPUs
|
|
HIP_VISIBLE_DEVICES = "0,1";
|
|
};
|
|
|
|
# Open ports in the firewall.
|
|
# networking.firewall.allowedTCPPorts = [ ... ];
|
|
# networking.firewall.allowedUDPPorts = [ ... ];
|
|
# Or disable the firewall altogether.
|
|
# networking.firewall.enable = false;
|
|
|
|
# =============================================================================
|
|
# SERVER HARDENING - Firewall, Fail2ban, SSH, Kernel
|
|
# =============================================================================
|
|
|
|
# Firewall - default deny, explicit allow
|
|
networking.firewall = {
|
|
# Enable firewall with default deny policy (NixOS firewall denies all by default)
|
|
enable = true;
|
|
allowPing = true;
|
|
|
|
# Only essential ports exposed to internet
|
|
allowedTCPPorts = [
|
|
2424 # SSH (non-standard port)
|
|
2222 # Gitea (version control)
|
|
80 # HTTP (Traefik redirect)
|
|
443 # HTTPS (Traefik)
|
|
# 8000 # Portainer - REVIEW: internal only?
|
|
# 4242 # Coms - REVIEW: internal only?
|
|
# 5000 # TAK API - REVIEW: internal only?
|
|
# 8087 # TAK Connect - REVIEW: internal only?
|
|
# 8089 # TAK Management - REVIEW: internal only?
|
|
];
|
|
|
|
allowedUDPPorts = [
|
|
51820 # WireGuard VPN
|
|
];
|
|
|
|
# Rate limiting and attack prevention
|
|
extraCommands = ''
|
|
# Rate limit SSH connections (max 4 new connections per 60 seconds)
|
|
iptables -A INPUT -p tcp --dport 2424 -m state --state NEW -m recent --set
|
|
iptables -A INPUT -p tcp --dport 2424 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
|
|
|
|
# Rate limit HTTP/HTTPS (protects Traefik)
|
|
iptables -A INPUT -p tcp --dport 80 -m state --state NEW -m limit --limit 25/minute --limit-burst 100 -j ACCEPT
|
|
iptables -A INPUT -p tcp --dport 443 -m state --state NEW -m limit --limit 25/minute --limit-burst 100 -j ACCEPT
|
|
|
|
# Drop invalid packets
|
|
iptables -A INPUT -m state --state INVALID -j DROP
|
|
|
|
# Log dropped packets (rate limited)
|
|
iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "IPTables-Dropped: " --log-level 4
|
|
'';
|
|
};
|
|
|
|
# Fail2ban - automatic IP banning
|
|
services.fail2ban = {
|
|
enable = true;
|
|
maxretry = 3;
|
|
bantime = "1h";
|
|
banaction = "iptables-multiport";
|
|
|
|
jails = {
|
|
# SSH brute force protection (uses systemd journal backend)
|
|
sshd = {
|
|
enabled = true;
|
|
settings = {
|
|
filter = "sshd";
|
|
port = "2424";
|
|
maxretry = 3;
|
|
bantime = "1h";
|
|
};
|
|
};
|
|
|
|
# Recidive - ban repeat offenders for 1 week
|
|
recidive = {
|
|
enabled = true;
|
|
settings = {
|
|
filter = "recidive";
|
|
logpath = "/var/log/fail2ban.log";
|
|
bantime = "1w";
|
|
findtime = "1d";
|
|
maxretry = 3;
|
|
};
|
|
};
|
|
|
|
# HTTP authentication failures (Traefik)
|
|
http-auth = {
|
|
enabled = true;
|
|
settings = {
|
|
filter = "traefik-auth";
|
|
port = "80,443";
|
|
logpath = "/var/log/traefik/access.log";
|
|
maxretry = 5;
|
|
bantime = "1h";
|
|
};
|
|
};
|
|
|
|
# HTTP scanning/attacks (Traefik)
|
|
http-botsearch = {
|
|
enabled = true;
|
|
settings = {
|
|
filter = "traefik-botsearch";
|
|
port = "80,443";
|
|
logpath = "/var/log/traefik/access.log";
|
|
maxretry = 2;
|
|
bantime = "2h";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
# Custom fail2ban filters for Traefik
|
|
environment.etc."fail2ban/filter.d/traefik-auth.conf".text = ''
|
|
[Definition]
|
|
failregex = ^<HOST> -.*"(GET|POST|HEAD|PUT|DELETE).*" (401|403) \d+.*$
|
|
ignoreregex =
|
|
'';
|
|
|
|
environment.etc."fail2ban/filter.d/traefik-botsearch.conf".text = ''
|
|
[Definition]
|
|
failregex = ^<HOST> -.*"(GET|POST|HEAD|PUT|DELETE).*" 404 \d+.*$
|
|
^<HOST> -.*"(GET|POST|HEAD|PUT|DELETE).*/(\.|wp-|php|admin|login|xmlrpc|\.env|\.git|\.aws|\.azure).*" \d+.*$
|
|
ignoreregex =
|
|
'';
|
|
|
|
# SSH hardening
|
|
services.openssh.settings = {
|
|
PermitRootLogin = "no";
|
|
MaxAuthTries = 3;
|
|
MaxSessions = 5;
|
|
LoginGraceTime = 30;
|
|
ClientAliveInterval = 300;
|
|
ClientAliveCountMax = 2;
|
|
PermitEmptyPasswords = "no";
|
|
ChallengeResponseAuthentication = "no";
|
|
UsePAM = true;
|
|
LogLevel = "VERBOSE";
|
|
X11Forwarding = false;
|
|
AllowTcpForwarding = "no";
|
|
AllowAgentForwarding = "no";
|
|
PermitTunnel = "no";
|
|
};
|
|
|
|
# Kernel network hardening
|
|
boot.kernel.sysctl = {
|
|
# IP Spoofing protection
|
|
"net.ipv4.conf.all.rp_filter" = 1;
|
|
"net.ipv4.conf.default.rp_filter" = 1;
|
|
|
|
# Ignore ICMP broadcasts
|
|
"net.ipv4.icmp_echo_ignore_broadcasts" = 1;
|
|
|
|
# Disable source routing
|
|
"net.ipv4.conf.all.accept_source_route" = 0;
|
|
"net.ipv4.conf.default.accept_source_route" = 0;
|
|
"net.ipv6.conf.all.accept_source_route" = 0;
|
|
"net.ipv6.conf.default.accept_source_route" = 0;
|
|
|
|
# Disable redirects
|
|
"net.ipv4.conf.all.send_redirects" = 0;
|
|
"net.ipv4.conf.default.send_redirects" = 0;
|
|
|
|
# SYN flood protection
|
|
"net.ipv4.tcp_syncookies" = 1;
|
|
"net.ipv4.tcp_max_syn_backlog" = 2048;
|
|
"net.ipv4.tcp_synack_retries" = 2;
|
|
"net.ipv4.tcp_syn_retries" = 5;
|
|
|
|
# Log martian packets
|
|
"net.ipv4.conf.all.log_martians" = 1;
|
|
"net.ipv4.conf.default.log_martians" = 1;
|
|
|
|
# Ignore redirects
|
|
"net.ipv4.conf.all.accept_redirects" = 0;
|
|
"net.ipv4.conf.default.accept_redirects" = 0;
|
|
"net.ipv4.conf.all.secure_redirects" = 0;
|
|
"net.ipv4.conf.default.secure_redirects" = 0;
|
|
"net.ipv6.conf.all.accept_redirects" = 0;
|
|
"net.ipv6.conf.default.accept_redirects" = 0;
|
|
|
|
# Connection tuning
|
|
"net.core.somaxconn" = 4096;
|
|
"net.core.netdev_max_backlog" = 65536;
|
|
"net.ipv4.tcp_max_orphans" = 65536;
|
|
"net.ipv4.tcp_fin_timeout" = 15;
|
|
"net.ipv4.tcp_keepalive_time" = 300;
|
|
"net.ipv4.tcp_keepalive_probes" = 5;
|
|
"net.ipv4.tcp_keepalive_intvl" = 15;
|
|
};
|
|
|
|
# Audit logging
|
|
security.auditd.enable = true;
|
|
|
|
# Fail2ban log directory
|
|
systemd.tmpfiles.rules = [
|
|
"d /var/log/fail2ban 0755 root root -"
|
|
"d /var/log/traefik 0755 root root -"
|
|
];
|
|
|
|
# Copy the NixOS configuration file and link it from the resulting system
|
|
# (/run/current-system/configuration.nix). This is useful in case you
|
|
# accidentally delete configuration.nix.
|
|
# system.copySystemConfiguration = true;
|
|
|
|
# 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.stateVersion = "25.05"; # Did you read the comment?
|
|
|
|
}
|
|
|