fix: remove duplicate roku-remote block, fix modesetting.enabled->enable

This commit is contained in:
Harley
2026-07-10 23:02:44 -04:00
parent 62c60d1763
commit 3bf88b955c
2 changed files with 1500 additions and 622 deletions
+628 -622
View File
File diff suppressed because it is too large Load Diff
+872
View File
@@ -0,0 +1,872 @@
{ config, lib, pkgs, ... }:
{
imports = [ ./hardware-configuration.nix ];
## ── Storage drives ──────────────────────────────────────
fileSystems."/storage/20TB" =
{ device = "/dev/disk/by-uuid/ec172aa7-85f7-4375-a661-88fc66221620";
fsType = "ext4";
};
## ── Root btrfs mount options (compress, noatime) ───────────
fileSystems."/".options = [ "compress=zstd" "noatime" "space_cache=v2" ];
## ── Snapper (automatic btrfs snapshots) ──────────────────
services.snapper = {
configs."root" = {
SUBVOLUME = "/";
ALLOW_USERS = [ "harley" ];
TIMELINE_CREATE = true;
TIMELINE_CLEANUP = true;
TIMELINE_MIN_AGE = "1800";
TIMELINE_LIMIT_HOURLY = "6";
TIMELINE_LIMIT_DAILY = "7";
TIMELINE_LIMIT_WEEKLY = "4";
TIMELINE_LIMIT_MONTHLY = "6";
TIMELINE_LIMIT_YEARLY = "0";
NUMBER_CLEANUP = true;
NUMBER_LIMIT = "50";
};
};
## ── Snapper config for .file7.img (manual snapshots) ───
services.snapper.configs."file7" = {
SUBVOLUME = "/mnt/.recov";
ALLOW_USERS = [ "harley" ];
TIMELINE_CREATE = false;
NUMBER_CLEANUP = false;
};
## ── Atomic user tmpfs home (ephemeral, wiped on reboot) ─
fileSystems."/home/atomic" = {
device = "tmpfs";
fsType = "tmpfs";
options = [ "mode=0700" "uid=1001" "gid=100" "noatime" ];
};
## ── Populate atomic user home from seed on boot ─────────
systemd.services.populate-atomic-home = {
description = "Populate atomic user's tmpfs home from seed";
after = [ "home-atomic.mount" ];
requires = [ "home-atomic.mount" ];
wantedBy = [ "home-atomic.mount" "systemd-logind.service" ];
script = ''
cp -rT /etc/nixos/atomic-skel/ /home/atomic/
chown -R 1001:100 /home/atomic/
'';
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
};
boot.loader.efi.canTouchEfiVariables = true;
boot.loader.systemd-boot.enable = true;
boot.initrd.supportedFilesystems = [ "btrfs" ];
## ── Remote LUKS unlock via SSH in initramfs ──────────────
boot.initrd.network.enable = true;
boot.initrd.network.ssh.enable = true;
boot.initrd.network.ssh.hostKeys = [ ./secrets/initrd/ssh_host_ed25519_key ];
boot.initrd.network.ssh.authorizedKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH4OAJ8iC7CQIcedVbU86xgoL4YkMWAG9bU6aj9v4Skb harley@utumno"
];
## ── Networking ───────────────────────────────────────────
networking.hostName = "utumno";
networking.networkmanager.enable = true;
## ── Time / Locale ────────────────────────────────────────
time.timeZone = "America/New_York";
i18n.defaultLocale = "en_US.UTF-8";
## ── Greeter user for greetd login on tty6 ────────────────
users.users.greeter = {
isSystemUser = true;
group = "nogroup";
extraGroups = [ "video" "input" "seat" ];
description = "greetd login greeter";
};
## ── Users ────────────────────────────────────────────────
users.users.harley = {
isNormalUser = true;
shell = lib.mkForce pkgs.zsh;
extraGroups = [ "wheel" "networkmanager" "docker" "input" "video" ];
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH4OAJ8iC7CQIcedVbU86xgoL4YkMWAG9bU6aj9v4Skb harley@utumno"
];
};
## ── Atomic user (ephemeral home in RAM) ──────────────────
## ── Roman user ─────────────────────────────────────────
users.users.roman = {
isNormalUser = true;
description = "Roman";
home = "/home/roman";
createHome = true;
shell = pkgs.bash;
extraGroups = [ "wheel" "users" ];
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH4OAJ8iC7CQIcedVbU86xgoL4YkMWAG9bU6aj9v4Skb harley@utumno"
];
};
users.users.atomic = {
isNormalUser = true;
description = "Ephemeral user - home directory in RAM (tmpfs)";
home = "/home/atomic";
group = "users";
createHome = false;
extraGroups = [ "wheel" ];
shell = lib.mkForce pkgs.zsh;
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH4OAJ8iC7CQIcedVbU86xgoL4YkMWAG9bU6aj9v4Skb harley@utumno"
];
};
## ── Dedicated tty6 for atomic user (auto-login + Plasma) ─
systemd.services."getty@tty6" = {
enable = true;
serviceConfig = {
ExecStart = [
""
"${pkgs.util-linux}/bin/agetty --noclear 38400 linux"
];
Type = "idle";
Restart = "always";
RestartSec = 0;
};
};
## ── Sudo ─────────────────────────────────────────────────
security.sudo.extraRules = [
{ groups = [ "wheel" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; }
];
## ── greetd PAM service ──────────────────────────────────
security.pam.services.greetd = {};
## ── GTK theme settings (Tela-circle-dark icons, Catppuccin) ─
environment.variables = {
GTK_THEME = "Adwaita:dark";
GTK_ICON_THEME = "Tela-circle-dark";
HERMES_HOME = "/storage/20TB/Agents/.hermes";
};
# Make theme packages available to all users
environment.sessionVariables = {
XDG_DATA_DIRS = [
"${pkgs.tela-circle-icon-theme}/share/icons"
"${pkgs.catppuccin}/share/plasma"
];
};
## ── Allow unfree packages (NVIDIA driver, etc.)
nixpkgs.config.allowUnfree = true;
## ── NVIDIA GPU ───────────────────────────────────────────
services.xserver.videoDrivers = [ "nvidia" ];
hardware.graphics.enable = true;
hardware.nvidia = {
modesetting.enable = true;
powerManagement.enable = false;
open = false;
nvidiaSettings = true;
};
## ── Bluetooth ───────────────────────────────────────────
hardware.bluetooth = {
enable = true;
powerOnBoot = true;
settings.General.Experimental = true;
};
services.blueman.enable = true;
## ── Display Manager (SDDM) + Plasma 6 Desktop ───────────
services.xserver.enable = true;
services.xserver.displayManager.sddm.enable = true;
services.desktopManager.plasma6.enable = true;
## ── Sound (PipeWire) ─────────────────────────────────────
services.pipewire = {
enable = true;
pulse.enable = true;
alsa.enable = true;
alsa.support32Bit = true;
};
## ── SSH Daemon ───────────────────────────────────────────
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
PermitRootLogin = "prohibit-password";
};
};
## ── ZSH with oh-my-zsh ────────────────────────────────────
programs.zsh.enable = true;
programs.zsh.ohMyZsh = {
enable = true;
theme = "jonathan";
plugins = [
"git"
"sudo"
"web-search"
"history"
"extract"
"colored-man-pages"
];
customPkgs = [
(pkgs.runCommand "jonathan-zsh-theme" {
themeFile = builtins.readFile ./jonathan.zsh-theme;
passAsFile = [ "themeFile" ];
} ''
mkdir -p $out/share/zsh/themes
cp "$themeFilePath" $out/share/zsh/themes/jonathan.zsh-theme
'')
];
};
programs.zsh.syntaxHighlighting.enable = true;
programs.zsh.interactiveShellInit = ''
source ${pkgs.zsh-autosuggestions}/share/zsh-autosuggestions/zsh-autosuggestions.zsh
'';
programs.zsh.promptInit = ''
bindkey '^F' autosuggest-accept
bindkey '^[[C' autosuggest-accept
'';
programs.zsh.shellAliases = {
cl = "clear";
tt = "tmux";
tta = "tmux attach";
ttls = "tmux ls";
xclipp = "xclip -selection clipboard";
palantir = "ssh palantir";
rcp = "rsync -avP --partial --append";
};
## ── Nix settings (flakes, nix-command) ───────────────────
nix.settings = {
experimental-features = [ "nix-command" "flakes" ];
trusted-users = [ "harley" ];
};
## ── Overlay: stub broken python doc build (docutils bug in 26.11) ─
nixpkgs.overlays = [
(final: prev: {
python312 = prev.python312.overrideAttrs (old: {
passthru = old.passthru // { doc = null; };
});
})
];
## ── Essential system packages ────────────────────────────
environment.systemPackages = with pkgs; [
vim wget curl git htop btop neovim tmux screen
ripgrep fd jq tree unzip pciutils usbutils
shadow xclip
fzf bat tldr ncdu fastfetch smartmontools nvtopPackages.full
socat sshfs sshpass
nodejs go deno
yt-dlp ffmpeg imagemagick ghostscript
unrar p7zip patool pv progress
testdisk
android-tools
lutris
guake
firefox
cage
gtkgreet
(pkgs.kdePackages.bluedevil)
python312
geeqie
mpv
];
## ── Auto-upgrade? Not yet. ───────────────────────────────
system.autoUpgrade.enable = false;
## ── WireGuard VPN (wg0: 10.10.10.0/24, wg1: 10.10.20.0/24) ─────
networking.wireguard.interfaces = {
wg0 = {
ips = [ "10.10.10.1/24" ];
listenPort = 51820;
privateKeyFile = "/etc/nixos/secrets/wireguard/wg0-key.key";
# IP forwarding + NAT for wg0 clients to internet/LAN
postSetup = ''
${pkgs.iptables}/bin/iptables -A FORWARD -i wg0 -j ACCEPT
${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -o eno1 -j MASQUERADE
'';
postShutdown = ''
${pkgs.iptables}/bin/iptables -D FORWARD -i wg0 -j ACCEPT
${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -o eno1 -j MASQUERADE
'';
peers = [
# Exos — routes remote subnets
{
publicKey = "8rO4j18cPAw6zv8b26PNXFHpEwxqjBq1r8yRmWNOMXY=";
endpoint = "briggs.wiki:51820";
allowedIPs = [
"10.10.10.2/32"
"10.0.0.0/24"
"192.168.3.0/24"
];
}
# Maroon / Roman Phone
{
publicKey = "Sih2qcJtmhdlSwxlaRont7ezwazo0FMyMxTjhWBtpjA=";
allowedIPs = [ "192.168.3.1/32" ];
}
# Palantir (Phone)
{
publicKey = "RUPXirLLTY1KeFIjfwKQtVg3ckj8sLz2QN/AiLZOmFw=";
allowedIPs = [ "192.168.2.1/32" ];
}
# Menegroth
{
publicKey = "DWhEqMQ7/RHkEuPtMrpuhQqnls7o/B6idSJxXJJy6ys=";
allowedIPs = [ "192.168.2.2/32" ];
}
# iPad
{
publicKey = "lqTK2qoaNJ+Md8RZT3TVxN+fAHllTwIG2znZKI3Rt2c=";
allowedIPs = [ "192.168.2.3/32" ];
}
# Testing Phone 1 (HD10)
{
publicKey = "d312Q5tIHB1RwJ9w5sxmtbWtf7cQtRfi1ej8rQwjYjw=";
allowedIPs = [ "192.168.2.4/32" ];
}
# Teresa Phone
{
publicKey = "8O6ps6A+QkD/aawtuyLkvi192fonETNzM/ZyNpA1YU0=";
allowedIPs = [ "192.168.2.5/32" ];
}
# Testing VM
{
publicKey = "r6pjYWHUm/F+NqYPcyqP/0Fx+CojZ3aTxDa2wU3QmSI=";
allowedIPs = [ "192.168.2.7/32" ];
}
# VM
{
publicKey = "wwITy0KEhVGwutJcpKB4bl32pRbIeYMdok8he3qu8w0=";
allowedIPs = [ "192.168.2.8/32" ];
}
];
};
wg1 = {
ips = [ "10.10.20.1/24" ];
listenPort = 51821;
privateKeyFile = "/etc/nixos/secrets/wireguard/wg1-key.key";
# LAN-only: only allow forwarding to 10.0.1.0/24 (local LAN)
postSetup = ''
${pkgs.iptables}/bin/iptables -A FORWARD -i wg1 -d 10.0.1.0/24 -j ACCEPT
${pkgs.iptables}/bin/iptables -A FORWARD -i wg1 -j DROP
'';
postShutdown = ''
${pkgs.iptables}/bin/iptables -D FORWARD -i wg1 -d 10.0.1.0/24 -j ACCEPT
${pkgs.iptables}/bin/iptables -D FORWARD -i wg1 -j DROP
'';
peers = [
{
publicKey = "nL0vWYTXP+/EASz/zKynY2k4X/+viKytWzeXQTgF6iA=";
allowedIPs = [ "10.10.20.2/32" ];
}
];
};
};
# Open WireGuard ports in the firewall
networking.firewall = {
allowedUDPPorts = [ 51820 51821 5113 ];
allowedUDPPortRanges = [
{ from = 47984; to = 48010; }
];
allowedTCPPorts = [ 3010 222 8084 443 80 8787 8180 7777 3180 47989 47990 48010 5113 ];
# Enable IP forwarding (needed for WireGuard routing)
enable = true;
};
# Enable IP forwarding at kernel level
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
## ── keyd keyboard remapping ──────────────────────────────
services.keyd = {
enable = true;
keyboards = {
default = {
ids = [ "*" ];
settings = {
main = {
esc = "capslock";
capslock = "overload(nav, esc)";
};
nav = {
w = "up";
a = "left";
s = "down";
d = "right";
b = "C-left";
u = "pageup";
x = "delete";
g = "home";
n = "end";
h = "left";
j = "down";
k = "up";
l = "right";
space = "capslock";
};
};
};
q = {
ids = [ "*" ];
settings = {
main = {
esc = "capslock";
capslock = "overload(nav, esc)";
};
nav = {
w = "up";
a = "left";
s = "down";
d = "right";
h = "left";
j = "down";
k = "up";
l = "right";
space = "capslock";
};
};
};
};
};
## ── Gitea ─────────────────────────────────────────────────
services.gitea = {
enable = true;
stateDir = "/storage/20TB/Computer-Related/Gitea";
repositoryRoot = "/storage/20TB/Computer-Related/Gitea/repositories";
database.type = "sqlite3";
settings = {
server = {
DOMAIN = "10.0.1.49";
ROOT_URL = "http://10.0.1.49:3010";
HTTP_PORT = 3010;
SSH_PORT = 222;
DISABLE_SSH = false;
LFS_START_SERVER = false;
};
service = {
DISABLE_REGISTRATION = false;
REQUIRE_SIGNIN_VIEW = false;
};
security.INSTALL_LOCK = true;
};
};
## ── Prowlarr (port 9696) ────────────────────────────────────
services.prowlarr = {
enable = true;
dataDir = "/storage/20TB/Computer-Related/Prowlarr-data";
openFirewall = true;
};
## ── Kiwix ZIM server (port 8098) ──────────────────────────
services.kiwix-serve = {
enable = true;
port = 8098;
openFirewall = true;
library = {
# Update paths when ZIM files are added/removed
wikipedia = "/storage/20TB/Media/Kiwix/wikipedia_en_all_maxi_2026-02.zim";
archlinux = "/storage/20TB/Media/Kiwix/archlinux_en_all_maxi_2025-08.zim";
ifixit = "/storage/20TB/Media/Kiwix/ifixit_en_all_2025-06.zim";
khanacademy = "/storage/20TB/Media/Kiwix/khanacademy_en_all_2023-03.zim";
survivorlibrary = "/storage/20TB/Media/Kiwix/survivorlibrary.com_en_all_2025-06.zim";
urbanprepper = "/storage/20TB/Media/Kiwix/urban-prepper_en_all_2025-06.zim";
anonymousplanet = "/storage/20TB/Media/Kiwix/anonymousplanet.org_en_all_2025-08.zim";
basedcooking = "/storage/20TB/Media/Kiwix/based.cooking_en_all_2025-08.zim";
};
extraArgs = [ "--skipInvalid" ];
};
## ── Bitmagnet (torrent indexer, port 3333/3334) ────────────
services.bitmagnet = {
enable = true;
openFirewall = true;
settings = {
http_server.local_address = ":3333";
dht_server.port = 3334;
postgres = {
name = "bitmagnet";
user = "bitmagnet";
password = "";
};
};
};
services.postgresql = {
dataDir = "/storage/20TB/Computer-Related/Bitmagnet-data";
package = pkgs.postgresql_16;
};
## ── Sonarr (port 8989) ──────────────────────────────────
services.sonarr = {
enable = true;
dataDir = "/storage/20TB/Computer-Related/Sonarr-data";
openFirewall = true;
};
## ── Radarr (port 7878) ──────────────────────────────────
services.radarr = {
enable = true;
dataDir = "/storage/20TB/Computer-Related/Radarr-data";
openFirewall = true;
};
## ── Jellyfin (port 8096) ─────────────────────────────────
services.jellyfin = {
enable = true;
dataDir = "/storage/20TB/Computer-Related/Jellyfin-data";
cacheDir = "/storage/20TB/Computer-Related/Jellyfin-data/cache";
openFirewall = true;
};
## ── PIA VPN + qBittorrent + SOCKS5 (Docker compose) ────
systemd.services.pia-qbit = {
description = "PIA VPN + qBittorrent + SOCKS5 proxy stack";
after = [ "docker.service" "network-online.target" ];
wants = [ "docker.service" "network-online.target" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.docker ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
WorkingDirectory = "/opt/pia-qbit";
ExecStart = "${pkgs.docker}/bin/docker compose up -d";
ExecStop = "${pkgs.docker}/bin/docker compose down";
# Give VPN time to establish before declaring "done"
ExecStartPost = "${pkgs.coreutils}/bin/sleep 10";
};
};
# ## ── Immich (photo management, port 2283) ──────────────────
# services.immich = {
# enable = true;
# host = "10.10.10.1";
# mediaLocation = "/storage/8TB/Documents/immich";
# port = 2283;
# openFirewall = true;
# database.enable = true;
# redis.enable = true;
# };
## ── Firecrawl (web scraping, port 3002) ──────────────────
systemd.services.firecrawl = {
description = "Firecrawl web scraping stack";
after = [ "docker.service" "network-online.target" ];
wants = [ "docker.service" "network-online.target" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.docker ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
WorkingDirectory = "/opt/firecrawl";
ExecStart = "${pkgs.docker}/bin/docker compose up -d";
ExecStop = "${pkgs.docker}/bin/docker compose down";
};
};
## ── Hermes WebUI (port 8787) ────────────────────────────
systemd.services.hermes-webui = {
description = "Hermes Web UI";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
environment = {
HERMES_WEBUI_HOST = "0.0.0.0";
HERMES_WEBUI_PORT = "8787";
HERMES_HOME = "/storage/20TB/Agents/.hermes";
HERMES_WEBUI_STATE_DIR = "/storage/20TB/Agents/.hermes/webui";
};
serviceConfig = {
Type = "simple";
User = "harley";
WorkingDirectory = "/opt/hermes-webui";
ExecStart = "/opt/hermes-webui/venv/bin/python server.py";
Restart = "on-failure";
RestartSec = 5;
};
};
## ── OpenSpeedTest (natively via nginx + PHP, port 8084) ──
services.phpfpm.pools.openspeedtest = {
user = "nginx";
group = "nginx";
settings = {
pm = "dynamic";
"pm.max_children" = 5;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 1;
"pm.max_spare_servers" = 3;
"listen" = "/run/phpfpm-openspeedtest.sock";
};
};
## ── Nginx reverse proxy ─────────────────────────────────
services.nginx = {
enable = true;
recommendedProxySettings = true;
virtualHosts = {
"jellyfin.wingard.pro" = {
enableACME = false;
addSSL = true;
sslCertificate = "/etc/letsencrypt/live/jellyfin.wingard.pro/fullchain.pem";
sslCertificateKey = "/etc/letsencrypt/live/jellyfin.wingard.pro/privkey.pem";
locations."/" = {
proxyPass = "http://127.0.0.1:8096";
proxyWebsockets = true;
};
};
"speedtest.wingard.pro" = {
enableACME = false;
addSSL = true;
sslCertificate = "/etc/letsencrypt/live/speedtest.wingard.pro/fullchain.pem";
sslCertificateKey = "/etc/letsencrypt/live/speedtest.wingard.pro/privkey.pem";
root = "/opt/openspeedtest";
locations."/" = {
index = "index.html";
tryFiles = "$uri $uri/ /index.html";
};
locations."~ \.php$" = {
extraConfig = ''
fastcgi_pass unix:/run/phpfpm-openspeedtest.sock;
fastcgi_index index.php;
include ${pkgs.nginx}/conf/fastcgi.conf;
fastcgi_param SCRIPT_FILENAME /opt/openspeedtest$fastcgi_script_name;
'';
};
};
"speedtest-http" = {
listen = [ { addr = "0.0.0.0"; port = 8084; } ];
root = "/opt/openspeedtest";
locations."/" = {
index = "index.html";
tryFiles = "$uri $uri/ /index.html";
};
locations."~ \.php$" = {
extraConfig = ''
fastcgi_pass unix:/run/phpfpm-openspeedtest.sock;
fastcgi_index index.php;
include ${pkgs.nginx}/conf/fastcgi.conf;
fastcgi_param SCRIPT_FILENAME /opt/openspeedtest$fastcgi_script_name;
'';
};
};
};
};
## ── SearXNG meta search (port 8888) ──────────────────────
services.searx = {
enable = true;
openFirewall = true;
redisCreateLocally = true;
settings = {
general.instance_name = "Utumno Search";
search = {
safe_search = 0;
formats = [ "html" "json" ];
autocomplete = "";
};
server = {
port = 8888;
bind_address = "0.0.0.0";
secret_key = "@SEARXNG_SECRET@";
limiter = false;
method = "POST";
image_proxy = false;
};
ui = {
default_theme = "simple";
theme_args.simple_style = "dark";
query_in_title = false;
};
outgoing = {
request_timeout = 5.0;
max_request_timeout = 15.0;
pool_connections = 100;
pool_maxsize = 20;
enable_http2 = true;
};
};
environmentFile = "/opt/searxng/secrets.env";
};
## ── SABnzbd (port 8180) ─────────────────────────────────
services.sabnzbd = {
enable = true;
openFirewall = true;
settings = {
host = "0.0.0.0";
port = 8180;
misc = {
host = "0.0.0.0";
port = 8180;
enable_https = false;
html_login = true;
inet_exposure = 0;
auto_browser = false;
config_lock = false;
check_new_rel = false;
cache_limit = "";
};
};
};
## ── Roku Web Remote (port 7777) ──────────────────────────
systemd.services.roku-remote = {
description = "Roku TV Web Remote";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
User = "harley";
WorkingDirectory = "/opt/roku-remote";
ExecStart = "${pkgs.python3}/bin/python server.py";
Restart = "on-failure";
RestartSec = 5;
};
};
## ── Sunshine (Moonlight game streaming host) ────────────
hardware.uinput.enable = true;
systemd.services.sunshine = {
description = "Sunshine - game stream host for Moonlight";
after = [ "display-manager.service" ];
wants = [ "display-manager.service" ];
wantedBy = [ "multi-user.target" ];
environment = {
XDG_RUNTIME_DIR = "/run/user/1000";
WAYLAND_DISPLAY = "wayland-0";
};
serviceConfig = {
Type = "simple";
User = "harley";
Group = "users";
ExecStartPre = "${pkgs.coreutils}/bin/sleep 5";
ExecStart = "${pkgs.sunshine}/bin/sunshine";
Restart = "on-failure";
RestartSec = 5;
AmbientCapabilities = [ "CAP_SYS_ADMIN" ];
CapabilityBoundingSet = [ "CAP_SYS_ADMIN" ];
};
};
## ── Pi-hole DNS sinkhole ──────────────────────────────────
services.pihole-ftl = {
enable = true;
openFirewallDNS = true;
# Upstream DNS servers (Cloudflare + Quad9)
settings = {
dns = {
upstreams = [
"1.1.1.1#53"
"1.0.0.1#53"
"9.9.9.9#53"
];
# Listen on all interfaces (wg0, wg1, LAN)
listeningMode = "ALL";
# Block ESNI, iCloud Private Relay, Firefox canary, etc.
blocking.active = true;
blocking.mode = "NULL";
# Rate limiting
rateLimit.count = 1000;
rateLimit.interval = 60;
# Cache
cache.size = 10000;
cache.optimizer = 3600;
# DNSSEC
dnssec = false;
bogusPriv = true;
domainNeeded = false;
};
# Web server on port 8999 (like old Docker setup), admin at /admin/
webserver = {
port = lib.mkForce "8999";
interface.boxed = true;
interface.theme = "default-dark";
};
# Local DNS records (LAN hostnames pointing to Utumno)
dns.hosts = [
"10.0.1.49 jellyfin.wingard.pro"
"10.0.1.49 emu.wingard.pro"
"10.0.1.49 axo.wingard.pro"
"10.0.1.49 speedtest.wingard.pro"
];
# Privacy level 0 = full stats
misc.privacylevel = 0;
};
# Blocklist: StevenBlack's unified hosts
lists = [
{
url = "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts";
description = "Steven Black's unified adlist";
}
];
};
# Pi-hole web admin dashboard
services.pihole-web.enable = true;
## ── Docker ────────────────────────────────────────────────
virtualisation.docker = {
enable = true;
# Allow non-root users (harley is in docker group)
enableOnBoot = true;
# Use the overlay2 storage driver (default on modern kernels)
storageDriver = "overlay2";
# Auto-prune unused data
autoPrune.enable = true;
autoPrune.dates = "weekly";
};
## ── Periodic atomic home cleaner (reseed from skeleton) ──
systemd.services.atomic-home-cleaner = {
description = "Wipe atomic home if user has no active logind sessions";
script = ''
if ! pgrep -u atomic >/dev/null 2>&1; then
umount /home/atomic/mnt 2>/dev/null || true
rm -rf /home/atomic/.* /home/atomic/* 2>/dev/null
cp -rT /etc/nixos/atomic-skel/ /home/atomic/
chown -R 1001:100 /home/atomic/
fi
'';
serviceConfig.Type = "oneshot";
};
systemd.timers.atomic-home-cleaner = {
description = "Check every 2 min if atomic user's sessions ended";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "1min";
OnUnitActiveSec = "30s";
};
};
## ── State version ────────────────────────────────────────
system.stateVersion = "26.11";
}