fix: remove duplicate roku-remote block, fix modesetting.enabled->enable
This commit is contained in:
+628
-622
File diff suppressed because it is too large
Load Diff
@@ -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";
|
||||
}
|
||||
Reference in New Issue
Block a user