1069 lines
33 KiB
Plaintext
1069 lines
33 KiB
Plaintext
{ config, lib, pkgs, ... }:
|
|
|
|
{
|
|
############################################################
|
|
## Table of Contents ##
|
|
## 1) Nix Configs/Flakes ##
|
|
## 2) Boot Option ##
|
|
## 3) Filesystems ##
|
|
## 4) System Configuration ##
|
|
## 5) Networking ##
|
|
## |_5.1) Firewall ##
|
|
## |_5.2) Wireguard ##
|
|
## |_5.3) Pihole ##
|
|
## |_5.4) Nginx ##
|
|
## 6) Users and Sudo ##
|
|
## 7) General Packages ##
|
|
## 8) Services, Daemons, Scripts ##
|
|
## |_8.1) Docker & Containers ##
|
|
## |_8.2) Arrs suite ##
|
|
## 9) Customizations & variables ##
|
|
## |_9.1) Keyd keyboard settings ##
|
|
## |_9.2) Enviroment Variables ##
|
|
## ##
|
|
## ##
|
|
############################################################
|
|
|
|
############################################################
|
|
## ##
|
|
## 1: Inital Nix configuration ##
|
|
## ##
|
|
############################################################
|
|
imports = [ ./hardware-configuration.nix ];
|
|
|
|
## Flakes and nix-command
|
|
nix.settings = {
|
|
experimental-features = [ "nix-command" "flakes" ];
|
|
trusted-users = [ "harley" ];
|
|
};
|
|
|
|
## Allow unfree packages
|
|
nixpkgs.config.allowUnfree = true;
|
|
|
|
## 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; };
|
|
});
|
|
})
|
|
## Overlay: add mnemosyne memory packages and inject into hermes-agent
|
|
(final: prev:
|
|
let
|
|
inherit (final) fetchurl;
|
|
py = final.python312Packages;
|
|
# Build mnemosyne-memory
|
|
mnemosyne-memory = py.buildPythonPackage rec {
|
|
pname = "mnemosyne-memory";
|
|
version = "3.14.0";
|
|
format = "pyproject";
|
|
src = fetchurl {
|
|
url = "https://files.pythonhosted.org/packages/c1/b1/a3b8a18828aadd4fc7e67fb262294ea0038dbf130c8aac23196e998542d7/mnemosyne_memory-3.14.0.tar.gz";
|
|
hash = "sha256-EQbl7GmsIkncre0bepSNj1zux5WaMXbMbvvdD6QSdus=";
|
|
};
|
|
propagatedBuildInputs = [ py.fastembed py.sqlite-vec ];
|
|
nativeBuildInputs = [ py.setuptools py.wheel ];
|
|
doCheck = false;
|
|
pythonImportsCheck = [ "mnemosyne" ];
|
|
meta.description = "Zero-dependency AI memory. SQLite-backed. Sub-millisecond.";
|
|
meta.homepage = "https://github.com/mnemosyne-oss/mnemosyne";
|
|
meta.license = lib.licenses.mit;
|
|
};
|
|
# Build mnemosyne-hermes
|
|
mnemosyne-hermes = py.buildPythonPackage rec {
|
|
pname = "mnemosyne-hermes";
|
|
version = "0.4.0";
|
|
format = "pyproject";
|
|
src = fetchurl {
|
|
url = "https://files.pythonhosted.org/packages/45/da/77d0f0cb636b896f1c23449541a0d6a9e3a5301a6dee7a2064082a4d8583/mnemosyne_hermes-0.4.0.tar.gz";
|
|
hash = "sha256-fkh+cNVXIJXOQDxf8ZQxQSBgp+9WyH/ERW2kmbBHTrg=";
|
|
};
|
|
propagatedBuildInputs = [ mnemosyne-memory ];
|
|
nativeBuildInputs = [ py.setuptools py.wheel ];
|
|
doCheck = false;
|
|
meta.description = "Mnemosyne memory provider for Hermes Agent";
|
|
meta.homepage = "https://github.com/mnemosyne-oss/mnemosyne";
|
|
meta.license = lib.licenses.mit;
|
|
};
|
|
mnemosyneSitePkgs = "${mnemosyne-memory}/${py.python.sitePackages}:${mnemosyne-hermes}/${py.python.sitePackages}";
|
|
hermesWithPath = prev.hermes-agent.override {
|
|
extraPythonPackages = [ ];
|
|
};
|
|
# Wrap again to add mnemosyne site-packages to PYTHONPATH
|
|
hermes-wrapped = final.stdenv.mkDerivation {
|
|
pname = "hermes-agent-mnemosyne";
|
|
version = hermesWithPath.version;
|
|
nativeBuildInputs = [ final.makeWrapper ];
|
|
dontUnpack = true;
|
|
dontBuild = true;
|
|
installPhase = ''
|
|
mkdir -p $out/bin
|
|
for bin in hermes hermes-agent hermes-acp; do
|
|
if [ -f ${hermesWithPath}/bin/$bin ]; then
|
|
makeWrapper ${hermesWithPath}/bin/$bin $out/bin/$bin --suffix PYTHONPATH : "${mnemosyneSitePkgs}"
|
|
fi
|
|
done
|
|
'';
|
|
meta = hermesWithPath.meta;
|
|
};
|
|
in {
|
|
inherit mnemosyne-memory mnemosyne-hermes;
|
|
hermes-agent = hermes-wrapped;
|
|
}
|
|
)
|
|
];
|
|
|
|
## Auto-upgrade
|
|
system.autoUpgrade.enable = false;
|
|
############################################################
|
|
## ##
|
|
## 2: Boot and Kernel ##
|
|
## ##
|
|
############################################################
|
|
|
|
## btrfs support in the kernel and systemd boot stuff
|
|
boot.loader.efi.canTouchEfiVariables = true;
|
|
boot.loader.systemd-boot.enable = true;
|
|
boot.initrd.supportedFilesystems = [ "btrfs"];
|
|
|
|
## SSH and remote LUKS unlocking in initramfs
|
|
|
|
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"
|
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOnvzomqQoTWI7H2GpnqZO8tR7CwwwfUsylGu9IHWpAe u0_a392@localhost"
|
|
];
|
|
|
|
## Initrd convenience: 'unlock' command
|
|
boot.initrd.systemd.contents."/root/unlock".text = ''
|
|
#!/bin/sh
|
|
exec cryptsetup luksOpen /dev/disk/by-uuid/7d3b9cf3-0444-4cc9-80ba-e1401b783b6c nixroot
|
|
'';
|
|
boot.initrd.systemd.contents."/root/.profile".text = ''
|
|
export PATH=$PATH:/root
|
|
'';
|
|
## DHCP for initrd network (systemd stage 1)
|
|
boot.initrd.systemd.network.enable = true;
|
|
boot.initrd.systemd.network.networks."10-eno1" = {
|
|
matchConfig.Name = "eno1";
|
|
networkConfig.DHCP = "ipv4";
|
|
};
|
|
|
|
###########################################################
|
|
## ##
|
|
## 3: File systems ##
|
|
## ##
|
|
###########################################################
|
|
|
|
## Root, 512-GB, SSD-SATA, btrfs
|
|
|
|
fileSystems."/".options = [ "compress=zstd" "noatime" "space_cache=v2" ];
|
|
|
|
## 20TB Storage drive, HDD, btrfs
|
|
|
|
#fileSystems."/storage/20TB" = {
|
|
# device = "/dev/disk/by-uuid/ec172aa7-85f7-4375-a661-88fc66221620";
|
|
# fsType = "ext4";
|
|
# };
|
|
|
|
## Post-boot unlock + mount 8TB LUKS data drive
|
|
systemd.services.unlock-8tb = {
|
|
description = "Unlock and mount 8TB storage";
|
|
after = [ "local-fs.target" ];
|
|
wants = [ "local-fs.target" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
serviceConfig.Type = "oneshot";
|
|
serviceConfig.RemainAfterExit = true;
|
|
script = ''
|
|
/run/current-system/sw/bin/cryptsetup luksOpen \
|
|
--key-file /root/keyfiles/8tb.key \
|
|
/dev/disk/by-uuid/2bc4df99-677b-46d4-a1b8-38dd3c636a2b 8tb
|
|
/run/current-system/sw/bin/mount /dev/mapper/8tb /storage/8TB
|
|
/run/current-system/sw/bin/mount /storage/8TB/arrs-tmp/sabnzbd
|
|
'';
|
|
};
|
|
## 1TB storage drive, SSD-NVME, btrfs
|
|
#fileSystems."/storage/ssd/" = {
|
|
#device = " ";
|
|
#fstype = "btrfs";
|
|
#};
|
|
|
|
## automated Snapper snapshots for the root filesystem
|
|
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 for recov
|
|
services.snapper.configs."file7" = {
|
|
SUBVOLUME = "/mnt/.recov";
|
|
ALLOW_USERS = [ "harley" ];
|
|
TIMELINE_CREATE = false;
|
|
NUMBER_CLEANUP = false;
|
|
};
|
|
############################################################
|
|
## ##
|
|
## 4: System Configuration ##
|
|
## ##
|
|
############################################################
|
|
|
|
## Time/Locale
|
|
time.timeZone = "America/New_York";
|
|
i18n.defaultLocale = "en_US.UTF-8";
|
|
|
|
## Nvidia GPU
|
|
services.xserver.videoDrivers = [ "nvidia" ];
|
|
hardware.graphics.enable = true;
|
|
hardware.nvidia = {
|
|
modesetting.enable = true;
|
|
powerManagement.enable = true;
|
|
open = false;
|
|
nvidiaSettings = true;
|
|
};
|
|
|
|
## Greeter user for greetd login on tty6 LOOK INTO THIS, IDK WHAT IT IS
|
|
users.users.greeter = {
|
|
isSystemUser = true;
|
|
group = "nogroup";
|
|
extraGroups = [ "video" "input" "seat" ];
|
|
description = "greetd login greeter";
|
|
};
|
|
|
|
## greetd PAM service
|
|
security.pam.services.greetd = {};
|
|
|
|
## Display Manager (SDDM) and Plasma 6
|
|
services.xserver.enable = true;
|
|
services.xserver.displayManager.sddm.enable = true;
|
|
services.desktopManager.plasma6.enable = true;
|
|
programs.kdeconnect.enable = true;
|
|
|
|
## Pipewire
|
|
services.pipewire = {
|
|
enable = true;
|
|
pulse.enable = true;
|
|
alsa.enable = true;
|
|
alsa.support32Bit = true;
|
|
};
|
|
|
|
|
|
# Xpadneo for xbox controller bluetooth
|
|
hardware.xpadneo.enable = true;
|
|
|
|
|
|
|
|
############################################################
|
|
## ##
|
|
## 5) Networking ##
|
|
## ##
|
|
############################################################
|
|
|
|
networking.hostName = "utumno";
|
|
networking.networkmanager.enable = true;
|
|
# Don't block boot waiting for all network profiles to come online
|
|
systemd.services.NetworkManager-wait-online.enable = lib.mkForce false;
|
|
|
|
hardware.bluetooth = {
|
|
enable = true;
|
|
powerOnBoot = true;
|
|
settings.General.Experimental = true;
|
|
};
|
|
|
|
|
|
##
|
|
##
|
|
## 5.1 Firewall
|
|
##
|
|
##
|
|
networking.firewall = {
|
|
|
|
## UDP ports and ranges
|
|
allowedUDPPorts = [ 51820 51812 5113 ];
|
|
allowedUDPPortRanges = [
|
|
{ from = 47884; to = 48010; }
|
|
];
|
|
|
|
## TCP ports and ranges
|
|
allowedTCPPorts = [ 22 3010 222 8084 443 80 8787 9119 8180 7777 3180 47989 47990 47991 47992 48010 5113 8777 ];
|
|
# Enable IP forwarding (needed for Wireguard routing)
|
|
enable = true;
|
|
};
|
|
|
|
## Enable Ip forwarding at the kernel level
|
|
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
|
|
|
|
## SSHD
|
|
services.openssh = {
|
|
enable = true;
|
|
settings = {
|
|
PasswordAuthentication = false;
|
|
PermitRootLogin = "prohibit-password";
|
|
};
|
|
};
|
|
|
|
|
|
##
|
|
##
|
|
## 5.2: Wireguard
|
|
##
|
|
##
|
|
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" ];
|
|
}
|
|
];
|
|
};
|
|
|
|
};
|
|
##
|
|
##
|
|
## 5.3) Pi-hole DNS
|
|
##
|
|
##
|
|
services.pihole-ftl = {
|
|
enable = true;
|
|
openFirewallDNS = true;
|
|
## Upstream DNS servers (Cloudflare and Quad9)
|
|
settings = {
|
|
dns = {
|
|
upstreams = [
|
|
"1.1.1.1#53"
|
|
"1.0.0.1#53"
|
|
"9.9.9.9#53"
|
|
];
|
|
## Listen on all interfaces
|
|
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;
|
|
## Local DNS records
|
|
hosts = [
|
|
"10.0.1.49 jellyfin.wingard.pro"
|
|
"10.0.1.49 speedtest.wingard.pro"
|
|
];
|
|
};
|
|
## Web server on 8999, admin page at /admin/
|
|
webserver = {
|
|
port = lib.mkForce "8999";
|
|
interface.boxed = "true";
|
|
interface.theme = "default-dark";
|
|
};
|
|
## Privacy level 0 = full stats
|
|
misc.privacylevel = 0;
|
|
};
|
|
## Blocklist: Steven Black'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;
|
|
##
|
|
##
|
|
## 5.4: Nginx
|
|
##
|
|
##
|
|
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;
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
};
|
|
############################################################
|
|
## ##
|
|
## 6: Users ##
|
|
## ##
|
|
############################################################
|
|
|
|
## setting up sudo wheel group
|
|
security.sudo.extraRules = [
|
|
{ groups = [ "wheel" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; }
|
|
];
|
|
|
|
## Harley
|
|
users.users.harley = {
|
|
isNormalUser = true;
|
|
shell = lib.mkForce pkgs.zsh;
|
|
extraGroups = [ "wheel" "networkmanager" "docker" "input" "uinput" "video"];
|
|
openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH4OAJ8iC7CQIcedVbU86xgoL4YkMWAG9bU6aj9v4Skb harley@utumno" ];
|
|
};
|
|
|
|
## Roman
|
|
users.users.roman = {
|
|
isNormalUser = true;
|
|
description = "Roman";
|
|
home = "/home/roman/";
|
|
createHome = true;
|
|
shell = pkgs.bash;
|
|
extraGroups = [ "wheel" "users" ];
|
|
openssh.authorizedKeys.keys = [ " " ];
|
|
};
|
|
## The Ephemeral user and filesystem
|
|
|
|
## The actual Ephemeral user
|
|
users.users.atomic = {
|
|
isNormalUser = true;
|
|
description = "Ephemeral User - home directory inside a tmpfs";
|
|
home = "/home/atomic";
|
|
group = "users";
|
|
createHome = false;
|
|
extraGroups = [ "wheel" ];
|
|
shell = lib.mkForce pkgs.zsh;
|
|
openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH4OAJ8iC7CQIcedVbU86xgoL4YkMWAG9bU6aj9v4Skb harley@utumno" ];
|
|
};
|
|
|
|
## filesystem for the Ephemeral user
|
|
fileSystems."/home/atomic" = {
|
|
device = "tmpfs";
|
|
fsType = "tmpfs";
|
|
options = [ "mode=0700" "uid=1001" "gid=100" "noatime" ];
|
|
};
|
|
|
|
## Populate the Ephemeral user 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 /home/.atomic-persist /home/atomic
|
|
chown -R 1001:100 /home/atomic
|
|
'';
|
|
serviceConfig.Type = "oneshot";
|
|
serviceConfig.RemainAfterExit = true;
|
|
};
|
|
|
|
## Periodic Ephemeral 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 /home/.atomic-persist/ /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";
|
|
};
|
|
};
|
|
|
|
## One-shot service to unmount and re-populate atomic's tmpfs home
|
|
systemd.services.atomic-fresh = {
|
|
description = "Wipe and re-populate atomic's tmpfs home";
|
|
after = [ "home-atomic.mount" ];
|
|
script = ''
|
|
loginctl terminate-user atomic 2>/dev/null || true
|
|
sleep 1
|
|
rm -rf /home/atomic/.??* /home/atomic/** 2>/dev/null || true
|
|
umount /home/atomic 2>/dev/null || true
|
|
mount /home/atomic
|
|
cp -rT /home/.atomic-persist/ /home/atomic/
|
|
chown -R 1001:100 /home/atomic/
|
|
'';
|
|
serviceConfig.Type = "oneshot";
|
|
path = [ pkgs.util-linux ];
|
|
serviceConfig.User = "root";
|
|
};
|
|
############################################################
|
|
## ##
|
|
## 7: General Packages ##
|
|
## ##
|
|
############################################################
|
|
|
|
environment.systemPackages = with pkgs; [
|
|
|
|
## Cli tools
|
|
vim wget curl progress git btop neovim tmux lsof bat ncdu nvtopPackages.full fastfetch
|
|
unzip unrar p7zip xclip
|
|
testdisk btdu
|
|
|
|
## Games
|
|
lutris rpcs3 mednafen mednafen-server mednaffe dolphin-emu
|
|
|
|
## System
|
|
adwaita-icon-theme tela-circle-icon-theme
|
|
sshfs sshpass pciutils usbutils python312 (pkgs.kdePackages.bluedevil) gtkgreet
|
|
cage guake kitty android-tools patool pv ghostscript imagemagick nodejs go deno socat
|
|
fzf smartmontools shadow ripgrep fd jq tree qdirstat uv
|
|
|
|
firefox
|
|
obsidian
|
|
|
|
## Multimedia
|
|
mpv geeqie ffmpeg yt-dlp
|
|
## Virtual display for Sunshine headless streaming (krfb-virtualmonitor)
|
|
kdePackages.krfb
|
|
];
|
|
############################################################
|
|
## ##
|
|
## 8) Services ##
|
|
## ##
|
|
############################################################
|
|
|
|
## 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/8TB/Backups/Agents/.hermes";
|
|
# HERMES_WEBUI_STATE_DIR = "/storage/8TB/Backups/Agents/.hermes/webui";
|
|
# SEARXNG_URL = "http://10.0.1.49:8888";
|
|
# };
|
|
# serviceConfig = {
|
|
# Type = "simple";
|
|
# User = "harley";
|
|
# WorkingDirectory = "/opt/hermes-webui";
|
|
# ExecStart = "/opt/hermes-webui/venv/bin/python server.py";
|
|
# Restart = "on-failure";
|
|
# RestartSec = 5;
|
|
# };
|
|
# };
|
|
|
|
## Hermes Dashboard (Port 9119)
|
|
systemd.services.hermes-dashboard = {
|
|
description = "Hermes Dashboard";
|
|
after = [ "network-online.target" "wireguard-wg0.service" ];
|
|
wants = [ "network-online.target" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
environment = {
|
|
HERMES_HOME = "/storage/8TB/Backups/Agents/.hermes";
|
|
};
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = "harley";
|
|
Group = "users";
|
|
WorkingDirectory = "/storage/8TB/Backups/Agents/tux";
|
|
ExecStart = "${pkgs.hermes-agent}/bin/hermes -p tux dashboard --host 10.10.10.1 --port 9119 --no-open";
|
|
Restart = "on-failure";
|
|
RestartSec = 5;
|
|
};
|
|
};
|
|
|
|
## SearXNG meta search engine (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";
|
|
};
|
|
|
|
## Gitea (Port 3010)
|
|
services.gitea = {
|
|
enable = true;
|
|
stateDir = "/storage/8TB/Backups/Gitea";
|
|
repositoryRoot = "/storage/8TB/Backups/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;
|
|
};
|
|
};
|
|
|
|
## 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" ];
|
|
};
|
|
|
|
## OpenSpeedTest (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";
|
|
};
|
|
};
|
|
|
|
## Immich (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;
|
|
#};
|
|
|
|
## Roku Remote (Port 7777)
|
|
## btop-web system monitor (Port 8777)
|
|
systemd.services.btop-web = {
|
|
description = "btop-style web system monitor";
|
|
after = [ "network-online.target" ];
|
|
wants = [ "network-online.target" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = "harley";
|
|
WorkingDirectory = "/opt/btop-web";
|
|
ExecStart = "${pkgs.python3}/bin/python server.py";
|
|
Restart = "always";
|
|
RestartSec = 5;
|
|
};
|
|
};
|
|
|
|
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
|
|
services.sunshine = {
|
|
enable = true;
|
|
autoStart = true;
|
|
capSysAdmin = true; # only needed for Wayland -- omit this when using with Xorg
|
|
openFirewall = true;
|
|
};
|
|
## uninput enable for Sunshine.
|
|
hardware.uinput.enable = true;
|
|
##
|
|
##
|
|
## 8.1: 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";
|
|
};
|
|
## Firecrawl web-scraper (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";
|
|
};
|
|
};
|
|
|
|
########## ##########
|
|
########## ##########
|
|
########## 8.2: ARRS ##########
|
|
########## ##########
|
|
########## ##########
|
|
|
|
## PIA VPN + qBittorrent (Port 8088) + SOCKS5 (Port 1080) (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";
|
|
};
|
|
};
|
|
|
|
## Bitmagnet (Port 3333)
|
|
#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;
|
|
#};
|
|
|
|
## Jellyfin (Port 8096)
|
|
services.jellyfin = {
|
|
enable = true;
|
|
dataDir = "/storage/8TB/arrs-tmp/jellyfin";
|
|
cacheDir = "/storage/8TB/arrs-tmp/jellyfin-cache";
|
|
openFirewall = true;
|
|
};
|
|
## Prowlarr (port 9696)
|
|
services.prowlarr = {
|
|
enable = true;
|
|
dataDir = "/storage/8TB/arrs-tmp/prowlarr";
|
|
openFirewall = true;
|
|
};
|
|
## Sonarr (port 8989)
|
|
services.sonarr = {
|
|
enable = true;
|
|
dataDir = "/storage/8TB/arrs-tmp/sonarr";
|
|
openFirewall = true;
|
|
};
|
|
|
|
## Radarr (port 7878)
|
|
services.radarr = {
|
|
enable = true;
|
|
dataDir = "/storage/8TB/arrs-tmp/radarr";
|
|
openFirewall = true;
|
|
};
|
|
## SABnzbd (port 8180)
|
|
fileSystems."/var/lib/sabnzbd" = {
|
|
device = "/storage/8TB/arrs-tmp/sabnzbd";
|
|
options = [ "bind" "noauto" ];
|
|
fsType = "none";
|
|
};
|
|
|
|
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 = "";
|
|
};
|
|
};
|
|
};
|
|
############################################################
|
|
## ##
|
|
## 9: Customization ##
|
|
## ##
|
|
############################################################
|
|
|
|
##
|
|
##
|
|
## 9.1: Keyd keyboard remappings
|
|
##
|
|
##
|
|
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";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
##
|
|
##
|
|
## 9.2 Enviroment Variables
|
|
##
|
|
##
|
|
environment.variables = {
|
|
GTK_THEME = "Adwaita:dark";
|
|
GTK_ICON_THEME = "Tela-circle-dark";
|
|
HERMES_HOME = "/storage/8TB/Backups/Agents/.hermes";
|
|
};
|
|
|
|
## 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";
|
|
};
|
|
# Make theme packages available to all users
|
|
environment.sessionVariables = {
|
|
XDG_DATA_DIRS = [
|
|
"${pkgs.tela-circle-icon-theme}/share/icons"
|
|
"${pkgs.catppuccin}/share/plasma"
|
|
];
|
|
};
|
|
############################################################
|
|
## ##
|
|
## ##
|
|
## ##
|
|
############################################################
|
|
## ── State version ────────────────────────────────────────
|
|
system.stateVersion = "26.11";
|
|
} |