{ description = "Unified NixOS flake for mreow (laptop), yarn (desktop), muffin (server)"; inputs = { # Two channels: unstable for desktops, 25.11 for server. nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.11"; home-manager = { url = "github:nix-community/home-manager"; inputs.nixpkgs.follows = "nixpkgs"; }; home-manager-stable = { url = "github:nix-community/home-manager/release-25.11"; inputs.nixpkgs.follows = "nixpkgs-stable"; }; # Shared across all hosts lanzaboote = { url = "github:nix-community/lanzaboote"; inputs.nixpkgs.follows = "nixpkgs"; inputs.rust-overlay.follows = "rust-overlay"; }; nixos-hardware.url = "github:NixOS/nixos-hardware/master"; disko = { url = "github:nix-community/disko/latest"; inputs.nixpkgs.follows = "nixpkgs"; }; impermanence = { url = "github:nix-community/impermanence"; inputs.nixpkgs.follows = "nixpkgs"; inputs.home-manager.follows = "home-manager"; }; # Desktop (mreow + yarn) rust-overlay = { url = "github:oxalica/rust-overlay"; inputs.nixpkgs.follows = "nixpkgs"; }; zen-browser = { url = "github:0xc000022070/zen-browser-flake"; inputs.nixpkgs.follows = "nixpkgs"; inputs.home-manager.follows = "home-manager"; }; firefox-addons = { url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons"; inputs.nixpkgs.follows = "nixpkgs"; }; niri = { url = "github:sodiboo/niri-flake"; inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs-stable.follows = "nixpkgs"; }; emacs-overlay = { url = "github:nix-community/emacs-overlay"; inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs-stable.follows = "nixpkgs"; }; nix-flatpak.url = "github:gmodena/nix-flatpak/"; nix-doom-emacs-unstraightened = { url = "github:marienz/nix-doom-emacs-unstraightened"; inputs.nixpkgs.follows = "nixpkgs"; inputs.emacs-overlay.follows = "emacs-overlay"; }; jovian-nixos = { url = "github:Jovian-Experiments/Jovian-NixOS"; inputs.nixpkgs.follows = "nixpkgs"; }; noctalia = { url = "github:noctalia-dev/noctalia-shell"; inputs.nixpkgs.follows = "nixpkgs"; }; nix-cachyos-kernel = { url = "github:xddxdd/nix-cachyos-kernel/release"; inputs.nixpkgs.follows = "nixpkgs"; }; llm-agents = { url = "github:numtide/llm-agents.nix"; inputs.nixpkgs.follows = "nixpkgs"; }; json2steamshortcut = { url = "github:ChrisOboe/json2steamshortcut"; inputs.nixpkgs.follows = "nixpkgs"; }; # Server (muffin) — follows nixpkgs-stable nix-minecraft = { url = "github:Infinidoge/nix-minecraft"; inputs.nixpkgs.follows = "nixpkgs-stable"; }; vpn-confinement.url = "github:Maroka-chan/VPN-Confinement"; llamacpp = { url = "github:TheTom/llama-cpp-turboquant/feature/turboquant-kv-cache"; inputs.nixpkgs.follows = "nixpkgs-stable"; }; srvos = { url = "github:nix-community/srvos"; inputs.nixpkgs.follows = "nixpkgs-stable"; }; deploy-rs = { url = "github:serokell/deploy-rs"; inputs.nixpkgs.follows = "nixpkgs-stable"; }; agenix = { url = "github:ryantm/agenix"; inputs.nixpkgs.follows = "nixpkgs-stable"; inputs.home-manager.follows = "home-manager-stable"; inputs.darwin.follows = ""; }; senior_project-website = { url = "github:Titaniumtown/senior-project-website"; flake = false; }; website = { url = "git+https://git.sigkill.computer/titaniumtown/website"; flake = false; }; trackerlist = { url = "github:ngosang/trackerslist"; flake = false; }; ytbn-graphing-software = { url = "git+https://git.sigkill.computer/titaniumtown/YTBN-Graphing-Software"; }; arr-init = { url = "git+ssh://gitea@git.gardling.com/titaniumtown/arr-init"; inputs.nixpkgs.follows = "nixpkgs-stable"; }; nixpkgs-p2pool-module = { url = "github:JacoMalan1/nixpkgs/create-p2pool-service"; flake = false; }; qbittorrent-metrics-exporter = { url = "git+https://codeberg.org/anriha/qbittorrent-metrics-exporter"; inputs.nixpkgs.follows = "nixpkgs-stable"; }; }; outputs = inputs@{ self, nixpkgs, nixpkgs-stable, home-manager, home-manager-stable, lanzaboote, nixos-hardware, disko, impermanence, nix-minecraft, vpn-confinement, srvos, deploy-rs, agenix, arr-init, nixpkgs-p2pool-module, jovian-nixos, ... }: let username = "primary"; system = "x86_64-linux"; niriPackage = inputs.niri.packages.${system}.niri-unstable; # --- Desktop-channel pkgs (used by portable homeConfigurations) --- desktopPkgs = import nixpkgs { inherit system; }; # --- Server (muffin) plumbing --- bootstrapPkgs = import nixpkgs-stable { inherit system; }; patchedStableSrc = bootstrapPkgs.applyPatches { name = "nixpkgs-stable-patched"; src = nixpkgs-stable; patches = [ ./patches/nixpkgs/0001-firefox-syncserver-add-postgresql-backend-support.patch ]; }; serverPkgs = import patchedStableSrc { inherit system; targetPlatform = system; buildPlatform = builtins.currentSystem; }; siteConfig = import ./site-config.nix; serviceConfigs = import ./hosts/muffin/service-configs.nix { site_config = siteConfig; }; serverLib = import ./lib { inherit inputs; lib = nixpkgs-stable.lib; pkgs = serverPkgs; service_configs = serviceConfigs; site_config = siteConfig; }; testSuite = import ./tests/tests.nix { pkgs = serverPkgs; lib = serverLib; inherit inputs; site_config = siteConfig; config = self.nixosConfigurations.muffin.config; }; # --- Host builders --- # Desktop: unstable + home-manager-unstable + niri-unstable mkDesktopHost = hostname: nixpkgs.lib.nixosSystem { specialArgs = { inherit inputs username hostname; niri-package = niriPackage; site_config = siteConfig; }; modules = [ home-manager.nixosModules.home-manager ( { config, ... }: { home-manager.useUserPackages = true; home-manager.sharedModules = [ inputs.zen-browser.homeModules.twilight ]; home-manager.extraSpecialArgs = { inherit inputs hostname username ; niri-package = niriPackage; homeDirectory = "/home/${username}"; stateVersion = config.system.stateVersion; site_config = siteConfig; }; home-manager.users.${username} = import ./hosts/${hostname}/home.nix; } ) ./hosts/${hostname}/default.nix ]; }; # Server: stable + home-manager-stable + srvos + agenix + patched pkgs muffinHost = serverLib.nixosSystem { inherit system; specialArgs = { inherit username inputs ; hostname = "muffin"; eth_interface = "enp4s0"; service_configs = serviceConfigs; site_config = siteConfig; lib = serverLib; }; modules = [ # SAFETY! port sanity checks ( { config, lib, ... }: let publicPorts = lib.attrValues serviceConfigs.ports.public; privatePorts = lib.attrValues serviceConfigs.ports.private; allPortNumbers = map (p: p.port) (publicPorts ++ privatePorts); uniquePortNumbers = lib.unique allPortNumbers; publicTcp = map (p: p.port) (lib.filter (p: p.proto == "tcp" || p.proto == "both") publicPorts); publicUdp = map (p: p.port) (lib.filter (p: p.proto == "udp" || p.proto == "both") publicPorts); privatePortNumbers = map (p: p.port) privatePorts; fwTcp = config.networking.firewall.allowedTCPPorts; fwUdp = config.networking.firewall.allowedUDPPorts; missingTcp = lib.filter (p: !(builtins.elem p fwTcp)) publicTcp; missingUdp = lib.filter (p: !(builtins.elem p fwUdp)) publicUdp; leakedTcp = lib.filter (p: builtins.elem p fwTcp) privatePortNumbers; leakedUdp = lib.filter (p: builtins.elem p fwUdp) privatePortNumbers; in { config.assertions = [ { assertion = (lib.length allPortNumbers) == (lib.length uniquePortNumbers); message = "Duplicate port numbers detected in ports.public / ports.private"; } { assertion = missingTcp == [ ]; message = "Public ports missing from allowedTCPPorts: ${builtins.toString missingTcp}"; } { assertion = missingUdp == [ ]; message = "Public ports missing from allowedUDPPorts: ${builtins.toString missingUdp}"; } { assertion = leakedTcp == [ ] && leakedUdp == [ ]; message = "Private ports leaked into firewall allow-lists — TCP: ${builtins.toString leakedTcp}, UDP: ${builtins.toString leakedUdp}"; } ]; } ) srvos.nixosModules.server srvos.nixosModules.mixins-terminfo ./hosts/muffin/disk.nix ./hosts/muffin/default.nix # Firefox-syncserver: swap upstream module + package for patched versions. { disabledModules = [ "services/networking/firefox-syncserver.nix" ]; imports = [ "${patchedStableSrc}/nixos/modules/services/networking/firefox-syncserver.nix" ]; nixpkgs.overlays = [ nix-minecraft.overlay (import ./lib/overlays.nix) (_final: prev: { syncstorage-rs = prev.callPackage "${patchedStableSrc}/pkgs/by-name/sy/syncstorage-rs/package.nix" { }; }) # NOTE: systemd patch is applied via `systemd.package` in the module # list below, not via an overlay. An overlay replaces pkgs.systemd # for every consumer, which cascades through udev-check-hook and # causes the entire closure (fish, e2fsprogs, valkey, …) to rebuild # and re-run flaky test suites in the sandbox. `systemd.package` # only injects the patched systemd into the runtime init chain. ]; nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (nixpkgs-stable.lib.getName pkg) [ "minecraft-server" ]; } # Runtime-only systemd patch: reset FreezerState on inactive/failed # transitions so a SIGKILL to a frozen unit doesn't strand # FreezerState=frozen (unrecoverable without a reboot, upstream issue # #38517). PR #38528 closed only the watchdog path; this closes # systemctl kill / OOM / segfault paths too. # # Applied via systemd.package, not via overlay, so pkgs.systemd stays # untouched for every other consumer — no udev-check-hook cascade, # no fish/e2fsprogs/valkey rebuild, no flaky-test fallout. ( { pkgs, ... }: { systemd.package = pkgs.systemd.overrideAttrs (old: { patches = (old.patches or [ ]) ++ [ ./patches/systemd/0001-core-unit-reset-freezer-state-on-inactive-failed.patch ]; }); } ) lanzaboote.nixosModules.lanzaboote arr-init.nixosModules.default (import "${nixpkgs-p2pool-module}/nixos/modules/services/networking/p2pool.nix") home-manager-stable.nixosModules.home-manager ( { ... }: { home-manager.extraSpecialArgs = { site_config = siteConfig; }; home-manager.users.${username} = import ./hosts/muffin/home.nix; } ) ] ++ (with nixos-hardware.nixosModules; [ common-cpu-amd-pstate common-cpu-amd-zenpower common-pc-ssd common-gpu-intel ]); }; in { formatter.${system} = nixpkgs.legacyPackages.${system}.nixfmt-tree; nixosConfigurations = { mreow = mkDesktopHost "mreow"; yarn = mkDesktopHost "yarn"; muffin = muffinHost; }; # Standalone home-manager profile — usable on any x86_64-linux machine # with nix installed (NixOS or not). Activate with: # nix run home-manager/master -- switch --flake ".#primary" # Ships the shared terminal profile (fish, helix, modern CLI, git). homeConfigurations.primary = home-manager.lib.homeManagerConfiguration { pkgs = desktopPkgs; extraSpecialArgs = { site_config = siteConfig; }; modules = [ ./home/profiles/terminal.nix { home = { username = username; homeDirectory = "/home/${username}"; stateVersion = "24.11"; }; } ]; }; deploy.nodes.muffin = { hostname = siteConfig.hosts.muffin.alias; profiles.system = { sshUser = "root"; user = "root"; # Deploy guard enforcement lives in the preflight driver (deploy.sh # and .gitea/workflows/deploy.yml) — not in activation. Activation- # time enforcement is unsafe: deploy-rs sets the new profile pointer # before running deploy-rs-activate, so a non-zero activation exit # triggers auto-rollback which re-runs switch-to-configuration on the # previous generation. That re-activation rotates agenix secrets, # reinstalls lanzaboote, and reloads systemd units — side effects we # want to avoid when the deploy is supposed to be a no-op blocked by # the guard. Blocking before the deploy-rs invocation is the only # clean way to leave the running system untouched. # # Activation uses `switch-to-configuration boot` + a detached finalize # (modules/server-deploy-finalize.nix) rather than the default # `switch`. The gitea-actions runner driving CI deploys lives on # muffin itself; a direct `switch` restarts gitea-runner-muffin mid- # activation, killing the SSH session, the CI job, and deploy-rs's # magic-rollback handshake. `boot` only touches the bootloader — no # service restarts — and deploy-finalize schedules a pid1-owned # transient unit that runs the real `switch` (or `systemctl reboot` # when kernel/initrd/kernel-modules changed) ~60s later, surviving # runner restart because it's decoupled from the SSH session. path = deploy-rs.lib.${system}.activate.custom self.nixosConfigurations.muffin.config.system.build.toplevel '' # matches activate.nixos's workaround for NixOS/nixpkgs#73404 cd /tmp $PROFILE/bin/switch-to-configuration boot ${nixpkgs-stable.lib.getExe self.nixosConfigurations.muffin.config.system.build.deployFinalize} ''; }; }; checks.${system} = testSuite; packages.${system} = { tests = serverPkgs.linkFarm "all-tests" ( serverPkgs.lib.mapAttrsToList (name: test: { name = name; path = test; }) testSuite ); # Buildenv of every binary in the portable terminal profile. Install # without home-manager via: # nix profile install ".#cli-tools" cli-tools = self.homeConfigurations.primary.config.home.path; } // (serverPkgs.lib.mapAttrs' (name: test: { name = "test-${name}"; value = test; }) testSuite); lib = serverLib; }; }