#+title: NixOS Configuration #+AUTHOR: Preston Pan #+DESCRIPTION: My NixOS system, written entirely in a literate configuration * Introduction This is my NixOS configuration. It is a part of my monorepo, and this file automatically tangles to all the under the nix/ directory in my monorepo [[https://git.nullring.xyz/monorepo.git][git repository]]. My monorepo also stores my website, as my website stores my [[file:elfeed.org][elfeed]] and [[file:emacs.org][emacs]] configurations. Additionally, I want to track my emacs configuration with my Nix configuration. Having them in one repository means that my emacs configuration is pinned to my flake. Hence, my monorepo serves a dual purpose, as do many of the files within my monorepo. They are often data files used in my configuration (i.e. emacs, elfeed, org-roam, agenda, journal, etc...) and they are webpages as well. This page is one such example of this concept. * Configurables We start with some configurable variables (you can change these if you want to use this configuration yourself): #+begin_src nix :tangle ../nix/flakevars.nix let # I'm ret2pop! What's your name? internetName = "ret2pop"; in { # Name of spontaneity box remoteHost = "${internetName}.net"; # Your internet name internetName = internetName; # Name of your organization orgHost = "nullring.xyz"; # Hostnames of my systems hostnames = [ "affinity" "continuity" "spontaneity" "installer" ]; } #+end_src * Flake.nix The flake is the entry point of the NixOS configuration. Here, I have a list of all the systems that I use with all the modules that they use. My NixOS configuration is heavily modularized, so that adding new configurations that add modifications is made simple. Additionally I have implemented integration tests for my systems in an automated way (it'll do integration tests for services that are enabled), with ~nix flake check~. I have implemented also git hooks which help with CI. and now for the main flake: #+begin_src nix :tangle ../nix/flake.nix { description = "Emacs centric configurations for a complete networked system"; inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; nur.url = "github:nix-community/NUR"; sops-nix.url = "github:Mic92/sops-nix"; scripts.url = "github:ret2pop/scripts"; wallpapers.url = "github:ret2pop/wallpapers"; sounds.url = "github:ret2pop/sounds"; deep-research.url = "github:ret2pop/ollama-deep-researcher"; impermanence.url = "github:nix-community/impermanence"; nix-topology = { url = "github:oddlama/nix-topology"; inputs.nixpkgs.follows = "nixpkgs"; }; home-manager = { url = "github:nix-community/home-manager/release-25.05"; inputs.nixpkgs.follows = "nixpkgs"; }; disko = { url = "github:nix-community/disko"; inputs.nixpkgs.follows = "nixpkgs"; }; lanzaboote = { url = "github:nix-community/lanzaboote/v0.4.1"; inputs.nixpkgs.follows = "nixpkgs"; }; nixos-dns = { url = "github:Janik-Haag/nixos-dns"; inputs.nixpkgs.follows = "nixpkgs"; }; git-hooks = { url = "github:cachix/git-hooks.nix"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { self, nixpkgs, home-manager, nur, disko, lanzaboote, sops-nix, nix-topology, nixos-dns, deep-research, impermanence, git-hooks, ... } @attrs: let vars = import ./flakevars.nix; system = "x86_64-linux"; pkgs = import nixpkgs { inherit system; }; generate = nixos-dns.utils.generate nixpkgs.legacyPackages."${system}"; dnsConfig = { inherit (self) nixosConfigurations; extraConfig = import ./dns/default.nix; }; rpiCheck = hostname: (builtins.match "rpi-.*" hostname) != null; noRpi = builtins.filter (hostname: (! rpiCheck hostname)); noInstaller = builtins.filter (hostname: (hostname != "installer")); filterHosts = noInstaller (noRpi vars.hostnames); mkHostModules = hostname: if (hostname == "installer") then ([ (./. + "/systems/${hostname}/default.nix") { networking.hostName = "${hostname}"; } nix-topology.nixosModules.default ]) else (if (rpiCheck hostname) then [ (./. + "/systems/${hostname}/default.nix") disko.nixosModules.disko home-manager.nixosModules.home-manager sops-nix.nixosModules.sops lanzaboote.nixosModules.lanzaboote ] else [ { environment.systemPackages = with nixpkgs.lib; [ deep-research.packages."${system}".deep-research ]; } impermanence.nixosModules.impermanence nix-topology.nixosModules.default lanzaboote.nixosModules.lanzaboote disko.nixosModules.disko home-manager.nixosModules.home-manager sops-nix.nixosModules.sops nixos-dns.nixosModules.dns { nixpkgs.overlays = [ nur.overlays.default ]; home-manager.extraSpecialArgs = attrs // { systemHostName = "${hostname}"; }; networking.hostName = "${hostname}"; } (./. + "/systems/${hostname}/default.nix") ]); # function that generates all systems from hostnames mkConfigs = map (hostname: let hostSystem = if (rpiCheck hostname) then "aarch64-linux" else system; in { name = "${hostname}"; value = nixpkgs.lib.nixosSystem { system = hostSystem; specialArgs = attrs // { isIntegrationTest = false; monorepoSelf = null; }; modules = mkHostModules hostname; }; }); mkDiskoFiles = map (hostname: { name = "${hostname}"; value = self.nixosConfigurations."${hostname}".config.monorepo.vars.diskoSpec; }); mkBuildChecks = map (hostname: { name = "${hostname}-build-check"; value = { enable = true; name = "${hostname}-vm-build"; description = "Ensure ${hostname} can build"; stages = [ "pre-merge-commit" ]; entry = "${pkgs.writeShellScript "${hostname}-check" '' #!/usr/bin/env bash set -e set -o pipefail trap "echo -e '\nHook interrupted by user. Aborting merge!'; exit 1" INT TERM echo "Running Nix integration tests..." BRANCH=$(git branch --show-current) if [ "$BRANCH" != "main" ]; then exit 0 fi echo "Merge to main detected. Building VM for ${hostname}..." if nix build .#nixosConfigurations.${hostname}.config.system.build.vm --no-link; then echo "Build succeeded. Proceeding with merge." exit 0 else echo "Build failed! Aborting." exit 1 fi ''}"; pass_filenames = false; always_run = true; }; }); hostToServices = (hostname: let super = self.nixosConfigurations."${hostname}".config; in [ { serviceName = "nginx"; enabled = super.services.nginx.enable; } { serviceName = "sshd"; enabled = super.services.openssh.enable; } # { # serviceName = "conduit"; # enabled = super.services.matrix-conduit.enable; # } { serviceName = "git-daemon"; enabled = super.services.gitDaemon.enable; } { serviceName = "tor"; enabled = super.services.tor.enable; } ]); _mkServiceTestScripts = hostname: services: builtins.concatStringsSep "\n" (builtins.map (service: (if service.enabled then '' ${hostname}.succeed("systemctl is-active ${service.serviceName}") '' else "")) services); mkServiceTestScripts = hostname: _mkServiceTestScripts hostname (hostToServices hostname); mkIntegrationTests = builtins.map (hostname: let lib = nixpkgs.lib; hostPkgs = self.nixosConfigurations."${hostname}".pkgs; hardwareConfig = ./systems/${hostname}/hardware-configuration.nix; in { name = "integration-test-${hostname}"; value = hostPkgs.testers.runNixOSTest { name = "services-test-${hostname}"; nodes = { "${hostname}" = { ... }: { _module.args = attrs // { isIntegrationTest = true; }; imports = mkHostModules hostname ++ [ "${nixpkgs}/nixos/modules/misc/nixpkgs/read-only.nix" { nixpkgs.pkgs = lib.mkVMOverride hostPkgs; nixpkgs.config = lib.mkForce {}; systemd.services.systemd-networkd-wait-online.enable = lib.mkForce false; systemd.services.NetworkManager-wait-online.enable = lib.mkForce false; nixpkgs.overlays = lib.mkForce []; } ]; disabledModules = [ ./modules/nixpkgs-options.nix ] ++ lib.optional (builtins.pathExists hardwareConfig) hardwareConfig; }; }; testScript = '' ${hostname}.start() ${hostname}.wait_for_unit("default.target") ${hostname}.succeed('printf "smoke"') ${mkServiceTestScripts hostname} ''; }; } ); integrationTests = builtins.listToAttrs (mkIntegrationTests filterHosts); pre-commit-check = git-hooks.lib.${system}.run { src = ./.; hooks = builtins.listToAttrs (mkBuildChecks filterHosts) // { statix.enable = false; deadnix.enable = true; prevent-direct-main-commits = { enable = true; name = "Prevent direct commits to main"; description = "Blocks commits to main unless they are merge commits"; pass_filenames = false; entry = "${pkgs.writeShellScript "block-main-commits" '' BRANCH=$(git branch --show-current) GIT_DIR=$(git rev-parse --git-dir) if [ "$BRANCH" = "main" ] && [ ! -f "$GIT_DIR/MERGE_HEAD" ]; then echo "Direct commits to 'main' are blocked." echo "Please commit to a feature branch and merge it into main." exit 1 fi ''}"; }; }; }; in { checks."${system}" = integrationTests // { inherit pre-commit-check; }; nixosConfigurations = builtins.listToAttrs (mkConfigs vars.hostnames); evalDisko = builtins.listToAttrs (mkDiskoFiles (noInstaller vars.hostnames)); topology."${system}" = import nix-topology { pkgs = import nixpkgs { inherit system; overlays = [ nix-topology.overlays.default ]; }; modules = [ ./topology/default.nix { nixosConfigurations = self.nixosConfigurations; } ]; }; devShell."${system}" = with pkgs; mkShell { buildInputs = [ fira-code python3 poetry statix deadnix ]; shellHook = '' ${pre-commit-check.shellHook} git config branch.main.mergeoptions "--no-ff" ''; }; packages."${system}" = { zoneFiles = generate.zoneFiles dnsConfig; octodns = generate.octodnsConfig { inherit dnsConfig; config = { providers = { cloudflare = { class = "octodns_cloudflare.CloudflareProvider"; token = "env/CLOUDFLARE_TOKEN"; }; config = { check_origin = false; }; }; }; zones = { "${vars.remoteHost}." = nixos-dns.utils.octodns.generateZoneAttrs [ "cloudflare" ]; "${vars.orgHost}." = nixos-dns.utils.octodns.generateZoneAttrs [ "cloudflare" ]; }; }; }; }; } #+end_src Note that the configurations are automatically generated with he mkConfigs function, and the final disko output is automatically generated with mkDiskoFiles. * Sops Configuration In order to use the sops configuration, you must change the age public key to the one that you own: #+begin_src yaml :tangle ../nix/.sops.yaml keys: - &primary age165ul43e8rc0qwzz2f2q9cw02psm2mkudsrwavq2e0pxs280p64yqy2z0dr - &vps age1acpuyy2qnduyxzwvusd8urr6a78e3f37ylhvh2pngyqytf5r8ans5vkest creation_rules: - path_regex: secrets/secrets.yaml$ key_groups: - age: - *primary - path_regex: secrets/vps_secrets.yaml$ key_groups: - age: - *vps - path_regex: secrets/common_secrets.yaml$ key_groups: - age: - *primary - *vps #+end_src also note that you will have to write your own secrets.yaml file, with an entry called ~mail~, which is used for the imaps and smtps password. * Nix DNS #+begin_src nix :tangle ../nix/dns/default.nix { defaultTTL = 120; } #+end_src * Nix Topology Nix Topology generates a nice graph of all my hosts. You can view this graph by running ~nix build .#topology.x86_64-linux.config.output~. #+begin_src nix :tangle ../nix/topology/default.nix { config, ... }: let inherit (config.lib.topology); in { nodes = { spontaneity = { interfaces.wan.network = "remote"; }; installer = { interfaces.lan.network = "home"; }; affinity = { interfaces.lan = { network = "home"; physicalConnections = [ { node = "spontaneity"; interface = "wan"; } { node = "installer"; interface = "lan"; } ]; }; }; continuity = { interfaces.lan = { network = "home"; physicalConnections = [ { node = "spontaneity"; interface = "wan"; } { node = "affinity"; interface = "lan"; } ]; }; }; }; networks = { home = { name = "Home Network"; cidrv4 = "192.168.1.1/24"; }; remote = { name = "Remote Network"; cidrv4 = "144.202.27.169/32"; }; }; } #+end_src * Modules ** Vars Variables used for regular configuration in your system ~default.nix~ file. The options are largely self-documenting. #+begin_src nix :tangle ../nix/modules/vars.nix { lib, ... }: let vars = import ../flakevars.nix; in { options.monorepo.vars = { device = lib.mkOption { type = lib.types.str; default = "/dev/sda"; example = "/dev/nvme0n1"; description = "device that NixOS is installed to"; }; internetName = lib.mkOption { type = lib.types.str; default = "${vars.internetName}"; example = "myinternetname"; description = "Internet name to be used for internet usernames"; }; sshKey = lib.mkOption { type = lib.types.str; default = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICts6+MQiMwpA+DfFQxjIN214Jn0pCw/2BDvOzPhR/H2 preston@continuity-dell"; example = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICts6+MQiMwpA+DfFQxjIN214Jn0pCw/2BDvOzPhR/H2 preston@continuity-dell"; description = "Admin public key for managing multiple configurations"; }; dkimKey = lib.mkOption { type = lib.types.str; default = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsC9GpfjvQlldPrHAC7Yt+ZF0aduUIVV4j2+KUkF0j6NsrpOgvU6COWKQSod/B/qyPBLWf+w5P5YiJ9XnOgw6Db/I9C67eusEHnV/cbvokXLQjSBvXee1OEdrT9i+6iUgDeGWP4CrD1DcwvXzAcCI9exy3yALHVlbkyYvi0KAYofs8dVQ3JCwSCMlol71lA6ULJ2zbCIWeSOv9/C6QZ5HOIeeoFLesX6O/YvF4FYxWbSHy244TXYuczQKuayjKgD6e8gIT5WJRQj8IAWOQ2podWw6hSuB3Ig+ekoOfnl5ivJGOMbAzFTj8FtbS4ncyidLU1kIOeuLfiILeDDLlIeYTwIDAQAB"; example = "string_after_p="; description = "dkim key to put in host record for email"; }; repoName = lib.mkOption { type = lib.types.str; default = "monorepo"; example = "myreponame"; description = "Name of this repository"; }; projects = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "monorepo" "nullerbot" ]; example = [ "project1" "project2" "project3" ]; description = "Names of repos that will have mailing lists"; }; fileSystem = lib.mkOption { type = lib.types.str; default = "ext4"; example = "btrfs"; description = "filesystem to install with disko"; }; diskoSpec = lib.mkOption { type = lib.types.attrs; description = "retains a copy of the disko spec for reflection"; }; fullName = lib.mkOption { type = lib.types.str; default = "Preston Pan"; example = "John Doe"; description = "Full Name"; }; userName = lib.mkOption { type = lib.types.str; default = "preston"; example = "myUser"; description = "system username"; }; gpgKey = lib.mkOption { type = lib.types.str; default = "AEC273BF75B6F54D81343A1AC1FE6CED393AE6C1"; example = "1234567890ABCDEF..."; description = "GPG key fingerprint"; }; remoteHost = lib.mkOption { type = lib.types.str; default = "${vars.remoteHost}"; example = "example.com"; description = "Address to push to and pull from for website and git repos"; }; orgHost = lib.mkOption { type = lib.types.str; default = "${vars.orgHost}"; example = "orgname.org"; description = "Domain name of your organization, points to same VPS as remoteHost"; }; email = lib.mkOption { type = lib.types.str; default = "${vars.internetName}@${vars.orgHost}"; example = "example@example.org"; description = "Admin email address"; }; timeZone = lib.mkOption { type = lib.types.str; default = "America/Vancouver"; example = "America/Chicago"; description = "Linux timezone"; }; ntfySecret = lib.mkOption { type = lib.types.str; default = "ntfy"; example = "ntfy-env"; description = "Name of Ntfy secret for notification handling"; }; monitors = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "HDMI-A-1" "eDP-1" "DP-2" "DP-3" "DP-4" "LVDS-1" ]; example = []; description = "Monitors that waybar will use"; }; }; } #+end_src ** Default Profile Again, these are self documenting variables that you may see used below. These are to be used under ~default.nix~ in the ~systems~ folder. #+begin_src nix :tangle ../nix/modules/default.nix { lib, config, pkgs, ... }: { imports = [ ./configuration.nix ./vars.nix ]; options = { monorepo = { profiles = { cuda.enable = lib.mkEnableOption "Enables CUDA support"; documentation.enable = lib.mkEnableOption "Enables documentation on system."; secureBoot.enable = lib.mkEnableOption "Enables secure boot. See sbctl."; pipewire.enable = lib.mkEnableOption "Enables pipewire low latency audio setup"; tor.enable = lib.mkEnableOption "Enables tor along with torsocks"; server = { enable = lib.mkEnableOption "Enables server services"; interface = lib.mkOption { type = lib.types.str; default = "eth0"; }; ipv4 = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; }; ipv6 = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; }; gateway = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; }; }; ttyonly.enable = lib.mkEnableOption "TTY only, no xserver"; grub.enable = lib.mkEnableOption "Enables grub instead of systemd-boot"; workstation.enable = lib.mkEnableOption "Enables workstation services"; desktop.enable = lib.mkEnableOption "Enables everything common to desktops"; impermanence.enable = lib.mkEnableOption "Enables imperamanence"; home.enable = lib.mkEnableOption "Enables home profiles"; }; }; }; config = { environment.systemPackages = lib.mkIf config.monorepo.profiles.documentation.enable ((with pkgs; [ linux-manual man-pages man-pages-posix iproute2 silver-searcher ripgrep ]) ++ (if (config.monorepo.vars.fileSystem == "btrfs") then with pkgs; [ btrfs-progs btrfs-snap btrfs-list btrfs-heatmap ] else [])); boot.loader.grub = lib.mkIf config.monorepo.profiles.grub.enable { enable = true; }; assertions = [ { assertion = !(config.monorepo.profiles.workstation.enable && config.monorepo.profiles.server.enable); message = '' You can't enable both workstation and server profile together. Please select only one. ''; } { assertion = !(config.monorepo.profiles.desktop.enable && config.monorepo.profiles.server.enable); message = '' You can't enable both desktop and server profile together. Please select only one. ''; } ]; monorepo = { profiles = { desktop.enable = lib.mkDefault config.monorepo.profiles.workstation.enable; documentation.enable = lib.mkDefault true; pipewire.enable = lib.mkDefault true; tor.enable = lib.mkDefault true; impermanence.enable = lib.mkDefault false; server.enable = lib.mkDefault false; ttyonly.enable = lib.mkDefault config.monorepo.profiles.server.enable; home.enable = lib.mkDefault config.monorepo.profiles.desktop.enable; }; }; }; } #+end_src ** Secrets This uses sops in order to declaratively create the secrets on my system by unencrypting the yaml file specified. Yes, this is safe to include in the repo. TODO clean up secrets, move them to relevant places. #+begin_src nix :tangle ../nix/modules/secrets.nix { config, ... }: { sops = { defaultSopsFile = if config.monorepo.profiles.server.enable then ../secrets/vps_secrets.yaml else ../secrets/secrets.yaml; templates = if config.monorepo.profiles.server.enable then { "public-inbox-netrc" = { owner = "public-inbox"; group = "public-inbox"; mode = "0400"; content = (builtins.concatStringsSep "\n" (builtins.map (x: "machine mail.${config.monorepo.vars.orgHost} login ${x}@${config.monorepo.vars.orgHost} password ${config.sops.placeholder."mail_monorepo_password_pi"}") config.monorepo.vars.projects)) + '' machine mail.${config.monorepo.vars.orgHost} login discussion@${config.monorepo.vars.orgHost} password ${config.sops.placeholder."mail_monorepo_password_pi"}''; }; "matterbridge" = { owner = "matterbridge"; content = '' [irc.myirc] Server="127.0.0.1:6667" Nick="bridge" RemoteNickFormat="[{PROTOCOL}] <{NICK}> " UseTLS=false [telegram.mytelegram] Token="${config.sops.placeholder.telegram_token}" RemoteNickFormat="<({PROTOCOL}){NICK}> " MessageFormat="HTMLNick :" QuoteFormat="{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})" QuoteLengthLimit=46 IgnoreMessages="^/" [discord.mydiscord] Token="${config.sops.placeholder.discord_token}" Server="Null Identity" AutoWebHooks=true RemoteNickFormat="[{PROTOCOL}] <{NICK}> " PreserveThreading=true [[gateway]] name="gateway1" enable=true [[gateway.inout]] account="irc.myirc" channel="#nullring" [[gateway.inout]] account="discord.mydiscord" channel="ID:996282946879242262" [[gateway.inout]] account="telegram.mytelegram" channel="-5290629325" ''; }; } else {}; age = { keyFile = "/home/${config.monorepo.vars.userName}/.config/sops/age/keys.txt"; }; secrets = if config.monorepo.profiles.desktop.enable then { mail = { format = "yaml"; }; cloudflare-dns = { format = "yaml"; }; digikey = { format = "yaml"; }; dn42 = { format = "yaml"; }; } else { znc = { format = "yaml"; }; znc_password_salt = { format = "yaml"; }; znc_password_hash = { format = "yaml"; }; matrix_bridge = { format = "yaml"; }; mail_password = { format = "yaml"; owner = "maddy"; }; mail_monorepo_password_pi = { format = "yaml"; owner = "public-inbox"; }; mautrix_env = { format = "yaml"; }; telegram_token = { format = "yaml"; }; discord_token = { format = "yaml"; }; mpd_password = { format = "yaml"; owner = "nginx"; }; }; }; } #+end_src ** X11 My Xorg configuration is used as a backup for when wayland applications don't work. Note that using this configuration is extremely inefficient and my i3 configuration is unoptimized. Still, it is suitable for using Krita. #+begin_src nix :tangle ../nix/modules/xserver.nix { config, ... }: { services.xserver = { enable = (! config.monorepo.profiles.ttyonly.enable); displayManager = { startx.enable = (! config.monorepo.profiles.ttyonly.enable); }; desktopManager = { runXdgAutostartIfNone = true; }; videoDrivers = (if config.monorepo.profiles.cuda.enable then [ "nvidia" ] else []); }; } #+end_src You should add your own video drivers in a custom machine configuration. ** Containers In order to run docker/podman containers, I need this file: #+begin_src nix :tangle ../nix/modules/docker.nix { lib, ... }: { virtualisation = { oci-containers = { backend = "podman"; containers = {}; }; containers.enable = lib.mkDefault false; podman = { enable = lib.mkDefault false; dockerCompat = true; defaultNetwork.settings.dns_enabled = true; }; }; } #+end_src ** Pipewire My low latency pipewire configuration is used for music production, as well as for regular desktop usage. Pipewire is much better than pulseaudio because it supports jack with the same underlying interface and it breaks significantly less often. #+begin_src nix :tangle ../nix/modules/pipewire.nix { lib, config, ... }: { services.pipewire = { enable = lib.mkDefault config.monorepo.profiles.pipewire.enable; alsa = { enable = lib.mkDefault config.monorepo.profiles.pipewire.enable; support32Bit = true; }; pulse.enable = lib.mkDefault config.monorepo.profiles.pipewire.enable; jack.enable = lib.mkDefault config.monorepo.profiles.pipewire.enable; wireplumber.enable = lib.mkDefault config.monorepo.profiles.pipewire.enable; extraConfig = { pipewire."92-low-latency" = { "context.properties" = { "default.clock.rate" = 48000; "default.clock.quantum" = 512; "default.clock.min-quantum" = 512; "default.clock.max-quantum" = 1024; }; pipewire-pulse."92-low-latency" = { "context.properties" = [ { name = "libpipewire-module-protocol-pulse"; args = { }; } ]; "pulse.properties" = { "pulse.min.req" = "32/48000"; "pulse.default.req" = "32/48000"; "pulse.max.req" = "32/48000"; "pulse.min.quantum" = "32/48000"; "pulse.max.quantum" = "32/48000"; }; "stream.properties" = { "node.latency" = "32/48000"; "resample.quality" = 1; }; }; }; }; }; } #+end_src ** SSH My SSH daemon configuration. #+begin_src nix :tangle ../nix/modules/ssh.nix { config, lib, ... }: { services.openssh = { enable = true; settings = { PasswordAuthentication = false; AllowUsers = [ config.monorepo.vars.userName "git" ]; PermitRootLogin = "no"; KbdInteractiveAuthentication = false; }; }; networking.firewall.allowedTCPPorts = lib.mkIf config.services.openssh.enable [ 22 ]; } #+end_src ** Tor This is my tor configuration, used for my cryptocurrency wallets and whatever else I want it to do. #+begin_src nix :tangle ../nix/modules/tor.nix { config, lib, ... }: { services.tor = { enable = lib.mkDefault config.monorepo.profiles.tor.enable; openFirewall = true; client = { enable = lib.mkDefault config.monorepo.profiles.tor.enable; socksListenAddress = { IsolateDestAddr = true; addr = "127.0.0.1"; port = 9050; }; dns.enable = true; }; torsocks = { enable = lib.mkDefault config.monorepo.profiles.tor.enable; server = "127.0.0.1:9050"; }; }; } #+end_src ** Kubo IPFS I use IPFS for my website and also for my ISOs for truly declarative and deterministic configuration. NixOS might be moving to IPFS for binary cache distribution and package distribution soon, and I'm waiting on that. #+begin_src nix :tangle ../nix/modules/kubo.nix { config, lib, ... }: { services.kubo = { enable = lib.mkDefault config.monorepo.profiles.workstation.enable; autoMount = false; enableGC = true; settings = { Addresses.API = [ "/ip4/127.0.0.1/tcp/5001" ]; Bootstrap = [ "/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu" "/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm" ]; Datastore = { StorageMax = "20GB"; }; }; }; } #+end_src ** TODO i2pd I use i2p for some p2p connections. We enable it with the server profile: #+begin_src nix :tangle ../nix/modules/i2pd.nix { lib, ... }: { services.i2pd = { enable = lib.mkDefault false; address = "0.0.0.0"; inTunnels = { }; outTunnels = { }; }; } #+end_src ** TODO Icecast This is an internet radio which will host a ton of music. #+begin_src nix :tangle ../nix/modules/icecast.nix { lib, ... }: { services.icecast = { enable = lib.mkDefault false; listen.address = "0.0.0.0"; extraConfig = '' 0 /stream.m3u 3600
''; }; admin.password = "changeme"; } #+end_src ** IRC A great protocol. It's the most widely usable by any netizen, as it is just pure plaintext and the operating costs are trivial. *** NgIRCD I run my own IRC server to bridge with my Matrix server and my discord guild. #+begin_src nix :tangle ../nix/modules/ngircd.nix { lib, config, ... }: { services.ngircd = { enable = lib.mkDefault config.monorepo.profiles.server.enable; config = '' [Global] Name = ${config.monorepo.vars.orgHost} Info = NullRing IRC Instance Listen = ::,0.0.0.0 MotdFile = /etc/motd.txt Network = NullRing Ports = 6667 [Options] PAM = no [SSL] CertFile = /var/lib/acme/${config.monorepo.vars.orgHost}/fullchain.pem CipherList = HIGH:!aNULL:@STRENGTH:!SSLv3 KeyFile = /var/lib/acme/${config.monorepo.vars.orgHost}/key.pem Ports = 6697 ''; }; environment.etc."motd.txt" = { source = ../data/motd.txt; mode = "644"; user = "ngircd"; group = "ngircd"; }; networking.firewall.allowedTCPPorts = if (config.services.ngircd.enable == true) then [ 6697 6667 ] else []; } #+end_src *** MOTD I also have a MOTD file that I want to add, which displays when users connect to the server: #+begin_src fundamental :tangle ../nix/data/motd.txt Welcome to the NullRing experience! The main channel is #nullring; we're glad to have you! Rules: 1. Don't be annoying. 2. No illegal content. And if you're here to have constructive, philisophical and theoretical conversations, this is the place for you! #+end_src *** ZNC I want to be able to create some sort of identity persistence on IRC for users: #+begin_src nix :tangle ../nix/modules/znc.nix { lib, config, ... }: { services.znc = { enable = lib.mkDefault config.monorepo.profiles.server.enable; openFirewall = true; confOptions = { useSSL = true; passBlock = '' Method = sha256 Hash = d4abdd69aa24de69693885c5bd83a4a0e9ee989e1a69a905041b0dad9abc06ea Salt = sDY,?H5AxC-!gH3a.:)D ''; modules = [ "partyline" "webadmin" "adminlog" "log" ]; networks = { "libera" = { server = "irc.libera.chat"; port = 6697; useSSL = true; modules = [ "simple_away" ]; }; "nullring" = { server = "${config.monorepo.vars.orgHost}"; port = 6697; useSSL = true; modules = [ "simple_away" "log" ]; }; }; }; }; } #+end_src Note that the password hash and whatnot is completely random so there is almost no point to cracking it with hashcat. ** Conduit This is a modern matrix server that is meant to be lightweight while still federating and hosting the same protocol. There is also a configuration for lk-jwt and livekit which is important for configuring p2p calls in matrix. #+begin_src nix :tangle ../nix/modules/conduit.nix { config, lib, ... }: let livekitListenPort = 8443; # secrets.yaml livekit_secret = "livekit_secret"; conduit_secret = "conduit_secrets"; in { sops.secrets = lib.mkIf config.services.matrix-conduit.enable { "${livekit_secret}" = lib.mkIf config.services.livekit.enable { format = "yaml"; mode = "0444"; }; "${conduit_secret}" = { format = "yaml"; }; }; services.matrix-conduit = { enable = lib.mkDefault config.monorepo.profiles.server.enable; secretFile = "/run/secrets/${conduit_secret}"; settings.global = { server_name = "matrix.${config.monorepo.vars.orgHost}"; trusted_servers = [ "matrix.org" "nixos.org" "conduit.rs" ]; address = "0.0.0.0"; port = 6167; allow_registration = false; }; }; services.livekit = { enable = lib.mkDefault (config.services.matrix-conduit.enable || config.services.matrix-synapse.enable); keyFile = "/run/secrets/${livekit_secret}"; settings = { port = 7880; turn = { enabled = true; domain = "livekit.${config.monorepo.vars.orgHost}"; cert_file = "/var/lib/acme/livekit.${config.monorepo.vars.orgHost}/fullchain.pem"; key_file = "/var/lib/acme/livekit.${config.monorepo.vars.orgHost}/key.pem"; tls_port = 5349; udp_port = 3478; }; rtc = { use_external_ip = true; tcp_port = 7881; udp_port = 7882; port_range_start = 50000; port_range_end = 60000; }; }; }; services.lk-jwt-service = { enable = lib.mkDefault config.services.livekit.enable; port = 6495; livekitUrl = "wss://livekit.${config.monorepo.vars.orgHost}"; keyFile = "/run/secrets/${livekit_secret}"; }; # TODO: split into conduit and livekit networking.firewall.allowedTCPPorts = lib.mkIf config.services.matrix-conduit.enable [ 8448 7881 5349 livekitListenPort ]; # this is fine though networking.firewall.allowedUDPPorts = lib.mkIf config.services.livekit.enable [ 7882 3478 ]; networking.firewall.allowedUDPPortRanges = lib.mkIf config.services.livekit.enable [ { from = 49152; to = 65535; } ]; networking.domains.subDomains."matrix.${config.monorepo.vars.orgHost}" = lib.mkIf config.services.matrix-conduit.enable {}; networking.domains.subDomains."livekit.${config.monorepo.vars.orgHost}" = lib.mkIf config.services.livekit.enable {}; services.nginx.virtualHosts."matrix.${config.monorepo.vars.orgHost}" = lib.mkIf config.services.matrix-conduit.enable { enableACME = lib.mkDefault config.monorepo.profiles.server.enable; forceSSL = true; listen = [ { addr = "0.0.0.0"; port = 443; ssl = true; } { addr = "[::]"; port = 443; ssl = true; } { addr = "0.0.0.0"; port = 8448; ssl = true; } { addr = "[::]"; port = 8448; ssl = true; } ]; locations."/_matrix/" = { proxyPass = "http://127.0.0.1:${toString config.services.matrix-conduit.settings.global.port}"; extraConfig = '' proxy_set_header Host $host; proxy_buffers 32 16k; proxy_read_timeout 5m; ''; }; locations."= /.well-known/matrix/server" = { extraConfig = '' default_type application/json; add_header Content-Type application/json; add_header Access-Control-Allow-Origin *; ''; return = ''200 '{"m.server": "matrix.${config.monorepo.vars.orgHost}:443"}' ''; }; locations."/.well-known/matrix/client" = { extraConfig = '' default_type application/json; add_header Access-Control-Allow-Origin *; ''; return = "200 '{\"m.homeserver\": {\"base_url\": \"https://matrix.${config.monorepo.vars.orgHost}\"}, \"org.matrix.msc4143.rtc_foci\": [{\"type\": \"livekit\", \"livekit_service_url\": \"https://matrix.${config.monorepo.vars.orgHost}:${toString livekitListenPort}\"}]}'"; }; extraConfig = '' merge_slashes off; ''; }; services.nginx.virtualHosts."matrix.${config.monorepo.vars.orgHost}-livekit" = lib.mkIf config.services.livekit.enable { serverName = "matrix.${config.monorepo.vars.orgHost}"; listen = [ { addr = "0.0.0.0"; port = livekitListenPort; ssl = true; } { addr = "[::]"; port = livekitListenPort; ssl = true; } ]; addSSL = true; enableACME = false; forceSSL = false; useACMEHost = "matrix.${config.monorepo.vars.orgHost}"; locations."/" = { proxyPass = "http://127.0.0.1:${toString config.services.lk-jwt-service.port}"; proxyWebsockets = true; extraConfig = '' proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; ''; }; }; services.nginx.virtualHosts."livekit.${config.monorepo.vars.orgHost}" = lib.mkIf config.services.livekit.enable { enableACME = true; forceSSL = true; locations."/" = { proxyPass = "http://127.0.0.1:${toString config.services.livekit.settings.port}"; proxyWebsockets = true; extraConfig = '' proxy_read_timeout 3600s; proxy_send_timeout 3600s; # Standard headers for LiveKit proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # --- CORS CONFIGURATION START --- # 1. Allow all origins (including app.element.io) add_header 'Access-Control-Allow-Origin' '*' always; # 2. Allow specific methods (POST is required for /sfu/get) add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; # 3. Allow headers (Content-Type is crucial for JSON) add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; # 4. Handle the OPTIONS preflight request immediately if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; } # --- CORS CONFIGURATION END --- ''; }; }; } #+end_src ** TODO GoToSocial This is a basic ActivityPub server. #+begin_src nix :tangle ../nix/modules/gotosocial.nix { lib, config, ... }: { services.gotosocial = { enable = lib.mkDefault false; setupPostgresqlDB = true; settings = { application-name = "Nullring GoToSocial Instance"; host = "gotosocial.${config.monorepo.vars.orgHost}"; protocol = "https"; bind-address = "127.0.0.1"; port = 8080; }; }; } #+end_src ** TODO matrix-appservice-irc #+begin_src nix :tangle ../nix/modules/matrix-appservice-irc.nix { lib, config, ... }: { enable = lib.mkDefault config.monorepo.profiles.server.enable; registrationUrl = "localhost"; settings = { homeserver.url = "https://matrix.nullring.xyz"; homserver.domain = "matrix.nullring.xyz"; }; } #+end_src ** Gitolite #+begin_src nix :tangle ../nix/modules/gitolite.nix { lib, config, ... }: { services.gitolite = { enable = lib.mkDefault config.monorepo.profiles.server.enable; description = "My Gitolite User"; adminPubkey = config.monorepo.vars.sshKey; }; } #+end_src ** Matterbridge I want to connect IRC to discord with matterbridge. #+begin_src nix :tangle ../nix/modules/matterbridge.nix { lib, config, ... }: { services.matterbridge = { enable = lib.mkDefault config.monorepo.profiles.server.enable; configPath = "${config.sops.templates.matterbridge.path}"; }; } #+end_src *** Mautrix I use this bridge to bridge myself from Matrix to Discord and vise versa, because Matterbridge is not maintained very well and therefore does not support conduit at the moment. Note that this is not fully declarative and requires that you add ~/var/lib/mautrix-discord/discord-registration.yaml~ as an appservice to conduit. #+begin_src nix :tangle ../nix/modules/mautrix.nix { lib, config, ... }: { services.mautrix-discord = { enable = lib.mkDefault config.monorepo.profiles.server.enable; environmentFile = "/run/secrets/mautrix_env"; settings = { bridge = { animated_sticker = { args = { fps = 25; height = 320; width = 320; }; target = "webp"; }; autojoin_thread_on_open = true; avatar_proxy_key = "generate"; backfill = { forward_limits = { initial = { channel = 0; dm = 0; thread = 0; }; max_guild_members = -1; missed = { channel = 0; dm = 0; thread = 0; }; }; }; cache_media = "unencrypted"; channel_name_template = "{{if or (eq .Type 3) (eq .Type 4)}}{{.Name}}{{else}}#{{.Name}}{{end}}"; command_prefix = "!discord"; custom_emoji_reactions = true; delete_guild_on_leave = true; delete_portal_on_channel_delete = false; delivery_receipts = false; direct_media = { allow_proxy = true; enabled = false; server_key = "generate"; }; displayname_template = "{{if .Webhook}}Webhook{{else}}{{or .GlobalName .Username}}{{if .Bot}} (bot){{end}}{{end}}"; double_puppet_allow_discovery = true; double_puppet_server_map = { }; embed_fields_as_tables = true; enable_webhook_avatars = true; encryption = { allow = false; allow_key_sharing = false; appservice = false; default = false; delete_keys = { delete_fully_used_on_decrypt = false; delete_on_device_delete = false; delete_outbound_on_ack = false; delete_outdated_inbound = false; delete_prev_on_new_session = false; dont_store_outbound = false; periodically_delete_expired = false; ratchet_on_decrypt = false; }; msc4190 = false; plaintext_mentions = false; require = false; rotation = { disable_device_change_key_rotation = false; enable_custom = false; messages = 100; milliseconds = 604800000; }; verification_levels = { receive = "unverified"; send = "unverified"; share = "cross-signed-tofu"; }; }; federate_rooms = true; guild_name_template = "{{.Name}}"; login_shared_secret_map = { }; management_room_text = { additional_help = ""; welcome = "Hello, I'm a Discord bridge bot."; welcome_connected = "Use `help` for help."; welcome_unconnected = "Use `help` for help or `login` to log in."; }; message_error_notices = true; message_status_events = false; mute_channels_on_create = false; permissions = { "@${config.monorepo.vars.internetName}:matrix.${config.monorepo.vars.orgHost}" = "admin"; "*" = "user"; }; portal_message_buffer = 128; prefix_webhook_messages = true; private_chat_portal_meta = "default"; provisioning = { debug_endpoints = false; prefix = "/_matrix/provision"; shared_secret = "generate"; }; public_address = null; resend_bridge_info = false; restricted_rooms = false; startup_private_channel_create_limit = 5; sync_direct_chat_list = false; use_discord_cdn_upload = true; username_template = "discord_{{.}}"; }; appservice = { address = "http://localhost:29334"; hostname = "0.0.0.0"; port = 29334; id = "discord"; bot = { username = "discordbot"; displayname = "Discord bridge bot"; avatar = "mxc://maunium.net/nIdEykemnwdisvHbpxflpDlC"; }; ephemeral_events = true; async_transactions = false; database = { type = "sqlite3"; uri = "file:${config.services.mautrix-discord.dataDir}/mautrix-discord.db?_txlock=immediate"; max_open_conns = 20; max_idle_conns = 2; max_conn_idle_time = null; max_conn_lifetime = null; }; as_token = "$MAUTRIX_DISCORD_APPSERVICE_AS_TOKEN"; hs_token = "$MAUTRIX_DISCORD_APPSERVICE_HS_TOKEN"; }; dataDir = "/var/lib/mautrix-discord"; homeserver = { async_media = false; message_send_checkpoint_endpoint = null; ping_interval_seconds = 0; software = "standard"; status_endpoint = null; websocket = false; domain = "matrix.${config.monorepo.vars.orgHost}"; address = "http://localhost:6167"; }; }; }; } #+end_src ** Ollama Use ollama for serving large language models to my other computers. #+begin_src nix :tangle ../nix/modules/ollama.nix { config, lib, pkgs, ... }: { # services.open-webui.enable = lib.mkDefault (!config.monorepo.profiles.server.enable); services.ollama = { enable = lib.mkDefault config.monorepo.profiles.desktop.enable; package = if (config.monorepo.profiles.cuda.enable) then pkgs.ollama-cuda else pkgs.ollama-vulkan; loadModels = if (config.monorepo.profiles.cuda.enable) then [ "qwen3:30b" "qwen3-coder:latest" "qwen2.5-coder:latest" "gemma3:12b-it-qat" ] else [ "qwen3:0.6b" "qwen2.5-coder:0.5b" ]; host = "0.0.0.0"; openFirewall = true; }; } #+end_src ** Bitcoind #+begin_src nix :tangle ../nix/modules/bitcoin.nix { config, lib, ... }: { services.bitcoind."${config.monorepo.vars.userName}" = { enable = lib.mkDefault config.monorepo.profiles.workstation.enable; prune = 10000; }; } #+end_src ** Git Server I run my own git server in order to have a mirror in case github goes down. #+begin_src nix :tangle ../nix/modules/git-daemon.nix { config, lib, ... }: { services.gitDaemon = { enable = lib.mkDefault config.monorepo.profiles.server.enable; exportAll = true; basePath = "${config.users.users.git.home}"; }; networking.firewall.allowedTCPPorts = lib.mkIf config.services.gitDaemon.enable [ 9418 ]; } #+end_src ** Ntfy I want to have notifications on my phone, and run my own server to do this. #+begin_src nix :tangle ../nix/modules/ntfy-sh.nix { pkgs, lib, config, ... }: let serverName = "ntfy.${config.monorepo.vars.remoteHost}"; port = 2586; ntfySecret = config.monorepo.vars.ntfySecret; in { sops.secrets."${ntfySecret}" = lib.mkIf config.services.ntfy-sh.enable { format = "yaml"; owner = "ntfy-sh"; sopsFile = ../secrets/common_secrets.yaml; }; services.ntfy-sh = { enable = lib.mkDefault config.monorepo.profiles.server.enable; settings = { base-url = "https://${serverName}"; listen-http = "127.0.0.1:${toString port}"; envrionmentFile = "/run/secrets/${ntfySecret}"; auth-file = "/var/lib/ntfy-sh/user.db"; auth-default-access = "deny-all"; enable-login = true; }; }; services.nginx.enable = config.services.ntfy-sh.enable; systemd.services.ntfy-sh = lib.mkIf config.services.ntfy-sh.enable { serviceConfig = { EnvironmentFile = "/run/secrets/${ntfySecret}"; }; postStart = lib.mkForce '' # 1. Wait for the server to initialize the database echo "Waiting for ntfy auth database to appear..." TIMEOUT=30 while [ ! -f /var/lib/ntfy-sh/user.db ]; do sleep 1 TIMEOUT=$((TIMEOUT-1)) if [ $TIMEOUT -le 0 ]; then echo "Timed out waiting for database creation!" exit 1 fi done echo "Database found. Configuring admin user..." # 2. Define the username ADMIN_USER="ret2pop" # 3. Check if user exists, create if missing # We pipe the password twice because 'ntfy user add' asks for confirmation if ! ${pkgs.ntfy-sh}/bin/ntfy user list | grep -q "$ADMIN_USER"; then echo "Creating admin user $ADMIN_USER..." printf "$ADMIN_PASSWORD\n$ADMIN_PASSWORD" | \ ${pkgs.ntfy-sh}/bin/ntfy user add --role=admin "$ADMIN_USER" echo "User created." else echo "Admin user already exists." fi ''; }; networking.domains.subDomains."${serverName}" = lib.mkIf config.services.ntfy-sh.enable {}; services.nginx.virtualHosts."${serverName}" = lib.mkIf config.services.ntfy-sh.enable { serverName = "${serverName}"; enableACME = true; forceSSL = true; locations."/" = { proxyPass = "http://127.0.0.1:${toString port}"; proxyWebsockets = true; extraConfig = '' proxy_buffering off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; ''; }; }; } #+end_src ** Nginx These are all my virtual hosts. For many of these servers we have to have a reverse proxy in order to expose the locally running instances to the outside world under a domain. #+begin_src nix :tangle ../nix/modules/nginx.nix { pkgs, config, lib, monorepoSelf ? null, ... }: { services.nginx = { enable = lib.mkDefault config.monorepo.profiles.server.enable; user = "nginx"; recommendedGzipSettings = true; recommendedOptimisation = true; recommendedBrotliSettings = true; recommendedTlsSettings = true; recommendedProxySettings = false; virtualHosts = { "${config.monorepo.vars.remoteHost}" = lib.mkIf (monorepoSelf != null) { serverName = "${config.monorepo.vars.remoteHost}"; serverAliases = [ "${config.monorepo.vars.internetName}.${config.monorepo.vars.orgHost}" ]; root = "${monorepoSelf.packages.${pkgs.system}.website}"; addSSL = true; enableACME = true; locations."/" = { extraConfig = '' add_header Cache-Control "no-cache, must-revalidate"; expires off; ''; }; locations."~* \\.(?:woff2|ttf|otf|eot|woff|ico|css|js|gif|jpe?g|png|svg|mp3|mp4|iso|webmanifest)$" = { extraConfig = '' add_header Cache-Control "public, max-age=31536000, immutable"; access_log off; ''; }; }; # the port comes from ssh tunnelling "music.${config.monorepo.vars.remoteHost}" = lib.mkIf config.monorepo.profiles.server.enable { addSSL = true; enableACME = true; basicAuthFile = config.sops.secrets."mpd_password".path; locations."/" = { proxyPass = "http://localhost:8000"; extraConfig = '' proxy_buffering off; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 36000s; ''; }; }; "${config.monorepo.vars.orgHost}" = { serverName = "${config.monorepo.vars.orgHost}"; root = "/var/www/nullring/"; addSSL = true; enableACME = true; }; }; }; networking.firewall.allowedTCPPorts = lib.mkIf config.services.nginx.enable [ 80 443 ]; networking.domains.subDomains = lib.mkIf config.services.nginx.enable { "${config.monorepo.vars.remoteHost}" = {}; "${config.monorepo.vars.orgHost}" = {}; "${config.monorepo.vars.internetName}.${config.monorepo.vars.orgHost}" = {}; }; } #+end_src ** CGit Interface I have this cgit interface in order to integrate with public-inbox. #+begin_src nix :tangle ../nix/modules/cgit.nix { lib, config, ... }: let serverName = "git.${config.monorepo.vars.orgHost}"; in { services.cgit."my-projects" = { enable = lib.mkDefault config.services.gitDaemon.enable; scanPath = "${config.users.users.git.home}"; settings = { root-title = "Nullring Git Server"; root-desc = "Projects and cool things"; enable-commit-graph = 1; enable-log-filecount = 1; enable-log-linecount = 1; enable-index-owner = 0; clone-prefix = "https://${serverName}"; enable-tree-linenumbers = 1; strict-export = "git-daemon-export-ok"; }; gitHttpBackend = { enable = true; checkExportOkFiles = true; }; nginx = { virtualHost = "${serverName}"; }; }; networking.domains.subDomains."${serverName}" = lib.mkIf config.services.cgit."my-projects".enable {}; services.nginx.virtualHosts."${serverName}" = lib.mkIf config.services.cgit."my-projects".enable { forceSSL = true; enableACME = true; }; } #+end_src ** Nvidia I have an Nvidia GPU on my computer. #+begin_src nix :tangle ../nix/modules/nvidia.nix { config, lib, pkgs, ... }: { hardware = { graphics.extraPackages = (if config.monorepo.profiles.cuda.enable then with pkgs; [ libva-vdpau-driver libvdpau-va-gl nvidia-vaapi-driver ] else []); nvidia = { modesetting.enable = lib.mkDefault config.monorepo.profiles.cuda.enable; powerManagement = { enable = lib.mkDefault config.monorepo.profiles.cuda.enable; finegrained = false; }; open = config.monorepo.profiles.cuda.enable; package = config.boot.kernelPackages.nvidiaPackages.stable; nvidiaSettings = false; }; }; } #+end_src ** CUDA I need CUDA on some computers because I run local LLMs. #+begin_src nix :tangle ../nix/modules/cuda.nix { config, pkgs, ... }: { environment.systemPackages = (if config.monorepo.profiles.cuda.enable then with pkgs; [ cudatoolkit cudaPackages.cudnn cudaPackages.libcublas linuxPackages.nvidia_x11 ] else []); } #+end_src ** Maddy There is a non declarative part of setting dkims and spf. #+begin_src nix :tangle ../nix/modules/maddy.nix { lib, config, options, ... }: let emailServerName = "mail.${config.monorepo.vars.orgHost}"; serverName = "list.${config.monorepo.vars.orgHost}"; password_path = "mail_monorepo_password"; in { sops.secrets = lib.mkIf config.services.maddy.enable { "${password_path}" = lib.mkIf config.services.maddy.enable { format = "yaml"; owner = "maddy"; }; }; services.maddy = { enable = lib.mkDefault config.monorepo.profiles.server.enable; openFirewall = true; hostname = "${config.monorepo.vars.orgHost}"; primaryDomain = "mail.${config.monorepo.vars.orgHost}"; localDomains = [ "$(primary_domain)" "${config.monorepo.vars.orgHost}" ]; tls = { loader = "file"; certificates = [ { keyPath = "/var/lib/acme/mail.${config.monorepo.vars.orgHost}/key.pem"; certPath = "/var/lib/acme/mail.${config.monorepo.vars.orgHost}/fullchain.pem"; } ]; }; config = builtins.replaceStrings [ "imap tcp://0.0.0.0:143" "submission tcp://0.0.0.0:587" ] [ "imap tls://0.0.0.0:993 tcp://0.0.0.0:143" "submission tls://0.0.0.0:465 tcp://0.0.0.0:587" ] options.services.maddy.config.default; ensureAccounts = (builtins.map (x: "${x}@${config.monorepo.vars.orgHost}") config.monorepo.vars.projects) ++ [ "${config.monorepo.vars.internetName}@${config.monorepo.vars.orgHost}" "discussion@${config.monorepo.vars.orgHost}" ]; ensureCredentials = lib.genAttrs config.services.maddy.ensureAccounts (_: { passwordFile = "/run/secrets/${password_path}"; }) // { "${config.monorepo.vars.internetName}@${config.monorepo.vars.orgHost}" = { passwordFile = "/run/secrets/mail_password"; }; }; }; systemd.tmpfiles.rules = [ "C+ /var/lib/public-inbox/style.css 0644 public-inbox public-inbox - ${../data/public-inbox.css}" ]; systemd.services.public-inbox-httpd = if config.monorepo.profiles.server.enable then { preStart = '' # Copy or link the file. # Using 'cp' is often safer for sandboxed services than linking to the store. Lol. cp -f ${../data/public-inbox.css} /var/lib/public-inbox/style.css chmod 644 /var/lib/public-inbox/style.css ''; serviceConfig = { # Allow the service to see the file it just created BindPaths = [ "/var/lib/public-inbox" "${config.users.users.git.home}" ]; ReadOnlyPaths = [ "/var/lib/public-inbox/style.css" ]; # Ensure it can actually write to the directory during preStart ReadWritePaths = [ "/var/lib/public-inbox" ]; }; } else {}; systemd.services.public-inbox-watch = if config.monorepo.profiles.server.enable then { after = [ "sops-nix.service" ]; confinement.enable = lib.mkForce false; preStart = '' mkdir -p /var/lib/public-inbox/.tmp chmod 0700 /var/lib/public-inbox/.tmp ln -sfn ${config.sops.templates."public-inbox-netrc".path} /var/lib/public-inbox/.netrc ''; environment = { PUBLIC_INBOX_FORCE_IPV4 = "1"; NETRC = config.sops.templates."public-inbox-netrc".path; HOME = "/var/lib/public-inbox"; TMPDIR = "/var/lib/public-inbox/.tmp"; }; serviceConfig = { RestrictSUIDSGID = lib.mkForce false; ReadWritePaths = [ "/var/lib/public-inbox" ]; RestrictAddressFamilies = lib.mkForce [ "AF_UNIX" "AF_INET" "AF_INET6" ]; PrivateNetwork = lib.mkForce false; SystemCallFilter = lib.mkForce []; RootDirectory = lib.mkForce ""; CapabilityBoundingSet = lib.mkForce [ "~" ]; UMask = lib.mkForce "0022"; ProtectSystem = lib.mkForce false; }; } else {}; services.public-inbox = { enable = lib.mkDefault config.monorepo.profiles.server.enable; settings = { coderepo = lib.genAttrs config.monorepo.vars.projects (name: { dir = "${config.users.users.git.home}/${name}.git"; # works even if no cgit server running here, this is just the default cgitUrl = "https://git.${config.monorepo.vars.orgHost}/${name}.git"; }); publicinbox.css = ["/var/lib/public-inbox/style.css"]; publicinbox.wwwlisting = "all"; }; http = { enable = true; port = 9090; }; inboxes = lib.genAttrs config.monorepo.vars.projects (name: { description = "discussion of the ${name} project."; address = [ "${name}@${config.monorepo.vars.orgHost}" ]; inboxdir = "/var/lib/public-inbox/${name}"; url = "https://list.${config.monorepo.vars.orgHost}/${name}"; watch = [ "imaps://${name}${config.monorepo.vars.orgHost}@${emailServerName}/INBOX" ]; coderepo = [ "${name}" ]; }) // { "discussion" = { description = "Main Nullring Discussion Mailing List"; address = [ "discussion@${config.monorepo.vars.orgHost}" ]; inboxdir = "/var/lib/public-inbox/discuss"; url = "https://${serverName}/discussion"; watch = [ "imaps://discussion%40${config.monorepo.vars.orgHost}@${emailServerName}/INBOX" ]; }; }; }; networking.domains.baseDomains."${config.monorepo.vars.orgHost}" = lib.mkIf config.services.maddy.enable { mx.data = [ { preference = 10; exchange = "${emailServerName}"; } ]; }; networking.domains.subDomains = lib.mkIf config.services.maddy.enable { "${serverName}" = {}; "${emailServerName}" = {}; "_dmarc.${config.monorepo.vars.orgHost}" = { txt = { data = "v=DMARC1; p=none"; }; }; "default._domainkey.${config.monorepo.vars.orgHost}" = { txt = { data = "v=DKIM1; k=rsa; p=${config.monorepo.vars.dkimKey}"; }; }; }; networking.firewall.allowedTCPPorts = lib.mkIf config.services.maddy.enable [ 143 465 587 993 ]; services.nginx.virtualHosts."${serverName}" = lib.mkIf config.services.public-inbox.enable { forceSSL = true; enableACME = true; locations."/" = { proxyPass = "http://localhost:${toString config.services.public-inbox.http.port}"; extraConfig = '' proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; ''; }; }; services.nginx.virtualHosts."${emailServerName}" = lib.mkIf config.services.maddy.enable { serverName = "${emailServerName}"; root = "/var/www/dummy"; addSSL = true; enableACME = true; }; } #+end_src *** Public Inbox CSS This is a minimal stylesheet for public inbox so that I don't get eye cancer while reading it. #+begin_src nix :tangle ../nix/data/public-inbox.css :root { --bg: #f8f9fa; --fg: #2e3440; --link: #5e81ac; --link-hover: #81a1c1; --border: #d8dee9; --card-bg: #ffffff; --meta-fg: #4c566a; /* Darker gray for better legibility */ --btn-fg: #ffffff; --max-width: 780px; } @media (prefers-color-scheme: dark) { :root { --bg: #1a1b26; --fg: #a9b1d6; --link: #7aa2f7; --link-hover: #bb9af7; --border: #414868; /* Distinct border for dark mode */ --card-bg: #1f2335; --meta-fg: #9aa5ce; /* Brighter gray for dark mode */ --btn-fg: #1a1b26; } } span.q { color: var(--meta-fg); font-style: italic; } body { background-color: var(--bg); color: var(--fg); line-height: 1.6; max-width: var(--max-width); margin: 3rem auto; padding: 0 1.5rem; font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; /* Keep this for smoother rendering on macOS/iOS */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } /* 1. Global Link Fixes */ body a, body a:visited { color: var(--link); } /* 2. Card Styling */ body pre { white-space: pre-wrap; background: var(--card-bg); padding: 2rem; border-radius: 12px; border: 1px solid var(--border); margin-bottom: 2.5rem; } /* 3. Header Cleanup */ body pre b:first-of-type { font-weight: 700; color: var(--link); font-size: 1.1rem; display: block; margin-bottom: 0.5rem; } /* 4. Fixing the "Permalink" label on documentation */ /* We target links ending in / but EXCLUDE system paths like help or mirror */ body pre a[href$="/"]:not([href*="_/"]):not([href*="new.atom"]) { font-size: 0; text-decoration: none; margin-right: 10px; } body pre a[href$="/"]:not([href*="_/"]):not([href*="new.atom"]):after { content: "permalink"; font-size: 11px; font-weight: bold; color: var(--fg); /* Use main text color for high contrast */ background: var(--bg); border: 1px solid var(--link); /* Use link color for the border */ padding: 4px 10px; border-radius: 4px; display: inline-block; } /* 5. Fixing the "Raw" button contrast */ body pre a[href$="/raw"] { font-size: 0; text-decoration: none; } body pre a[href$="/raw"]:after { content: "raw"; font-size: 11px; font-weight: bold; color: var(--fg); background: var(--bg); border: 1px solid var(--link); padding: 4px 10px; border-radius: 4px; display: inline-block; } /* Hover effect for ghost buttons: solid color shift */ body pre a[href$="/"]:hover:after, body pre a[href$="/raw"]:hover:after { background: var(--link); color: var(--btn-fg); } /* 6. The Reply Button (Primary Action) */ body pre a[href$="#R"], body pre a[href$="#R"]:visited { font-size: 0; text-decoration: none; } body pre a[href$="#R"]:after { content: "REPLY"; font-size: 12px; font-weight: bold; padding: 6px 20px; background: var(--link); color: var(--btn-fg); border-radius: 6px; display: inline-block; margin-left: 10px; } /* 7. Hide clutter */ body pre a[href^="#r"], body pre a[href^="#r"] + b, body hr { display: none; } /* Fix: Mathematically outscore the header rule to keep link text inline */ body pre a[href] b:first-of-type { display: inline; font-size: inherit; margin-bottom: 0; color: inherit; } #+end_src ** Fail2Ban This is a service that bans bots that try to sign in on my server. #+begin_src nix :tangle ../nix/modules/fail2ban.nix { lib, config, ... }: { services.fail2ban = { enable = lib.mkDefault config.monorepo.profiles.server.enable; # Ban IP after 5 failures for 1 hour maxretry = 5; bantime = "1h"; banaction = "iptables-allports"; banaction-allports = "iptables-allports"; }; } #+end_src ** Impermanence This is my impermanence profile, which removes all files on reboot except for the ones listed below. #+begin_src nix :tangle ../nix/modules/impermanence.nix { lib, config, ... }: { assertions = [ { assertion = (! (config.monorepo.profiles.impermanence.enable && (! (config.monorepo.vars.fileSystem == "btrfs")))); message = "Impermanence requires btrfs filesystem."; } ]; boot.initrd.postResumeCommands = (if config.monorepo.profiles.impermanence.enable then lib.mkAfter '' mkdir /btrfs_tmp mount -t btrfs -n -o subvol=/ /dev/mapper/crypted /btrfs_tmp if [[ -e /btrfs_tmp/root ]]; then mkdir -p /btrfs_tmp/old_roots timestamp=$(date --date="@$(stat -c %Y /btrfs_tmp/root)" "+%Y-%m-%-d_%H:%M:%S") mv /btrfs_tmp/root "/btrfs_tmp/old_roots/$timestamp" fi delete_subvolume_recursively() { IFS=$'\n' for i in $(btrfs subvolume list -o "$1" | cut -f 9- -d ' '); do delete_subvolume_recursively "/btrfs_tmp/$i" done btrfs subvolume delete "$1" } for i in $(find /btrfs_tmp/old_roots/ -maxdepth 1 -mtime +30); do delete_subvolume_recursively "$i" done btrfs subvolume create /btrfs_tmp/root umount -n /btrfs_tmp '' else ""); boot.initrd.luks.devices = (if (config.monorepo.vars.fileSystem == "btrfs") then { crypted = { device = "/dev/disk/by-partlabel/disk-main-luks"; }; } else {}); fileSystems = if (config.monorepo.profiles.impermanence.enable) then { "/persistent" = { neededForBoot = true; }; "/home" = { neededForBoot = true; }; } else {}; environment.persistence."/persistent" = { enable = config.monorepo.profiles.impermanence.enable; hideMounts = true; directories = [ "/var/log" "/var/lib/bluetooth" "/var/lib/nixos" "/var/lib/systemd/coredump" "/etc/NetworkManager/system-connections" ]; files = [ "/etc/machine-id" { file = "/var/keys/secret_file"; parentDirectory = { mode = "u=rwx,g=,o="; }; } ]; users."${config.monorepo.vars.userName}" = { directories = [ "Downloads" "music" "Pictures" "Documents" "Videos" "Monero" "org" "monorepo" "soundfont" "website_html" "ardour" "audacity" "img" "email" "projects" "secrets" ".emacs.d" ".elfeed" ".electrum" ".mozilla" ".bitmonero" ".config" ".crypto" { directory = ".gnupg"; mode = "0700"; } { directory = ".ssh"; mode = "0700"; } { directory = ".local/share/keyrings"; mode = "0700"; } ".local/share/direnv" ]; files = [ ".emacs" ]; }; }; } #+end_src ** Nixpkgs #+begin_src nix :tangle ../nix/modules/nixpkgs-options.nix { lib, config, isIntegrationTest, ... }: { nixpkgs = lib.mkIf (! isIntegrationTest) { hostPlatform = lib.mkDefault "x86_64-linux"; config = { allowUnfree = true; cudaSupport = lib.mkDefault config.monorepo.profiles.cuda.enable; }; config.permittedInsecurePackages = [ "python3.13-ecdsa-0.19.1" "olm-3.2.16" ]; }; } #+end_src ** Main Configuration This is the backbone of the all the NixOS configurations, with all these options being shared because they enhance security. #+begin_src nix :tangle ../nix/modules/configuration.nix { config, pkgs, lib, ... }: let userGroups = [ "nginx" "git" "ircd" "ngircd" "conduit" "livekit" "matterbridge" "maddy" "ntfy-sh" "public-inbox" "plugdev" ]; allDomains = (lib.attrNames config.networking.domains.baseDomains) ++ (lib.attrNames config.networking.domains.subDomains); # 2. Generate BOTH possible outcomes in advance prodHosts = map (dom: "${config.monorepo.profiles.server.ipv4} ${dom}") allDomains; vmHosts = map (dom: "127.0.0.1 ${dom}") allDomains; in { imports = [ ./cgit.nix ./public_inbox.nix ./matterbridge.nix ./mautrix.nix ./xserver.nix ./ssh.nix ./pipewire.nix ./tor.nix ./kubo.nix ./nvidia.nix ./cuda.nix ./nginx.nix ./secrets.nix ./git-daemon.nix ./ollama.nix ./i2pd.nix ./conduit.nix ./bitcoin.nix ./ngircd.nix ./znc.nix ./docker.nix ./impermanence.nix ./maddy.nix ./ntfy-sh.nix ./fail2ban.nix ./nixpkgs-options.nix ]; environment.etc."wpa_supplicant.conf".text = '' country=CA ''; systemd.tmpfiles.rules = [ "d /srv/git 0755 git git -" ]; zramSwap = lib.mkIf config.monorepo.profiles.desktop.enable { enable = true; algorithm = "zstd"; memoryPercent = 50; }; virtualisation.vmVariant = { sops.validateSopsFiles = false; disko.devices = lib.mkForce {}; virtualisation.forwardPorts = lib.mkIf config.monorepo.profiles.server.enable [ { from = "host"; host.port = 10443; guest.port = 443; } { from = "host"; host.port = 9080; guest.port = 80; } ]; virtualisation.useNixStoreImage = false; virtualisation.sharedDirectories.sops-keys = { source = "/home/preston/.config/sops/age"; target = "/home/preston/.config/sops/age"; }; networking.extraHosts = lib.mkForce (lib.concatStringsSep "\n" vmHosts); networking.defaultGateway = lib.mkForce null; networking.interfaces.eth0.useDHCP = lib.mkForce true; fileSystems."/" = lib.mkForce { device = "/dev/disk/by-label/nixos"; fsType = "ext4"; }; systemd.services.sops-nix = { unitConfig.RequiresMountsFor = "/home/preston/.config/sops/age"; }; }; documentation = { enable = lib.mkDefault config.monorepo.profiles.documentation.enable; man.enable = lib.mkDefault config.monorepo.profiles.documentation.enable; dev.enable = lib.mkDefault config.monorepo.profiles.documentation.enable; }; environment = { etc = { securetty.text = '' # /etc/securetty: list of terminals on which root is allowed to login. # See securetty(5) and login(1). ''; }; }; systemd.network.enable = lib.mkDefault config.monorepo.profiles.server.enable; systemd.network.networks."40-${config.monorepo.profiles.server.interface}" = lib.mkIf config.monorepo.profiles.server.enable { matchConfig.Name = "${config.monorepo.profiles.server.interface}"; networkConfig = { IPv6AcceptRA = true; IPv6PrivacyExtensions = false; }; ipv6AcceptRAConfig = { UseAutonomousPrefix = false; }; }; systemd = { services.NetworkManager-wait-online.enable = false; coredump.enable = false; network.config.networkConfig.IPv6PrivacyExtensions = "kernel"; tmpfiles.settings = { "restrictetcnixos"."/etc/nixos/*".Z = { mode = "0000"; user = "root"; group = "root"; }; }; }; boot = { supportedFilesystems = { btrfs = true; ext4 = true; }; extraModprobeConfig = '' options snd-usb-audio vid=0x1235 pid=0x8200 device_setup=1 options rtw88_core disable_lps_deep=y power_save=0 disable_aspm_l1ss=y options rtw88_pci disable_msi=y disable_aspm=y options rtw_core disable_lps_deep=y options rtw_pci disable_msi=y disable_aspm=y options rtw89_core disable_ps_mode=y options rtw89_pci disable_aspm_l1=y disable_aspm_l1ss=y disable_clkreq=y options iwlwifi 11n_disable=8 uapsd_disable=1 bt_coex_active=0 disable_11ax=1 power_save=0 ''; extraModulePackages = [ ]; initrd = { availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "sd_mod" "nvme" "sd_mod" "ehci_pci" "rtsx_pci_sdmmc" "usbhid" ]; kernelModules = [ ]; }; lanzaboote = { enable = config.monorepo.profiles.secureBoot.enable; pkiBundle = "/var/lib/sbctl"; }; loader = { systemd-boot.enable = lib.mkForce ((! config.monorepo.profiles.grub.enable) && (! config.monorepo.profiles.secureBoot.enable)); efi.canTouchEfiVariables = lib.mkForce (! config.monorepo.profiles.grub.enable); }; kernelModules = [ "snd-seq" "snd-rawmidi" "xhci_hcd" "kvm_intel" "af_packet" "ccm" "ctr" "cmac" "arc4" "ecb" "michael_mic" "gcm" "sha256" "sha384" ]; kernelParams = [ "cfg80211.reg_alpha2=CA" "usbcore.autosuspend=-1" "pcie_aspm=off" "pci=noaer" "page_alloc.shuffle=1" "slab_nomerge" # madaidan "pti=on" "randomize_kstack_offset=on" "vsyscall=none" # cpu "spectre_v2=on" "spec_store_bypass_disable=on" "tsx=off" "l1tf=full,force" "kvm.nx_huge_pages=force" # hardened "extra_latent_entropy" # mineral "quiet" ]; blacklistedKernelModules = [ "netrom" "rose" "adfs" "affs" "bfs" "befs" "cramfs" "efs" "erofs" "exofs" "freevxfs" "f2fs" "hfs" "hpfs" "jfs" "minix" "nilfs2" "ntfs" "omfs" "qnx4" "qnx6" "sysv" "ufs" ]; kernel.sysctl = if config.monorepo.profiles.server.enable then { "net.ipv6.conf.${config.monorepo.profiles.server.interface}.autoconf" = 0; "net.ipv6.conf.${config.monorepo.profiles.server.interface}.accept_ra" = 1; } else { "kernel.ftrace_enabled" = false; "net.core.bpf_jit_enable" = false; "kernel.kptr_restrict" = 2; # madaidan "kernel.smtcontrol" = "on"; "vm.swappiness" = 1; "vm.unprivileged_userfaultfd" = 0; "dev.tty.ldisc_autoload" = 0; "kernel.kexec_load_disabled" = 1; "kernel.sysrq" = 4; "kernel.perf_event_paranoid" = 3; # net "net.ipv4.ip_forward" = 1; "net.ipv4.icmp_echo_ignore_broadcasts" = true; }; }; networking = { interfaces = lib.mkIf config.monorepo.profiles.server.enable { "${config.monorepo.profiles.server.interface}" = { ipv4.addresses = [ { address = config.monorepo.profiles.server.ipv4; prefixLength = 24; } ]; ipv6.addresses = [ { address = config.monorepo.profiles.server.ipv6; prefixLength = 64; } ]; useDHCP = lib.mkForce false; }; }; defaultGateway = lib.mkIf config.monorepo.profiles.server.enable config.monorepo.profiles.server.gateway; useDHCP = false; tempAddresses = lib.mkIf config.monorepo.profiles.server.enable "disabled"; extraHosts = lib.mkIf config.monorepo.profiles.server.enable (lib.concatStringsSep "\n" prodHosts); domains = lib.mkIf config.monorepo.profiles.server.enable { enable = true; baseDomains = { "${config.monorepo.vars.remoteHost}" = { a.data = config.monorepo.profiles.server.ipv4; aaaa.data = config.monorepo.profiles.server.ipv6; }; "${config.monorepo.vars.orgHost}" = { a.data = config.monorepo.profiles.server.ipv4; aaaa.data = config.monorepo.profiles.server.ipv6; txt = { data = "v=spf1 ip4:${config.monorepo.profiles.server.ipv4} ip6:${config.monorepo.profiles.server.ipv6} -all"; }; }; }; }; nameservers = [ "8.8.8.8" "1.1.1.1"]; dhcpcd.enable = (! config.monorepo.profiles.server.enable); networkmanager = { enable = lib.mkForce (! config.monorepo.profiles.server.enable); # rpis need network wifi = { powersave = false; }; ensureProfiles = { profiles = { home-wifi = { connection = { id = "TELUS6572"; permissions = ""; type = "wifi"; }; ipv4 = { dns-search = ""; method = "auto"; }; ipv6 = { addr-gen-mode = "stable-privacy"; dns-search = ""; method = "auto"; }; wifi = { mac-address-blacklist = ""; mode = "infrastructure"; ssid = "TELUS6572"; }; wifi-security = { auth-alg = "open"; key-mgmt = "wpa-psk"; # when someone actually steals my internet then I will be concerned. # This password only matters if you actually show up to my house in real life. # That would perhaps allow for some nasty networking related shenanigans. # I guess we'll cross that bridge when I get there. psk = "b4xnrv6cG6GX"; }; }; }; }; }; firewall = { allowedTCPPorts = [ 22 11434 ]; allowedUDPPorts = [ ]; }; }; hardware = { wirelessRegulatoryDatabase = true; enableAllFirmware = true; cpu.intel.updateMicrocode = true; graphics.enable = ! config.monorepo.profiles.ttyonly.enable; bluetooth = { enable = lib.mkDefault config.monorepo.profiles.desktop.enable; powerOnBoot = lib.mkDefault config.monorepo.profiles.desktop.enable; }; }; services = { pulseaudio.enable = ! config.monorepo.profiles.pipewire.enable; chrony = { enable = true; enableNTS = true; servers = [ "time.cloudflare.com" "ptbtime1.ptb.de" "ptbtime2.ptb.de" ]; }; jitterentropy-rngd.enable = true; resolved.settings.Resolve.DNSSEC = true; usbguard.enable = false; dbus.apparmor = "enabled"; # Misc. udev = { extraRules = ''''; packages = if config.monorepo.profiles.workstation.enable then with pkgs; [ platformio-core platformio-core.udev openocd ] else []; }; printing.enable = lib.mkDefault config.monorepo.profiles.workstation.enable; udisks2.enable = (! config.monorepo.profiles.ttyonly.enable); }; programs = { nix-ld.enable = true; zsh.enable = true; light.enable = true; ssh.enableAskPassword = false; }; security = { acme = { acceptTerms = true; defaults.email = "${config.monorepo.vars.internetName}@gmail.com"; }; apparmor = { enable = true; killUnconfinedConfinables = true; packages = with pkgs; [ apparmor-profiles ]; }; pam.loginLimits = [ { domain = "*"; item = "nofile"; type = "-"; value = "32768"; } { domain = "*"; item = "memlock"; type = "-"; value = "32768"; } ]; rtkit.enable = true; lockKernelModules = true; protectKernelImage = true; allowSimultaneousMultithreading = true; forcePageTableIsolation = true; tpm2 = { enable = true; pkcs11.enable = true; tctiEnvironment.enable = true; }; auditd.enable = true; audit.enable = true; chromiumSuidSandbox.enable = (! config.monorepo.profiles.ttyonly.enable); sudo.enable = true; }; xdg.portal = { enable = (! config.monorepo.profiles.ttyonly.enable); wlr.enable = (! config.monorepo.profiles.ttyonly.enable); extraPortals = with pkgs; if (! config.monorepo.profiles.ttyonly.enable) then [ xdg-desktop-portal-gtk xdg-desktop-portal xdg-desktop-portal-hyprland ] else []; config.common.default = "*"; }; environment.etc."gitconfig".text = '' [init] defaultBranch = main ''; environment.extraInit = '' umask 0022 ''; environment.systemPackages = with pkgs; [ restic sbctl gitFull git-lfs git-lfs-transfer vim curl nmap exiftool (writeShellScriptBin "new-repo" '' #!/bin/bash cd ${config.users.users.git.home} git init --bare "$1" vim "$1/description" chown -R git:git "$1" '' ) ]; users.groups = lib.genAttrs userGroups (_: lib.mkDefault {}); users.users = lib.genAttrs userGroups (name: { isSystemUser = lib.mkDefault true; group = "${name}"; extraGroups = [ "acme" "nginx" ]; }) // { conduit = { isSystemUser = lib.mkDefault true; group = "conduit"; extraGroups = []; }; matterbridge = { isSystemUser = lib.mkDefault true; group = "matterbridge"; extraGroups = []; }; public-inbox = { isSystemUser = lib.mkDefault true; group = "public-inbox"; extraGroups = [ "acme" "nginx" "git" ]; }; ircd = { isSystemUser = lib.mkDefault true; group = "ircd"; home = "/home/ircd"; }; nginx = { group = "nginx"; isSystemUser = lib.mkDefault true; extraGroups = [ "acme" ]; }; root.openssh.authorizedKeys.keys = [ config.monorepo.vars.sshKey ]; git = { isSystemUser = true; home = "/srv/git"; shell = "/bin/sh"; group = "git"; openssh.authorizedKeys.keys = [ config.monorepo.vars.sshKey "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIEF+mcL9nDkzVhCYyYWCIrP+b6oRiiaV509jywbD0Vq nix-on-droid@localhost" "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCedJm0yYB0qLah/Y7PqLVgNh6qp+yujssGtuR05KbZLzSnsLUjUMObMyjFB9xTKrSGDqyoMkNe2l5VXMBJ9wBKLbzqMWbkakAWOj7EC/qZ6dFWA075mniwAuWKY/Q8QYohAJbbeU4j0ObWrltd4ar2Ac9vsVyftYF5efg8PEqVdOxzrBn5taY1zCCRjee5ISeRDIovnBbq7x86jsx5VnXTjMN9FZCI2qmz992Sg/PPXpXat+O1YQlG0eBHEny2Ug9gaAYnGOVr6kZKE4lrjz47nrXVXO6lJsNXmuzTVnEgo30DAA3dV4fws/M5ptM5Pgg2qe94HyHWhhmtXOekWmGtP3YxpVe3M/SPl31UL570ZDuuCcpJTsbe90ZyXC3CiSJkLKbmFkfOgZ6DI2LT8KSp09/2NCtZYriLN/nXObn6gQzByGMxVyKNx2hh8ENt9hzTCAk5lYDK3g3wS8eLCY3EH/caEqT9mLZEZeRHtAhtfozo1VJL7sSZ0Zm7wiIxHylwOshh1sYI1gb1MgMqNnrr1t8+8UK+Q0NERQW3yiphG36HXWy/DdCG0EF+N850KbgH1FFur+m+3hZCZCFVp3tGCcOC+bxWMBT3+9yC6LARi5cFjLQaWLsNO5xEs4vqX3+s3QjJ0pAYDkgtoeY2Fbh+imN+JasWn/cSy5p3UdE4ZQ== andrei@kiss" ]; }; "${config.monorepo.vars.userName}" = { openssh.authorizedKeys.keys = [ config.monorepo.vars.sshKey ]; linger = true; initialPassword = "${config.monorepo.vars.userName}"; isNormalUser = true; description = config.monorepo.vars.fullName; extraGroups = [ "networkmanager" "wheel" "video" "docker" "jackaudio" "tss" "dialout" "docker" "plugdev" ]; shell = pkgs.zsh; packages = []; }; }; nix = { settings = { keep-outputs = true; keep-derivations = true; auto-optimise-store = true; max-jobs = 4; cores = 0; substituters = [ "https://cache.nixos-cuda.org" ]; trusted-public-keys = [ "cache.nixos-cuda.org:74DUi4Ye579gUqzH4ziL9IyiJBlDpMRn9MBN8oNan9M=" ]; experimental-features = "nix-command flakes ca-derivations"; trusted-users = [ "@wheel" ]; }; gc.automatic = true; }; time.timeZone = config.monorepo.vars.timeZone; i18n.defaultLocale = "en_CA.UTF-8"; system.stateVersion = "24.11"; } #+end_src ** Disko This is the disko configuration for my continuity system. It features a boot and ext4 partition, with configurable disk. *** GPT Common This is all configuration common to any GPT partitioned drive. I dynamically choose the partitioning scheme based on the options set. #+begin_src nix :tangle ../nix/disko/gpt-common.nix { config, ... }: let matchSd = builtins.match "/dev/mmcblk[0-9]+" config.monorepo.vars.device != null; partitions = if ((builtins.match "/dev/vd[a-z]+" config.monorepo.vars.device) != null) then (import ./virtual-machine.nix) else (if matchSd then (import ./sd-card.nix) else (import (./. + "/${config.monorepo.vars.fileSystem}.nix"))); spec = { disko.devices = { disk = { main = { type = "disk"; device = config.monorepo.vars.device; content = { type = if matchSd then "mbr" else "gpt"; inherit partitions; }; }; }; }; }; in { monorepo.vars.diskoSpec = spec; disko.devices = spec.disko.devices; } #+end_src *** ESP Boot Partition #+begin_src nix :tangle ../nix/disko/esp-boot.nix { type = "filesystem"; format = "vfat"; mountpoint = "/boot"; mountOptions = [ "umask=0077" ]; } #+end_src *** Btrfs This is a fully featured drive configuration and the recommended configuration to install if on a workstation or laptop. Btrfs enables you to enable impermanence and also encrypt the drive with ~/tmp/secret.key~. #+begin_src nix :tangle ../nix/disko/btrfs.nix { ESP = { size = "512M"; type = "EF00"; content = import ./esp-boot.nix; }; luks = { size = "100%"; content = { type = "luks"; name = "crypted"; passwordFile = "/tmp/secret.key"; content = { type = "btrfs"; extraArgs = [ "-f" ]; subvolumes = { "/root" = { mountpoint = "/"; mountOptions = [ "compress=zstd" "noatime" ]; }; "/home" = { mountpoint = "/home"; mountOptions = [ "compress=zstd" "noatime" ]; }; "/nix" = { mountpoint = "/nix"; mountOptions = [ "compress=zstd" "noatime" ]; }; "/persistent" = { mountpoint = "/persistent"; mountOptions = [ "compress=zstd" "noatime" ]; }; }; }; }; }; } #+end_src *** Ext4 This configuration is used for simple partitioning schemes with EFI. A simple ext4 disk with no encryption or any fancy features. You should be using EFI if you can. #+begin_src nix :tangle ../nix/disko/ext4.nix { ESP = { type = "EF00"; size = "500M"; priority = 1; content = import ./esp-boot.nix; }; root = { size = "100%"; priority = 2; content = { type = "filesystem"; format = "ext4"; mountpoint = "/"; }; }; } #+end_src *** Virtual Machine This configuration is meant for virtual machines where BIOS is the only option. #+begin_src nix :tangle ../nix/disko/virtual-machine.nix { boot = { size = "1M"; type = "EF02"; }; root = { label = "disk-main-root"; size = "100%"; content = { type = "filesystem"; format = "ext4"; mountpoint = "/"; }; }; } #+end_src *** TODO SD Card #+begin_src nix :tangle ../nix/disko/sd-card.nix { boot = {}; root = {}; } #+end_src ** Home *** Default Home Profile As you can see, I have my installed home packages installed based on the profiles enabled. Also, I have many imports that we'll go through next. #+begin_src nix :tangle ../nix/modules/home/default.nix { lib, config, pkgs, sops-nix, super, ... }: { imports = [ sops-nix.homeManagerModules.sops ../vars.nix ./fcitx.nix ./emacs.nix ./firefox.nix ./git.nix ./hyprland.nix ./mpv.nix ./yt-dlp.nix ./wofi.nix ./kitty.nix ./waybar.nix ./zsh.nix ./mbsync.nix ./msmtp.nix ./gammastep.nix ./mpd.nix ./mako.nix ./user.nix ./gtk.nix ./secrets.nix ./pantalaimon.nix ]; options = { monorepo.profiles = { enable = lib.mkEnableOption "Enables home manager desktop configuration"; # Programs graphics.enable = lib.mkEnableOption "Enables graphical programs for user"; lang-c.enable = lib.mkEnableOption "Enables C language support"; lang-sh.enable = lib.mkEnableOption "Enables sh language support"; lang-rust.enable = lib.mkEnableOption "Enables Rust language support"; lang-python.enable = lib.mkEnableOption "Enables python language support"; lang-sol.enable = lib.mkEnableOption "Enables solidity language support"; lang-openscad.enable = lib.mkEnableOption "Enables openscad language support"; lang-js.enable = lib.mkEnableOption "Enables javascript language support"; lang-nix.enable = lib.mkEnableOption "Enables nix language support"; lang-idris.enable = lib.mkEnableOption "Enables idris language support"; lang-agda.enable = lib.mkEnableOption "Enables agda language support"; lang-coq.enable = lib.mkEnableOption "Enables coq language support"; lang-lean.enable = lib.mkEnableOption "Enables lean language support"; lang-haskell.enable = lib.mkEnableOption "Enables haskell language support"; crypto.enable = lib.mkEnableOption "Enables various cryptocurrency wallets"; art.enable = lib.mkEnableOption "Enables various art programs"; music.enable = lib.mkEnableOption "Enables mpd"; workstation.enable = lib.mkEnableOption "Enables workstation packages (music production and others)"; cuda.enable = lib.mkEnableOption "Enables CUDA user package builds"; hyprland.enable = lib.mkEnableOption "Enables hyprland"; email.enable = lib.mkEnableOption "Enables email"; }; }; config = { home.packages = (if config.monorepo.profiles.email.enable then [ pkgs.mu ] else []) ++ (if config.monorepo.profiles.lang-c.enable then (with pkgs; [ autobuild clang gdb gnumake bear clang-tools autotools-language-server ]) else []) ++ (if config.monorepo.profiles.workstation.enable then (with pkgs; [ mumble ]) else []) ++ (if config.monorepo.profiles.lang-js.enable then (with pkgs; [ nodejs bun yarn typescript typescript-language-server vscode-langservers-extracted ]) else []) ++ (if config.monorepo.profiles.lang-rust.enable then (with pkgs; [ cargo rust-analyzer rustfmt ]) else []) ++ (if config.monorepo.profiles.lang-python.enable then (with pkgs; [ poetry python3 python314Packages.python-lsp-server ]) else []) ++ (if config.monorepo.profiles.lang-sol.enable then (with pkgs; [ solc ]) else []) ++ (if config.monorepo.profiles.lang-openscad.enable then (with pkgs; [ openscad openscad-lsp ]) else []) ++ (if config.monorepo.profiles.lang-sh.enable then (with pkgs; [ bash-language-server ]) else []) ++ (if config.monorepo.profiles.lang-haskell.enable then (with pkgs; [ haskell-language-server haskellPackages.hlint ghc ]) else []) ++ (if config.monorepo.profiles.lang-coq.enable then (with pkgs; [ coq ]) else []) ++ (if config.monorepo.profiles.lang-lean.enable then (with pkgs; [ lean4 ]) else []) ++ (if config.monorepo.profiles.lang-agda.enable then (with pkgs; [ agda ]) else []) ++ (if config.monorepo.profiles.lang-idris.enable then (with pkgs; [ idris idris2Packages.idris2Lsp ]) else []) ++ (if config.monorepo.profiles.lang-nix.enable then (with pkgs; [ nil nixd nixfmt nix-prefetch-scripts ]) else []) ++ (if config.monorepo.profiles.crypto.enable then (with pkgs; [ bitcoin monero-cli monero-gui ]) else []) ++ (if config.monorepo.profiles.art.enable then (with pkgs; [ inkscape krita ]) else []) ++ (if config.monorepo.profiles.music.enable then (with pkgs; [ mpc sox ]) else []) ++ (if config.monorepo.profiles.workstation.enable then (with pkgs; [ alsa-utils alsa-scarlett-gui ardour audacity blender foxdot fluidsynth qjackctl qsynth qpwgraph imagemagick supercollider inkscape kdePackages.kdenlive kicad ]) else []); monorepo.profiles = { enable = lib.mkDefault super.monorepo.profiles.home.enable; music.enable = lib.mkDefault config.monorepo.profiles.enable; email.enable = lib.mkDefault config.monorepo.profiles.enable; cuda.enable = lib.mkDefault super.monorepo.profiles.cuda.enable; # Programming graphics.enable = lib.mkDefault (! super.monorepo.profiles.ttyonly.enable); hyprland.enable = lib.mkDefault config.monorepo.profiles.graphics.enable; lang-c.enable = lib.mkDefault config.monorepo.profiles.enable; lang-rust.enable = lib.mkDefault config.monorepo.profiles.enable; lang-python.enable = lib.mkDefault config.monorepo.profiles.enable; lang-sol.enable = lib.mkDefault config.monorepo.profiles.enable; lang-sh.enable = lib.mkDefault config.monorepo.profiles.enable; lang-openscad.enable = lib.mkDefault config.monorepo.profiles.enable; lang-js.enable = lib.mkDefault config.monorepo.profiles.enable; lang-nix.enable = lib.mkDefault config.monorepo.profiles.enable; lang-coq.enable = lib.mkDefault config.monorepo.profiles.enable; lang-lean.enable = lib.mkDefault config.monorepo.profiles.enable; lang-haskell.enable = lib.mkDefault config.monorepo.profiles.enable; lang-idris.enable = lib.mkDefault config.monorepo.profiles.enable; lang-agda.enable = lib.mkDefault config.monorepo.profiles.enable; crypto.enable = lib.mkDefault config.monorepo.profiles.enable; art.enable = lib.mkDefault config.monorepo.profiles.enable; workstation.enable = lib.mkDefault super.monorepo.profiles.workstation.enable; }; }; } #+end_src *** Secrets These are some secrets that I use regularly for my programs in home. #+begin_src nix :tangle ../nix/modules/home/secrets.nix { config, super, ... }: { sops = { defaultSopsFile = if config.monorepo.profiles.graphics.enable then ../../secrets/secrets.yaml else ../../secrets/vps_secrets.yaml; age = { keyFile = "/home/${super.monorepo.vars.userName}/.config/sops/age/keys.txt"; }; secrets = if super.monorepo.profiles.desktop.enable then { mail = { format = "yaml"; path = "${config.sops.defaultSymlinkPath}/mail"; }; cloudflare-dns = { format = "yaml"; path = "${config.sops.defaultSymlinkPath}/cloudflare-dns"; }; digikey = { format = "yaml"; path = "${config.sops.defaultSymlinkPath}/digikey"; }; dn42 = { format = "yaml"; path = "${config.sops.defaultSymlinkPath}/dn42"; }; ntfy = { format = "yaml"; path = "${config.sops.defaultSymlinkPath}/${super.monorepo.vars.ntfySecret}"; sopsFile = ../../secrets/common_secrets.yaml; }; } else { }; defaultSymlinkPath = "/run/user/1000/secrets"; defaultSecretsMountPoint = "/run/user/1000/secrets.d"; }; } #+end_src *** Firefox I conditionally enable metamask based on the cryptocurrency option. Everything else here should be straightforward. #+begin_src nix :tangle ../nix/modules/home/firefox.nix { lib, config, pkgs, ... }: { programs.librewolf = { enable = lib.mkDefault config.monorepo.profiles.graphics.enable; package = pkgs.librewolf; profiles = { default = { id = 0; name = "default"; isDefault = true; extensions.packages = with pkgs.nur.repos.rycee.firefox-addons; [ ublock-origin tree-style-tab firefox-color vimium privacy-redirect ] ++ (lib.optional config.monorepo.profiles.crypto.enable pkgs.nur.repos.rycee.firefox-addons.metamask); settings = { "privacy.resistFingerprinting.letterboxing" = true; }; }; }; }; } #+end_src *** Fcitx This is a virtual keyboard program for writing in multiple languages. I use this sometimes. #+begin_src nix :tangle ../nix/modules/home/fcitx.nix { config, pkgs, lib, ... }: { i18n.inputMethod = { type = "fcitx5"; enable = lib.mkDefault config.monorepo.profiles.graphics.enable; fcitx5.addons = if config.monorepo.profiles.graphics.enable then (with pkgs; [ fcitx5-gtk qt6Packages.fcitx5-chinese-addons qt6Packages.fcitx5-configtool fcitx5-mozc fcitx5-rime ]) else []; }; } #+end_src Note that I configure fcitx with chinese and some japanese input enabled. *** Emacs I install all my emacs packages within Nix so that they build deterministically with native compilation, and because I can fetch their exact versions. Note that I have a stub configuration here that tells emacs to load my real configuration at ~~/monorepo/config/emacs.org~ as an org file which gets automatically tangled to an emacs-lisp file. #+begin_src nix :tangle ../nix/modules/home/emacs.nix { lib, config, pkgs, super, ... }: { programs.emacs = { enable = lib.mkDefault config.monorepo.profiles.graphics.enable; package = pkgs.emacs-pgtk; extraConfig = '' (setq debug-on-error t) (setq system-email "${super.monorepo.vars.email}") (setq system-username "${super.monorepo.vars.internetName}") (setq system-fullname "${super.monorepo.vars.fullName}") (setq system-gpgkey "${super.monorepo.vars.gpgKey}") (load "${pkgs.writeText "init.el" (builtins.readFile ../../init.el)}") ''; extraPackages = import ./emacs-packages.nix; }; } #+end_src **** Emacs Packages I want to separate out these packages so that my parent flake which builds my website has a list of my packages. #+begin_src nix :tangle ../nix/modules/home/emacs-packages.nix epkgs: [ epkgs.agda2-mode epkgs.all-the-icons epkgs.auctex epkgs.catppuccin-theme epkgs.company epkgs.company-solidity epkgs.counsel epkgs.centaur-tabs epkgs.dashboard epkgs.doom-themes epkgs.doom-modeline epkgs.irony epkgs.elfeed epkgs.elfeed-org epkgs.elfeed-tube epkgs.elfeed-tube-mpv epkgs.elpher epkgs.ement epkgs.emmet-mode epkgs.emms epkgs.enwc epkgs.evil epkgs.evil-collection epkgs.evil-commentary epkgs.evil-org epkgs.f epkgs.flycheck epkgs.general epkgs.gptel epkgs.gruvbox-theme epkgs.haskell-mode epkgs.htmlize epkgs.idris-mode epkgs.irony-eldoc epkgs.ivy epkgs.ivy-pass epkgs.kiwix epkgs.latex-preview-pane epkgs.lsp-ivy epkgs.lsp-mode epkgs.lsp-haskell epkgs.lyrics-fetcher epkgs.mastodon epkgs.magit epkgs.magit-delta epkgs.mu4e epkgs.minuet epkgs.nix-mode epkgs.org-contrib epkgs.org-fragtog epkgs.org-journal epkgs.org-roam epkgs.org-roam-ui epkgs.org-superstar epkgs.page-break-lines epkgs.password-store epkgs.pdf-tools epkgs.pinentry epkgs.platformio-mode epkgs.projectile epkgs.rustic epkgs.scad-mode epkgs.simple-httpd epkgs.solidity-flycheck epkgs.solidity-mode epkgs.sudo-edit epkgs.telega epkgs.treemacs epkgs.treemacs-evil epkgs.treemacs-magit epkgs.treemacs-projectile epkgs.treesit-auto epkgs.typescript-mode epkgs.unicode-fonts epkgs.use-package epkgs.vterm epkgs.wgrep epkgs.web-mode epkgs.websocket epkgs.which-key epkgs.writegood-mode epkgs.writeroom-mode epkgs.yaml-mode epkgs.yasnippet epkgs.yasnippet-snippets ] #+end_src *** Gammastep This is a program like redshift for making your screen emit more red and less blue light. Here I have the long and lat set for Vancouver, but you should replace it if you live outside the timezone. #+begin_src nix :tangle ../nix/modules/home/gammastep.nix { lib, config, ... }: { services.gammastep = { enable = lib.mkDefault config.monorepo.profiles.graphics.enable; provider = "manual"; latitude = 49.282730; longitude = -123.120735; temperature = { day = 5000; night = 3000; }; settings = { general = { adjustment-method = "wayland"; }; }; }; } #+end_src *** Git My git configuration uses information set in the ~vars.nix~ in order to set configuration options. Make sure those are set correctly. I've set it to sign by default. #+begin_src nix :tangle ../nix/modules/home/git.nix { pkgs, lib, config, super, ... }: { programs.git = { enable = lib.mkDefault config.monorepo.profiles.graphics.enable; package = pkgs.gitFull; lfs.enable = lib.mkDefault config.monorepo.profiles.graphics.enable; userName = super.monorepo.vars.fullName; userEmail = "${super.monorepo.vars.email}"; signing = { key = super.monorepo.vars.gpgKey; signByDefault = true; }; extraConfig = { init.defaultBranch = "main"; credential."mail.${super.monorepo.vars.orgHost}" = { username = "${super.monorepo.vars.email}"; helper = "!f() { test \"$1\" = get && echo \"password=$(cat /run/user/1000/secrets/mail)\"; }; f"; }; sendemail = { smtpserver = "mail.${super.monorepo.vars.orgHost}"; smtpuser = "${super.monorepo.vars.email}"; smtpserverport = 465; smtpencryption = "ssl"; }; }; aliases = { pl = "pull"; ps = "push"; co = "checkout"; c = "commit"; a = "add"; st = "status"; sw = "switch"; b = "branch"; }; }; } #+end_src *** Hyprland My compositor/window manager. This automatically starts on startup. Instructions on how to use this component will come soon. #+begin_src nix :tangle ../nix/modules/home/hyprland.nix { lib, config, wallpapers, pkgs, scripts, ... }: { wayland.windowManager.hyprland = { enable = lib.mkDefault config.monorepo.profiles.hyprland.enable; package = pkgs.hyprland; xwayland.enable = true; systemd.enable = true; settings = { "$mod" = "SUPER"; bezier = [ "overshot, 0.05, 0.9, 0.1, 1.05" ]; animation = [ # "workspaces, 1, 10, overshot" "windows, 1, 2, default" "workspaces, 1, 2, default, slidefade 20%" ]; exec-once = [ "waybar" "swww-daemon --format xrgb" "sh -c 'swww img \"$(find ${wallpapers} -type f \\( -iname \"*.jpg\" -o -iname \"*.png\" \\) | shuf -n1)\"'" "fcitx5-remote -r" "fcitx5 -d --replace" "fcitx5-remote -r" "emacs" "librewolf" ]; env = [ "LIBVA_DRIVER_NAME,nvidia" "XDG_SESSION_TYPE,wayland" "GBM_BACKEND,nvidia-drm" "__GLX_VENDOR_LIBRARY_NAME,nvidia" "ELECTRON_OZONE_PLATFORM_HINT,auto" ]; monitor = [ "DP-4,2560x1440@165.000000,0x0,1" "Unknown-1,disable" ]; layerrule = [ { name = "waybar blur"; "match:namespace" = "waybar"; blur = "on"; } ]; windowrule = [ { name = "emacs"; "match:class" = "emacs"; workspace = 1; } { name = "librewolf"; "match:class" = "librewolf"; workspace = 2; } { name = "chromium-browser"; "match:class" = "chromium-browser"; workspace = 2; } { name = "signal"; "match:class" = "signal"; workspace = 3; } { name = "Element"; "match:class" = "Element"; workspace = 3; } { name = "pavucontrol"; "match:class" = "pavucontrol"; workspace = 4; } { name = "qpwgraph"; "match:class" = "qpwgraph"; workspace = 4; } { name = "mpv"; "match:class" = "mpv"; workspace = 4; } ]; bind = [ "$mod, F, exec, librewolf" "$mod, Return, exec, kitty" "$mod, E, exec, emacs" "$mod, B, exec, bitcoin-qt" "$mod, S, exec, pavucontrol" "$mod, M, exec, monero-wallet-gui" "$mod, V, exec, element-desktop" "$mod, C, exec, signal-desktop" "$mod, D, exec, wofi --show run" "$mod, P, exec, bash ${scripts}/powermenu.sh" "$mod, Q, killactive" "$mod SHIFT, H, movewindow, l" "$mod SHIFT, L, movewindow, r" "$mod SHIFT, K, movewindow, u" "$mod SHIFT, J, movewindow, d" "$mod SHIFT, T, togglefloating" "$mod SHIFT, F, fullscreen" "$mod, H, movefocus, l" "$mod, L, movefocus, r" "$mod, K, movefocus, u" "$mod, J, movefocus, d" ", XF86AudioPlay, exec, mpc toggle" ", Print, exec, grim" "$mod, right, resizeactive, 30 0" "$mod, left, resizeactive, -30 0" "$mod, up, resizeactive, 0 -30" "$mod, down, resizeactive, 0 30" ] ++ ( builtins.concatLists (builtins.genList ( x: let ws = let c = (x + 1) / 10; in builtins.toString (x + 1 - (c * 10)); in [ "$mod, ${ws}, workspace, ${toString (x + 1)}" "$mod SHIFT, ${ws}, movetoworkspace, ${toString (x + 1)}" ] ) 10) ); bindm = [ "$mod, mouse:272, movewindow" "$mod, mouse:273, resizewindow" "$mod ALT, mouse:272, resizewindow" ]; binde = [ ", XF86AudioRaiseVolume, exec, wpctl set-volume -l 1.5 @DEFAULT_AUDIO_SINK@ 5%+" ", XF86AudioLowerVolume, exec, wpctl set-volume -l 1.5 @DEFAULT_AUDIO_SINK@ 5%-" ", XF86AudioNext, exec, mpc next" ", XF86AudioPrev, exec, mpc prev" ", XF86MonBrightnessUp , exec, xbacklight -inc 10" ", XF86MonBrightnessDown, exec, xbacklight -dec 10" ]; decoration = { blur = { enabled = true; size = 9; passes = 4; contrast = 0.8; brightness = 1.1; noise = 0.02; new_optimizations = true; ignore_opacity = true; xray = false; }; rounding = 5; }; input = { scroll_method = "on_button_down"; scroll_button = 276; sensitivity = -0.5; kb_options = "caps:swapescape"; repeat_delay = 300; repeat_rate = 50; natural_scroll = false; touchpad = { natural_scroll = true; disable_while_typing = true; tap-to-click = true; }; }; cursor = { no_hardware_cursors = true; }; misc = { force_default_wallpaper = 0; disable_hyprland_logo = true; }; }; }; } #+end_src *** Kitty I've set my terminal, kitty, to use catppuccin colors. #+begin_src nix :tangle ../nix/modules/home/kitty.nix { lib, config, ... }: { programs.kitty = { enable = lib.mkDefault (config.monorepo.profiles.hyprland.enable && config.monorepo.profiles.graphics.enable); settings = { enable_audio_bell = false; font_family = "Iosevka Nerd Font"; font_size = 14; confirm_os_window_close = 0; background_opacity = "0.7"; # Catppuccin theme foreground = "#cdd6f4"; background = "#1e1e2e"; selection_foreground = "#1e1e2e"; selection_background = "#f5e0dc"; cursor = "#f5e0dc"; cursor_text_color = "#1e1e2e"; url_color = "#f5e0dc"; active_border_color = "#B4BEFE"; inactive_border_color = "#6C7086"; bell_border_color = "#F9E2AF"; wayland_titlebar_color = "#1E1E2E"; macos_titlebar_color = "#1E1E2E"; active_tab_foreground = "#11111B"; active_tab_background = "#CBA6F7"; inactive_tab_foreground = "#CDD6F4"; inactive_tab_background = "#181825"; tab_bar_background = "#11111B"; mark1_foreground = "#1E1E2E"; mark1_background = "#B4BEFE"; mark2_foreground = "#1E1E2E"; mark2_background = "#CBA6F7"; mark3_foreground = "#1E1E2E"; mark3_background = "#74C7EC"; color0 = "#45475A"; color8 = "#585B70"; color1 = "#F38BA8"; color9 = "#F38BA8"; color2 = "#A6E3A1"; color10 = "#A6E3A1"; color3 = "#F9E2AF"; color11 = "#F9E2AF"; color4 = "#89B4FA"; color12 = "#89B4FA"; color5 = "#F5C2E7"; color13 = "#F5C2E7"; color6 = "#94E2D5"; color14 = "#94E2D5"; color7 = "#BAC2DE"; color15 = "#A6ADC8"; }; }; } #+end_src *** Mako This is my notification system. My flake automatically fetches the notification sound, so you are all set from the get-go! #+begin_src nix :tangle ../nix/modules/home/mako.nix { lib, config, sounds, ... }: { services.mako = { enable = lib.mkDefault config.monorepo.profiles.graphics.enable; settings = { on-notify = "exec mpv ${sounds}/polite.ogg --no-config --no-video"; background-color = "#11111bf8"; text-color = "#cdd6f4"; border-color = "#89b4faff"; border-radius = 1; font = "Fira Code 10"; default-timeout = 3000; }; }; } #+end_src *** Mbsync Note that in order to use my email configuration, your imaps and smtps servers must be encrypted. This module uses the ~vars.nix~ as well as the home ~default.nix~ options. #+begin_src nix :tangle ../nix/modules/home/mbsync.nix { lib, config, super, ... }: { programs.mbsync = { enable = lib.mkDefault config.monorepo.profiles.email.enable; extraConfig = '' IMAPAccount ${super.monorepo.vars.internetName} Host mail.${super.monorepo.vars.orgHost} User ${super.monorepo.vars.email} PassCmd "cat ${config.sops.secrets.mail.path}" Port 993 TLSType IMAPS AuthMechs * CertificateFile /etc/ssl/certs/ca-certificates.crt IMAPStore ${super.monorepo.vars.internetName}-remote Account ${super.monorepo.vars.internetName} MaildirStore ${super.monorepo.vars.internetName}-local Path ~/email/${super.monorepo.vars.internetName}/ Inbox ~/email/${super.monorepo.vars.internetName}/INBOX SubFolders Verbatim Channel ${super.monorepo.vars.internetName} Far :${super.monorepo.vars.internetName}-remote: Near :${super.monorepo.vars.internetName}-local: Patterns * Create Near Sync All Expunge None SyncState * ''; }; } #+end_src *** MSMTP This is the program I use to send email from emacs. It is really the same thing as above, just set the options to the ones you want in your system ~default.nix~. #+begin_src nix :tangle ../nix/modules/home/msmtp.nix { lib, config, super, ... }: { programs.msmtp = { enable = lib.mkDefault config.monorepo.profiles.email.enable; extraConfig = '' # Set default values for all following accounts. defaults auth on tls on tls_trust_file /etc/ssl/certs/ca-certificates.crt tls_certcheck off logfile ~/.msmtp.log # Gmail account ${super.monorepo.vars.internetName} host mail.${super.monorepo.vars.orgHost} port 587 from ${super.monorepo.vars.email} user ${super.monorepo.vars.email} passwordeval "cat ${config.sops.secrets.mail.path}" # Set a default account account default : ${super.monorepo.vars.internetName} ''; }; } #+end_src *** Mpd This mpd configuration uses pipewire by default, and it should just work if you place music in the ~~/music~ directory and then run ~mpc add /~ afterwards. #+begin_src nix :tangle ../nix/modules/home/mpd.nix { lib, config, ... }: { services.mpd = { enable = lib.mkDefault config.monorepo.profiles.music.enable; dbFile = "/home/${config.monorepo.vars.userName}/.config/mpd/db"; dataDir = "/home/${config.monorepo.vars.userName}/.config/mpd/"; network.port = 6600; musicDirectory = "/home/${config.monorepo.vars.userName}/music"; playlistDirectory = "/home/${config.monorepo.vars.userName}/.config/mpd/playlists"; network.listenAddress = "0.0.0.0"; extraConfig = '' audio_output { type "pipewire" name "pipewire output" } audio_output { type "httpd" name "Ret2pop's Music Stream" encoder "opus" port "8000" bitrate "128000" format "48000:16:1" always_on "yes" tags "yes" } audio_output { type "shout" name "My VPS Stream" host "127.0.0.1" port "8888" mount "/stream" password "SuperSecretSourcePass" bitrate "128" format "44100:16:2" protocol "icecast2" user "source" description "My MPD Stream" genre "Mixed" } ''; }; } #+end_src *** MPV I have some emacs + yt-dlp integrations with mpv with my rss feed, and therefore we need it here: #+begin_src nix :tangle ../nix/modules/home/mpv.nix { lib, config, ... }: { programs.mpv = { enable = lib.mkDefault config.monorepo.profiles.graphics.enable; config = { profile = "gpu-hq"; force-window = true; ytdl-format = "bestvideo+bestaudio"; cache-default = 4000000; }; }; } #+end_src *** GTK #+begin_src nix :tangle ../nix/modules/home/gtk.nix { lib, config, pkgs, ... }: { config = lib.mkIf config.monorepo.profiles.graphics.enable { gtk = { theme = { name = "catppuccin-mocha-pink-standard"; package = pkgs.catppuccin-gtk.override { variant = "mocha"; accents = [ "pink" ]; }; }; }; xdg.configFile = { "gtk-4.0/assets".source = "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-4.0/assets"; "gtk-4.0/gtk.css".source = "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-4.0/gtk.css"; "gtk-4.0/gtk-dark.css".source = "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-4.0/gtk-dark.css"; "gtk-3.0/gtk.css".source = "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-3.0/gtk.css"; "gtk-3.0/gtk-dark.css".source = "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-3.0/gtk-dark.css"; "gtk-3.0/settings.ini".text = '' [Settings] gtk-theme-name=${config.gtk.theme.name} gtk-application-prefer-dark-theme=1 ''; }; }; } #+end_src *** Waybar This is the bar I use for my hyprland configuration. You will need to adjust the monitors field in the ~default.nix~ for it to really appear. #+begin_src nix :tangle ../nix/modules/home/waybar.nix { lib, config, ... }: { programs.waybar = { enable = lib.mkDefault config.monorepo.profiles.hyprland.enable; style = '' ,* { border: none; border-radius: 0px; font-family: Iosevka Nerd Font, FontAwesome, Noto Sans CJK; font-size: 14px; font-style: normal; min-height: 0; } window#waybar { background: rgba(30, 30, 46, 0.5); border-bottom: 1px solid #45475a; color: #cdd6f4; } #workspaces { background: #45475a; margin: 5px 5px 5px 5px; padding: 0px 5px 0px 5px; border-radius: 16px; border: solid 0px #f4d9e1; font-weight: normal; font-style: normal; } #workspaces button { padding: 0px 5px; border-radius: 16px; color: #a6adc8; } #workspaces button.active { color: #f4d9e1; background-color: transparent; border-radius: 16px; } #workspaces button:hover { background-color: #cdd6f4; color: black; border-radius: 16px; } #custom-date, #clock, #battery, #pulseaudio, #network, #custom-randwall, #custom-launcher { background: transparent; padding: 5px 5px 5px 5px; margin: 5px 5px 5px 5px; border-radius: 8px; border: solid 0px #f4d9e1; } #custom-date { color: #D3869B; } #custom-power { color: #24283b; background-color: #db4b4b; border-radius: 5px; margin-right: 10px; margin-top: 5px; margin-bottom: 5px; margin-left: 0px; padding: 5px 10px; } #tray { background: #45475a; margin: 5px 5px 5px 5px; border-radius: 16px; padding: 0px 5px; /*border-right: solid 1px #282738;*/ } #clock { color: #cdd6f4; background-color: #45475a; border-radius: 0px 0px 0px 24px; padding-left: 13px; padding-right: 15px; margin-right: 0px; margin-left: 10px; margin-top: 0px; margin-bottom: 0px; font-weight: bold; /*border-left: solid 1px #282738;*/ } #battery { color: #89b4fa; } #battery.charging { color: #a6e3a1; } #battery.warning:not(.charging) { background-color: #f7768e; color: #f38ba8; border-radius: 5px 5px 5px 5px; } #backlight { background-color: #24283b; color: #db4b4b; border-radius: 0px 0px 0px 0px; margin: 5px; margin-left: 0px; margin-right: 0px; padding: 0px 0px; } #network { color: #f4d9e1; border-radius: 8px; margin-right: 5px; } #pulseaudio { color: #f4d9e1; border-radius: 8px; margin-left: 0px; } #pulseaudio.muted { background: transparent; color: #928374; border-radius: 8px; margin-left: 0px; } #custom-randwall { color: #f4d9e1; border-radius: 8px; margin-right: 0px; } #custom-launcher { color: #e5809e; background-color: #45475a; border-radius: 0px 24px 0px 0px; margin: 0px 0px 0px 0px; padding: 0 20px 0 13px; /*border-right: solid 1px #282738;*/ font-size: 20px; } #custom-launcher button:hover { background-color: #FB4934; color: transparent; border-radius: 8px; margin-right: -5px; margin-left: 10px; } #custom-playerctl { background: #45475a; padding-left: 15px; padding-right: 14px; border-radius: 16px; /*border-left: solid 1px #282738;*/ /*border-right: solid 1px #282738;*/ margin-top: 5px; margin-bottom: 5px; margin-left: 0px; font-weight: normal; font-style: normal; font-size: 16px; } #custom-playerlabel { background: transparent; padding-left: 10px; padding-right: 15px; border-radius: 16px; /*border-left: solid 1px #282738;*/ /*border-right: solid 1px #282738;*/ margin-top: 5px; margin-bottom: 5px; font-weight: normal; font-style: normal; } #window { background: #45475a; padding-left: 15px; padding-right: 15px; border-radius: 16px; /*border-left: solid 1px #282738;*/ /*border-right: solid 1px #282738;*/ margin-top: 5px; margin-bottom: 5px; font-weight: normal; font-style: normal; } #custom-wf-recorder { padding: 0 20px; color: #e5809e; background-color: #1E1E2E; } #cpu { background-color: #45475a; /*color: #FABD2D;*/ border-radius: 16px; margin: 5px; margin-left: 5px; margin-right: 5px; padding: 0px 10px 0px 10px; font-weight: bold; } #memory { background-color: #45475a; /*color: #83A598;*/ border-radius: 16px; margin: 5px; margin-left: 5px; margin-right: 5px; padding: 0px 10px 0px 10px; font-weight: bold; } #disk { background-color: #45475a; /*color: #8EC07C;*/ border-radius: 16px; margin: 5px; margin-left: 5px; margin-right: 5px; padding: 0px 10px 0px 10px; font-weight: bold; } #custom-hyprpicker { background-color: #45475a; /*color: #8EC07C;*/ border-radius: 16px; margin: 5px; margin-left: 5px; margin-right: 5px; padding: 0px 11px 0px 9px; font-weight: bold; } ''; settings = { mainBar = { layer = "top"; position = "top"; height = 50; output = config.monorepo.vars.monitors; modules-left = [ "hyprland/workspaces" ]; modules-center = [ "hyprland/window" ]; modules-right = [ "battery" "clock" ]; battery = { format = "{icon} {capacity}%"; format-icons = ["" "" "" "" "" ]; }; clock = { format = "⏰ {:%a %d, %b %H:%M}"; }; }; }; }; } #+end_src *** Wofi This is a run launcher for wayland. I also use it for my powermenu. #+begin_src nix :tangle ../nix/modules/home/wofi.nix { lib, config, ... }: { programs.wofi = { enable = lib.mkDefault config.monorepo.profiles.graphics.enable; settings = { location = "bottom-right"; allow_markup = true; show = "drun"; width = 750; height = 400; always_parse_args = true; show_all = false; term = "kitty"; hide_scroll = true; print_command = true; insensitive = true; prompt = "Run what, Commander?"; columns = 2; }; style = '' @define-color rosewater #f5e0dc; @define-color rosewater-rgb rgb(245, 224, 220); @define-color flamingo #f2cdcd; @define-color flamingo-rgb rgb(242, 205, 205); @define-color pink #f5c2e7; @define-color pink-rgb rgb(245, 194, 231); @define-color mauve #cba6f7; @define-color mauve-rgb rgb(203, 166, 247); @define-color red #f38ba8; @define-color red-rgb rgb(243, 139, 168); @define-color maroon #eba0ac; @define-color maroon-rgb rgb(235, 160, 172); @define-color peach #fab387; @define-color peach-rgb rgb(250, 179, 135); @define-color yellow #f9e2af; @define-color yellow-rgb rgb(249, 226, 175); @define-color green #a6e3a1; @define-color green-rgb rgb(166, 227, 161); @define-color teal #94e2d5; @define-color teal-rgb rgb(148, 226, 213); @define-color sky #89dceb; @define-color sky-rgb rgb(137, 220, 235); @define-color sapphire #74c7ec; @define-color sapphire-rgb rgb(116, 199, 236); @define-color blue #89b4fa; @define-color blue-rgb rgb(137, 180, 250); @define-color lavender #b4befe; @define-color lavender-rgb rgb(180, 190, 254); @define-color text #cdd6f4; @define-color text-rgb rgb(205, 214, 244); @define-color subtext1 #bac2de; @define-color subtext1-rgb rgb(186, 194, 222); @define-color subtext0 #a6adc8; @define-color subtext0-rgb rgb(166, 173, 200); @define-color overlay2 #9399b2; @define-color overlay2-rgb rgb(147, 153, 178); @define-color overlay1 #7f849c; @define-color overlay1-rgb rgb(127, 132, 156); @define-color overlay0 #6c7086; @define-color overlay0-rgb rgb(108, 112, 134); @define-color surface2 #585b70; @define-color surface2-rgb rgb(88, 91, 112); @define-color surface1 #45475a; @define-color surface1-rgb rgb(69, 71, 90); @define-color surface0 #313244; @define-color surface0-rgb rgb(49, 50, 68); @define-color base #1e1e2e; @define-color base-rgb rgb(30, 30, 46); @define-color mantle #181825; @define-color mantle-rgb rgb(24, 24, 37); @define-color crust #11111b; @define-color crust-rgb rgb(17, 17, 27); * { font-family: 'Iosevka Nerd Font', monospace; font-size: 14px; } /* Window */ window { margin: 0px; padding: 10px; border: 0.16em solid @lavender; border-radius: 0.1em; background-color: @base; animation: slideIn 0.5s ease-in-out both; } /* Slide In */ @keyframes slideIn { 0% { opacity: 0; } 100% { opacity: 1; } } /* Inner Box */ #inner-box { margin: 5px; padding: 10px; border: none; background-color: @base; animation: fadeIn 0.5s ease-in-out both; } /* Fade In */ @keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } /* Outer Box */ #outer-box { margin: 5px; padding: 10px; border: none; background-color: @base; } /* Scroll */ #scroll { margin: 0px; padding: 10px; border: none; background-color: @base; } /* Input */ #input { margin: 5px 20px; padding: 10px; border: none; border-radius: 0.1em; color: @text; background-color: @base; animation: fadeIn 0.5s ease-in-out both; } #input image { border: none; color: @red; } #input * { outline: 4px solid @red!important; } /* Text */ #text { margin: 5px; border: none; color: @text; animation: fadeIn 0.5s ease-in-out both; } #entry { background-color: @base; } #entry arrow { border: none; color: @lavender; } /* Selected Entry */ #entry:selected { border: 0.11em solid @lavender; } #entry:selected #text { color: @mauve; } #entry:drop(active) { background-color: @lavender!important; } ''; }; } #+end_src *** yt-dlp A classic program that allows you to download from youtube. Also has integrations with mpv. #+begin_src nix :tangle ../nix/modules/home/yt-dlp.nix { lib, config, ... }: { programs.yt-dlp = { enable = lib.mkDefault config.monorepo.profiles.graphics.enable; settings = { embed-thumbnail = true; embed-subs = true; sub-langs = "all"; downloader = "aria2c"; downloader-args = "aria2c:'-c -x8 -s8 -k1M'"; }; }; } #+end_src *** pantalaimon #+begin_src nix :tangle ../nix/modules/home/pantalaimon.nix { lib, ... }: { services.pantalaimon = { enable = lib.mkDefault false; settings = { Default = { LogLevel = "Debug"; SSL = true; }; local-matrix = { Homeserver = "https://matrix.nullring.xyz"; ListenAddress = "127.0.0.1"; ListenPort = 8008; }; }; }; } #+end_src *** Zsh My zsh config has some useful aliases that one should read through. Otherwise it is pretty standard. #+begin_src nix :tangle ../nix/modules/home/zsh.nix { config, pkgs, systemHostName, ... }: { programs.zsh = { enable = true; initContent = '' umask 0022 export EXTRA_CCFLAGS="-I/usr/include" source ${pkgs.zsh-vi-mode}/share/zsh-vi-mode/zsh-vi-mode.plugin.zsh export QT_QPA_PLATFORM="wayland" export OLLAMA_MODEL="qwen3:14b" ''; localVariables = { EDITOR = "emacsclient --create-frame --alternate-editor=vim"; INPUT_METHOD = "fcitx"; QT_IM_MODULE = "fcitx"; GTK_IM_MODULE = "fcitx"; XMODIFIERS = "@im=fcitx"; XIM_SERVERS = "fcitx"; WXSUPPRESS_SIZER_FLAGS_CHECK = "1"; }; shellAliases = { clone-secrets = "git clone ssh://\"$1\"/home/preston/secrets \"$HOME/secrets\""; get-channel-id = "yt-dlp --print \"%(channel_id)s\" --playlist-end 1 \"$1\""; se = "sops edit"; f = "vim $(fzf)"; e = "cd $(find . -type d -print | fzf)"; c = "clear"; g = "git"; v = "vim"; py = "python3"; build-installer = "nix build $HOME/monorepo/nix#nixosConfigurations.installer.config.system.build.isoImage"; rb = "sudo nixos-rebuild switch --flake $HOME/monorepo/nix#${systemHostName}"; nfu = "cd ~/monorepo/nix && git add . && git commit -m \"new flake lock\" && nix flake update"; usync = "rsync -azvP --chmod=\"Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r\" ~/monorepo/result/ root@${config.monorepo.vars.remoteHost}:/var/www/${config.monorepo.vars.internetName}-website/"; usite = "cd ~/src/publish-org-roam-ui && bash local.sh && rm -rf ~/website_html/graph_view; cp -r ~/src/publish-org-roam-ui/out ~/website_html/graph_view && rsync -azvP --chmod=\"Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r\" ~/website_html/ root@${config.monorepo.vars.remoteHost}:/var/www/${config.monorepo.vars.internetName}-website/"; sai = "eval \"$(ssh-agent -s)\" && ssh-add ~/.ssh/id_ed25519 && ssh-add -l"; }; loginExtra = '' if [[ "$(tty)" = "/dev/tty1" ]]; then exec Hyprland fi ''; }; } #+end_src *** User This configuration is the backbone configuration for the default user. It specifies some generally useful packages and something every home should have, as well as some dependencies for these configurations. #+begin_src nix :tangle ../nix/modules/home/user.nix { lib, config, pkgs, ... }: { home = { activation.startup-files = lib.hm.dag.entryAfter [ "installPackages" ] '' if [ ! -d "/home/${config.monorepo.vars.userName}/email/${config.monorepo.vars.internetName}/" ]; then mkdir -p /home/${config.monorepo.vars.userName}/email/${config.monorepo.vars.internetName}/ fi if [ ! -d "/home/${config.monorepo.vars.userName}/music" ]; then mkdir -p /home/${config.monorepo.vars.userName}/music fi if [ ! -d /home/${config.monorepo.vars.userName}/org ]; then mkdir -p /home/${config.monorepo.vars.userName}/org fi if [ ! -d /home/${config.monorepo.vars.userName}/src ]; then mkdir -p /home/${config.monorepo.vars.userName}/src fi touch /home/${config.monorepo.vars.userName}/org/agenda.org touch /home/${config.monorepo.vars.userName}/org/notes.org ''; enableNixpkgsReleaseCheck = false; username = config.monorepo.vars.userName; homeDirectory = "/home/${config.monorepo.vars.userName}"; stateVersion = "24.11"; packages = with pkgs; (if config.monorepo.profiles.graphics.enable then [ # wikipedia # kiwix kiwix-tools gnupg unzip mupdf zathura fzf # passwords age sops # formatting ghostscript texliveFull pandoc # Emacs Deps graphviz jq # Apps octaveFull grim swww vim kotatogram-desktop tg qwen-code element-desktop signal-desktop signal-cli thunderbird jami # Sound/media pavucontrol alsa-utils imagemagick ffmpeg helvum # Net curl rsync gitFull iamb ungoogled-chromium # Tor torsocks tor-browser # For transfering secrets onto new system stow # fonts nerd-fonts.iosevka noto-fonts noto-fonts-cjk-sans noto-fonts-color-emoji fira-code font-awesome_6 victor-mono (aspellWithDicts (dicts: with dicts; [ en en-computers en-science ])) # Misc. pinentry-gnome3 x11_ssh_askpass xdg-utils acpilight pfetch libnotify htop (pkgs.writeShellScriptBin "help" '' #!/usr/bin/env sh # Portable, colored, nicely aligned alias list # Generate uncolored alias pairs aliases=$(cat <<'EOF' ${let aliases = config.programs.zsh.shellAliases; in lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name} -> ${value}" ) aliases)} EOF ) # Align and color using awk echo "$aliases" | awk ' BEGIN { GREEN="\033[0;32m"; YELLOW="\033[0;33m"; RESET="\033[0m"; maxlen=0; } { # Split line on " -> " split($0, parts, / -> /); name[NR]=parts[1]; cmd[NR]=parts[2]; if(length(parts[1])>maxlen) maxlen=length(parts[1]); } END { for(i=1;i<=NR;i++) { # printf with fixed width for alias name printf "%s%-*s%s -> %s%s%s\n", GREEN, maxlen, name[i], RESET, YELLOW, cmd[i], RESET; } }' '') (writeShellScriptBin "remote-build" '' #!/bin/bash nixos-rebuild --sudo --ask-sudo-password --target-host "$1" switch --flake $HOME/monorepo#spontaneity '' ) (writeShellScriptBin "install-vps" '' #!/bin/bash nix run github:nix-community/nixos-anywhere -- --generate-hardware-config nixos-generate-config $HOME/monorepo/nix/systems/spontaneity/hardware-configuration.nix --flake $HOME/monorepo/nix#spontaneity --target-host "$1" '') (writeShellScriptBin "secrets" '' #!/bin/bash cd "$HOME/secrets" git pull # repo is over LAN stow */ # manage secrets with gnu stow cd "$HOME" '') (writeShellScriptBin "spontaneity-ci" '' #!/bin/bash nixos-rebuild build-vm --flake $HOME/monorepo/nix#spontaneity && QEMU_OPTS="-serial stdio" ./result/bin/run-spontaneity-vm 2>&1 | tee vm-boot.log'') ] else [ pfetch # net curl torsocks rsync ]); }; services = { gpg-agent = { pinentry.package = pkgs.pinentry-emacs; enable = true; extraConfig = '' allow-emacs-pinentry allow-loopback-pinentry ''; }; }; xdg.mimeApps = { enable = lib.mkDefault config.monorepo.profiles.graphics.enable; defaultApplications = { "x-scheme-handler/mailto" = "emacsclient-mail.desktop"; "text/html" = "librewolf.desktop"; "text/xml" = "librewolf.desktop"; "application/xhtml+xml" = "librewolf.desktop"; "x-scheme-handler/http" = "librewolf.desktop"; "x-scheme-handler/https" = "librewolf.desktop"; "x-scheme-handler/about" = "librewolf.desktop"; "x-scheme-handler/unknown" = "librewolf.desktop"; }; }; programs.bash.enable = true; fonts.fontconfig.enable = true; } #+end_src * Systems ** Home This module dynamically imports the correct corresponding home.nix at the path. #+begin_src nix :tangle ../nix/systems/home.nix { config, sops-nix, ... }: { home-manager = { backupFileExtension = "backup"; sharedModules = [ sops-nix.homeManagerModules.sops ]; extraSpecialArgs = { super = config; }; useGlobalPkgs = true; useUserPackages = true; users."${config.monorepo.vars.userName}" = (import (./. + "/${config.networking.hostName}/home.nix")); }; } #+end_src ** Common These are the common includes for each of my systems. This ensures that we don't have to duplicate includes every time we want to add a new system. Also more common configuration can go here. #+begin_src nix :tangle ../nix/systems/common.nix { ... }: { imports = [ ./home.nix ../modules/default.nix ../disko/gpt-common.nix ]; # Put configuration (e.g. monorepo variable configuration) common to all configs here } #+end_src *** Home Manager Common Also I want to have the same kind of file for the home namespace. #+begin_src nix :tangle ../nix/systems/home-common.nix { ... }: { imports = [ ../modules/home/default.nix ]; # Put configuration (e.g. monorepo variable configuration) common to all configs here } #+end_src ** Continuity This is pretty understandable, if you understand all the above. #+begin_src nix :tangle ../nix/systems/continuity/default.nix { ... }: { imports = [ ../common.nix ]; config = { monorepo = { profiles = { impermanence.enable = true; desktop.enable = true; }; vars = { device = "/dev/sda"; fileSystem = "btrfs"; }; }; }; } #+end_src *** Home Each system has a corresponding home configuration in order to set monorepo home options. #+begin_src nix :tangle ../nix/systems/continuity/home.nix { ... }: { imports = [ ../home-common.nix ]; } #+end_src ** Affinity This is my configuration for my workstation. It runs ollama, as well as several other useful services. #+begin_src nix :tangle ../nix/systems/affinity/default.nix { ... }: { imports = [ ../common.nix ]; config = { monorepo = { vars.device = "/dev/nvme0n1"; vars.fileSystem = "ext4"; profiles = { cuda.enable = true; workstation.enable = true; }; }; }; } #+end_src *** Home I want cuda in home manager too. #+begin_src nix :tangle ../nix/systems/affinity/home.nix { ... }: { imports = [ ../home-common.nix ]; } #+end_src ** rpi-zero #+begin_src nix :tangle ../nix/systems/rpi-zero/default.nix { ... }: { imports = [ ../common.nix ]; config = { zramSwap = { enable = true; algorithm = "zstd"; memoryPercent = 100; }; boot.loader.grub.enable = false; boot.loader.generic-extlinux-compatible.enable = true; monorepo = { vars.device = "/dev/mmcblk0"; profiles = { ttyonly.enable = true; }; }; }; } #+end_src *** Home #+begin_src nix :tangle ../nix/systems/rpi-zero/home.nix { ... }: { imports = [ ../home-common.nix ]; config.monorepo.profiles.enable = false; } #+end_src ** Spontaneity Spontaneity is my VPS instance. Note that much of this is not fully reproducible; you must change the IPs yourself and you must change some DNS records to match what you have on your system after deployment. #+begin_src nix :tangle ../nix/systems/spontaneity/default.nix { ... }: { imports = [ ../common.nix # nixos-anywhere generates this file ./hardware-configuration.nix ]; config = { monorepo = { vars.device = "/dev/vda"; profiles = { server = { enable = true; ipv4 = "66.42.84.130"; gateway = "66.42.84.1"; ipv6 = "2001:19f0:5401:10d0:5400:5ff:fe4a:7794"; interface = "ens3"; }; grub.enable = true; pipewire.enable = false; tor.enable = false; }; }; boot.loader.grub.device = "nodev"; }; } #+end_src ** Home #+begin_src nix :tangle ../nix/systems/spontaneity/home.nix { ... }: { imports = [ ../home-common.nix ]; } #+end_src ** Installer My installer installs my systems almost completely without interaction. You can also make them install the exact version of the system that you want it to by pinning the commits to make it always work in the exact same deterministic way. *** ISO Default Profile This contains the installation script I use to install my systems. #+begin_src nix :tangle ../nix/systems/installer/default.nix { pkgs, lib, modulesPath, disko, monorepoSelf ? null, self, ... }: let commits = { diskoCommitHash = disko.rev or "dirty"; monorepoCommitHash = if monorepoSelf != null then (monorepoSelf.rev or "dirty") else (self.rev or "dirty"); monorepoUrl = "https://github.com/ret2pop/monorepo"; }; in { imports = [ (modulesPath + "/installer/cd-dvd/installation-cd-minimal.nix") ]; networking = { networkmanager = { enable = true; }; firewall = { allowedTCPPorts = [ 22 ]; allowedUDPPorts = [ ]; }; wireless.enable = lib.mkForce false; }; services.openssh = { enable = true; ports = [ 22 ]; settings = { PasswordAuthentication = false; AllowUsers = null; UseDns = true; PermitRootLogin = lib.mkForce "prohibit-password"; }; }; users.users = { root.openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICts6+MQiMwpA+DfFQxjIN214Jn0pCw/2BDvOzPhR/H2 preston@continuity-dell" ]; nixos = { packages = with pkgs; [ gitFull curl gum (writeShellScriptBin "nix_installer" '' #!/usr/bin/env bash set -euo pipefail if [ "$(id -u)" -eq 0 ]; then echo "ERROR! $(basename "$0") should be run as a regular user" exit 1 fi cd "$HOME" ping -q -c1 google.com &>/dev/null && echo "online! Proceeding with the installation..." || nmtui if [ ! -d "$HOME/monorepo/" ]; then git clone ${commits.monorepoUrl} --recurse-submodules cd "$HOME/monorepo" git checkout "${commits.monorepoCommitHash}" cd "$HOME" fi gum style --border normal --margin "1" --padding "1 2" "Enter a password for the encrypted disk. If you're not installing a profile with an encrypted disk, you can leave this blank." echo "$(gum input --password)" > /tmp/secret.key gum style --border normal --margin "1" --padding "1 2" "Choose a system to install or select \`new\` in order to create a new system." SYSTEM="$(gum choose $(find "$HOME/monorepo/nix/systems" -mindepth 1 -maxdepth 1 -type d -printf "%f\n" | grep -v -E 'installer'; printf "New"))" if [[ "$SYSTEM" == "New" ]]; then gum style --border normal --margin "1" --padding "1 2" "Choose a system name" SYSTEM="$(gum input --placeholder "system name")" gum style --border normal --margin "1" --padding "1 2" "Select a drive file or create a new drive file." DRIVE="$(gum choose $(find "$HOME/monorepo/nix/disko" -mindepth 1 -maxdepth 1 -type f -printf "%f\n"; printf "New"))" if [[ "$DRIVE" == "New" ]]; then gum style --border normal --margin "1" --padding "1 2" "Choose a name to call your drive file." DRIVE="$(gum input --placeholder "drive file name (ex: partition_scheme.nix)")" fi fi if [ ! -d "$HOME/monorepo/nix/systems/$SYSTEM" ]; then mkdir -p "$HOME/monorepo/nix/systems/$SYSTEM" cp "$HOME/monorepo/nix/systems/continuity/home.nix" "$HOME/monorepo/nix/systems/$SYSTEM/home.nix" cat > "$HOME/monorepo/nix/systems/$SYSTEM/default.nix" </dev/null vim "$HOME/monorepo/nix/systems/$SYSTEM/default.nix" gum style --border normal --margin "1" --padding "1 2" "Edit the home default.nix with options." gum input --placeholder "Press Enter to continue" >/dev/null vim "$HOME/monorepo/nix/systems/$SYSTEM/home.nix" sed -i "/hostnames = \[/,/];/ s/];/ \"$1\"\n ];/" "$HOME/monorepo/nix/flake.nix" if [ ! -f "$HOME/monorepo/nix/disko/$DRIVE" ]; then cp "$HOME/monorepo/nix/disko/drive-simple.nix" "$HOME/monorepo/nix/disko/$DRIVE" gum style --border normal --margin "1" --padding "1 2" "Edit the drive file with your preferred partitioning scheme." gum input --placeholder "Press Enter to continue" >/dev/null vim "$HOME/monorepo/nix/disko/$DRIVE" fi cd "$HOME/monorepo" && git add . && cd "$HOME" fi nix --extra-experimental-features 'nix-command flakes' eval "$HOME/monorepo/nix#evalDisko.$SYSTEM" > "$HOME/drive.nix" gum style --border normal --margin "1" --padding "1 2" "Formatting the drive is destructive!" if gum confirm "Are you sure you want to continue?"; then echo "Proceeding..." else echo "Aborting." exit 1 fi sudo nix --experimental-features "nix-command flakes" run "github:nix-community/disko/${commits.diskoCommitHash}" -- --mode destroy,format,mount "$HOME/drive.nix" cd /mnt sudo nixos-install --flake "$HOME/monorepo/nix#$SYSTEM" target_user="$(ls /mnt/home | head -n1)" if [ -z "$target_user" ]; then echo "No user directories found in /mnt/home" exit 1 fi sudo cp -r "$HOME/monorepo" "/mnt/home/$target_user/" echo "rebooting..."; sleep 3; reboot '') ]; }; }; systemd = { services.sshd.wantedBy = pkgs.lib.mkForce [ "multi-user.target" ]; targets = { sleep.enable = false; suspend.enable = false; hibernate.enable = false; hybrid-sleep.enable = false; }; }; } #+end_src