{ lib, pkgs, outputs, config, modulesPath, ... }: { imports = [ # inputs.nixos-hardware.nixosModules.raspberry-pi-4 (modulesPath + "/installer/sd-card/sd-image-aarch64.nix") outputs.nixosModules.vpn-ip ./hardware-configuration.nix # Import shared settings ]; hardware.enableRedistributableFirmware = true; powerManagement.cpuFreqGovernor = "ondemand"; hardware.graphics.enable = true; hardware.deviceTree = let /* Pin the raspberrypifw package to an old version. - later versions bricked the devicetree so that HDMI wouldn't work anymore - due to some changes around HDMI and audio for the rpi5? - see: https://github.com/pftf/RPi4/issues/252 - the uefi firmware rolled back to a version of the raspberrypi/firmware repo before those changes: https://github.com/pftf/RPi4/commit/6ba22a07bf19422c199dd801d3442319c04f5090 - they pinned it to b49983637106e5fb33e2ae60d8c15a53187541e4, so I'm doing the same on the system level */ raspberrypifw = pkgs.raspberrypifw.overrideAttrs { version = "pinned-2025.03.27"; src = pkgs.fetchFromGitHub { owner = "raspberrypi"; repo = "firmware"; rev = "f3465ff27e8795b9db6e088c383ec79464aa985e"; hash = ""; }; }; in { enable = true; # use the devicetree files from the official raspberrypi linux tree dtbSource = pkgs.device-tree_rpi.override {inherit raspberrypifw;}; filter = "bcm2711-rpi-4-b.dtb"; # only apply overlays on the one devicetree I actually need name = "broadcom/bcm2711-rpi-4-b.dtb"; # use and load the correct rpi4b devicetree /* Devicetree overlays applied to the main devicetree. - these are applied during build, i.e. these are *not* passed separately to the bootloader - they're compiled in beforehand, the resulting single dtb file is then given to the bootloader - overlays are applied in the order they're in the list - the upstream overlays by raspberrypi are in the raspberrypifw package, see `upstreamOverlay` - these are documented in their repo: https://github.com/raspberrypi/linux/blob/rpi-6.6.y/arch/arm/boot/dts/overlays/README See: - https://docs.zephyrproject.org/latest/build/dts/intro-syntax-structure.html - https://bootlin.com/blog/enabling-new-hardware-on-raspberry-pi-with-device-tree-overlays/ - https://elinux.org/Device_Tree_Source_Undocumented (for `/delete-property/`) */ overlays = let upstreamOverlay = name: raspberrypifw + /share/raspberrypi/boot/overlays/${name}.dtbo; in [ /* Fixes a bug I experienced with the mainline kernel where only one CPU core would work. - I wrote this overlay to apply the changes from this patch I found online: https://github.com/AntonioND/rpi3-arm-tf-bootstrap/blob/master/0001-rpi3-Enable-PSCI-support.patch - there'd be "failed to come online" messages in `dmesg` - I found the patch here: https://github.com/OP-TEE/build/issues/360 */ { name = "custom-enable-method"; dtsText = '' /dts-v1/; /plugin/; / { compatible = "brcm,bcm2711"; fragment@0 { target = <&cpus>; __overlay__ { /delete-property/ enable-method; }; }; fragment@1 { target-path = "/"; __overlay__ { psci { compatible = "arm,psci-1.0", "arm,psci-0.2"; method = "smc"; }; }; }; fragment@2 { target = <&cpu0>; __overlay__ { enable-method = "psci"; /delete-property/ cpu-release-addr; }; }; fragment@3 { target = <&cpu1>; __overlay__ { enable-method = "psci"; /delete-property/ cpu-release-addr; }; }; fragment@4 { target = <&cpu2>; __overlay__ { enable-method = "psci"; /delete-property/ cpu-release-addr; }; }; fragment@5 { target = <&cpu3>; __overlay__ { enable-method = "psci"; /delete-property/ cpu-release-addr; }; }; }; ''; } /* This overlay comprises the vc4-kms-v3d-pi4 and dwc2 overlays for graphics and USB. - note that nixos-hardware uses the vc4-fkms-v3d-pi4 overlay instead - fkms (fake/firmware kernel mode setting) uses a feature in the firmware blob, proper kms implements mode setting itself - https://forums.raspberrypi.com/viewtopic.php?t=255478 - the kms driver is newer, fkms is deprecated by upstream now - fkms didn't work for me anyway - kms does, but only with the pinned devicetree repo (see above) */ { name = "upstream-pi4"; dtboFile = upstreamOverlay "upstream-pi4"; } ]; }; nixpkgs.overlays = [ (final: super: { makeModulesClosure = x: super.makeModulesClosure (x // {allowMissing = true;}); }) ]; programs = { # Allow executing of anything on the system with a , eg: , python executes python from the nix store even if not in $PATH currently command-not-found.enable = lib.mkForce false; nix-index.enable = true; nix-index-database.comma.enable = true; }; services = { automatic-timezoned.enable = true; # stubby = { # enable = true; # settings = # pkgs.stubby.passthru.settingsExample # // { # upstream_recursive_servers = [ # { # address_data = "94.140.14.49"; # tls_auth_name = "4b921896.d.adguard-dns.com"; # tls_pubkey_pinset = [ # { # digest = "sha256"; # value = "19HOzAWb2bgl7bo/b4Soag+5luf7bo6vlDN8W812k4U="; # } # ]; # } # { # address_data = "94.140.14.59"; # tls_auth_name = "4b921896.d.adguard-dns.com"; # tls_pubkey_pinset = [ # { # digest = "sha256"; # value = "19HOzAWb2bgl7bo/b4Soag+5luf7bo6vlDN8W812k4U="; # } # ]; # } # { # address_data = "2a10:50c0:0:0:0:0:ded:ff"; # tls_auth_name = "4b921896.d.adguard-dns.com"; # tls_pubkey_pinset = [ # { # digest = "sha256"; # value = "19HOzAWb2bgl7bo/b4Soag+5luf7bo6vlDN8W812k4U="; # } # ]; # } # { # address_data = "2a10:50c0:0:0:0:0:dad:ff"; # tls_auth_name = "4b921896.d.adguard-dns.com"; # tls_pubkey_pinset = [ # { # digest = "sha256"; # value = "19HOzAWb2bgl7bo/b4Soag+5luf7bo6vlDN8W812k4U="; # } # ]; # } # ]; # }; # }; openssh = { enable = true; # require public key authentication for better security settings.PasswordAuthentication = false; settings.KbdInteractiveAuthentication = false; settings.PermitRootLogin = "no"; }; davfs2.enable = true; aria2 = { enable = true; settings = { dir = "/var/lib/media"; rpc-listen-port = 6969; }; rpcSecretFile = config.sops.secrets."rpcSecret".path; }; dnsmasq = { enable = true; settings = { interface = "wg1"; }; }; }; sops = { # users.users = { # ombi.extraGroups = ["radarr" "sonarr" "aria2"]; # }; # services.ombi = { # enable = true; # port = 2368; # }; # users.users = { # radarr.extraGroups = ["aria2"]; # sonarr.extraGroups = ["aria2"]; # }; # services = { # #uses port 7878 # radarr.enable = true; # #uses port 8989 # sonarr.enable = true; # prowlarr.enable = true; # }; secrets."webdav-secret" = { mode = "0600"; path = "/etc/davfs2/secrets"; owner = config.users.users.root.name; }; secrets."rpcSecret".mode = "0440"; secrets."rpcSecret".owner = config.users.users.aria2.name; secrets."protonvpn-priv-key".mode = "0440"; secrets."protonvpn-priv-key".owner = config.users.users.root.name; }; boot = { kernelPackages = lib.mkForce pkgs.linuxPackages_latest; initrd.kernelModules = ["vc4" "bcm2835_dma" "i2c_bcm2835" "cma=256M" "console=tty0" "reset-raspberrypi"]; kernelParams = ["video=HDMI-A-1:1920x1080@60D"]; kernel.sysctl = { "net.ipv4.ip_forward" = 1; "net.ipv6.conf.all.forwarding" = 1; }; }; sdImage.compressImage = false; services.vpn-ip = { enable = false; }; networking = { hostName = "wheatley"; networkmanager.enable = true; # Disable NetworkManager's internal DNS resolution networkmanager.dns = "none"; # These options are unnecessary when managing DNS ourselves useDHCP = false; dhcpcd.enable = false; # Configure DNS servers manually (this example uses Cloudflare and Google DNS) # IPv6 DNS servers can be used here as well. nameservers = [ # "127.0.0.1" # "::1" "94.140.14.49" "94.140.14.59" "2a10:50c0:0:0:0:0:ded:ff" "2a10:50c0:0:0:0:0:ded:ff" ]; wireguard.enable = true; wg-quick.interfaces = { # # "wg0" is the network interface name. You can name the interface arbitrarily. # wg0 = { # autostart = true; # # Determines the IP address and subnet of the server's end of the tunnel interface. # address = ["10.2.0.2/32"]; # # The port that WireGuard listens to. Must be accessible by the client. # listenPort = 51820; # dns = ["10.2.0.1"]; # # Path to the private key file. # # # # Note: The private key can also be included inline via the privateKey option, # # but this makes the private key world-readable; thus, using privateKeyFile is # # recommended. # privateKeyFile = config.sops.secrets."protonvpn-priv-key".path; # peers = [ # # List of allowed peers. # { # # Feel free to give a meaning full name # # Public key of the peer (not a file path). # publicKey = "/i7jCNpcqVBUkY07gVlILN4nFdvZHmxvreAOgLGoZGg="; # # List of IPs assigned to this peer within the tunnel subnet. Used to configure routing. # allowedIPs = ["0.0.0.0/0"]; # endpoint = "146.70.86.114:51820"; # } # ]; # }; # wg public key for host: A02sO7uLdgflhPIRd0cbJONIaPP4z8HTxDkmX4NegFg= # TODO: generate this dynamically based on other hosts wg1 = { # Determines the IP address and subnet of the server's end of the tunnel interface. address = ["10.0.0.1/24" "fdc9:281f:04d7:9ee9::1/64"]; # The port that WireGuard listens to. Must be accessible by the client. listenPort = 51821; # This allows the wireguard server to route your traffic to the internet and hence be like a VPN postUp = '' ${pkgs.iptables}/bin/iptables -A FORWARD -i wg0 -j ACCEPT ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.0.0.1/24 -o eth0 -j MASQUERADE ${pkgs.iptables}/bin/ip6tables -A FORWARD -i wg0 -j ACCEPT ${pkgs.iptables}/bin/ip6tables -t nat -A POSTROUTING -s fdc9:281f:04d7:9ee9::1/64 -o eth0 -j MASQUERADE ''; # Undo the above preDown = '' ${pkgs.iptables}/bin/iptables -D FORWARD -i wg0 -j ACCEPT ${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s 10.0.0.1/24 -o eth0 -j MASQUERADE ${pkgs.iptables}/bin/ip6tables -D FORWARD -i wg0 -j ACCEPT ${pkgs.iptables}/bin/ip6tables -t nat -D POSTROUTING -s fdc9:281f:04d7:9ee9::1/64 -o eth0 -j MASQUERADE ''; privateKeyFile = lib.mkForce config.sops.secrets."wg-private-key".path; peers = [ { #GLaDOS public key publicKey = "yieF2yQptaE3jStoaGytUnN+HLxyVhFBZIUOGUNAV38="; allowedIPs = ["10.0.0.2/32" "fdc9:281f:04d7:9ee9::2/128"]; } { #EDI public key publicKey = "i4nDZbU+a2k5C20tFJRNPVE1vhYKJwhoqGHEdeC4704="; allowedIPs = ["10.0.0.3/32" "fdc9:281f:04d7:9ee9::3/128"]; } { #Shodan public key publicKey = "Zah2nZDaHF8jpP5AtMA5bhE7t38fMB2UHzbXAc96/jw="; allowedIPs = ["10.0.0.4/32" "fdc9:281f:04d7:9ee9::3/128"]; } { #ADA public key publicKey = "SHu7xxRVWuqp4U4uipMoITKrFPWZATGsJevUeqBSzWo="; allowedIPs = ["10.0.0.5/32" "fdc9:281f:04d7:9ee9::3/128"]; } #Queen public key: FVTrYM7S2Ev2rGrYrHsG2et1/SU3UjEBQH2AOen4+04= ]; }; }; nat = { # enable NAT enable = true; externalInterface = "end0"; internalInterfaces = ["wg1"]; }; firewall = { enable = true; allowPing = false; allowedTCPPorts = [ 22 # SSH 5349 # STUN tls 5350 # STUN tls alt 80 # http 443 # https 51821 # wg 7878 53 # dnsmasq ]; allowedUDPPorts = [ 53 #dnsmasq ]; allowedUDPPortRanges = [ { from = 51820; to = 51822; # wg } { from = 49152; to = 49999; } # TURN relay ]; }; }; systemd.mounts = [ { enable = true; description = "Webdav mount point"; after = ["network-online.target"]; wants = ["network-online.target"]; what = "https://nextcloud.gladtherescake.eu/remote.php/dav/files/GLaDTheresCake"; where = "/home/kodi/nextcloud"; options = "uid=1002,gid=100,file_mode=0664,dir_mode=2775"; type = "davfs"; } ]; environment.systemPackages = [ (pkgs.kodi.withPackages (kodiPkgs: with kodiPkgs; [ steam-controller invidious youtube netflix upnext sponsorblock sendtokodi jellyfin inputstream-adaptive inputstreamhelper inputstream-ffmpegdirect upnext sponsorblock sendtokodi routing requests-cache requests plugin-cache a4ksubtitles ])) pkgs.iptables ]; users.extraUsers.kodi.isNormalUser = true; services.cage.user = "kodi"; services.cage.program = "${pkgs.kodi-wayland}/bin/kodi-standalone"; services.cage.enable = true; system.stateVersion = "25.05"; nixpkgs.hostPlatform = lib.mkForce "aarch64-linux"; }