My nix-darwin and NixOS config
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: DRY and reorganise modules

lib/default.nix — new shared helper
Add resolveFrom pkgSet names, a single implementation of the
"resolve package names against a set, warn and skip missing"
pattern that was copy-pasted four times. Update lib/USAGE.md
to document it.

Deduplicate resolvePackages / resolveKde (4 sites)
modules/packages.nix, modules/darwin/packages.nix,
modules/server/packages.nix, modules/desktop.nix all replace
their local copy with a one-liner:
resolvePackages = (import ../lib).resolveFrom pkgs;

flake.nix — extract three local helpers
sharedNixpkgsConfig — allowUnfree + vscode-extensions overlay,
was duplicated verbatim in nixosModules and
darwinModules
mkHMConfig { isDarwin, extraModules }
— 7-line home-manager block that differed only
in isDarwin and the platform-specific extra
modules; replaces two identical copies
mkUnstablePkgs system
— import nixpkgs-unstable { inherit system;
config.allowUnfree = true; }, was repeated
for server and server-arm

modules/options.nix — consistent type aliases
Add listInt and attrsInt alongside the existing listStr/nullStr.
Use them for allowedTCPPorts, allowedUDPPorts, and masApps (which
were still using the raw types.listOf int / types.attrsOf int forms).

hosts/server/default.nix — minor tidying
Collapse three separate services.logind.settings.Login.Handle*
assignments into a single attrset block.

Move server-only modules under modules/server/
caddy, cloudflare-tunnel, forgejo, grafana (+dashboard JSON),
immich, jellyfin, nextcloud, pds (+pds-landing/), split-dns all
move from modules/ root into modules/server/, co-located with the
existing server/* modules. Update ../secrets/ → ../../secrets/ in
the six files that reference secrets, and update all import paths
in hosts/server/default.nix accordingly. No behaviour change.

+132 -124
+45 -42
flake.nix
··· 64 64 sops-nix.homeManagerModules.sops 65 65 ]; 66 66 67 + # Nixpkgs settings applied identically on every host. 68 + sharedNixpkgsConfig = { 69 + nixpkgs.config.allowUnfree = true; 70 + nixpkgs.overlays = [ nix-vscode-extensions.overlays.default ]; 71 + }; 72 + 73 + # Build a home-manager configuration block, parameterised per platform. 74 + # isDarwin — sets extraSpecialArgs.isDarwin and controls which extra 75 + # HM modules are loaded (plasma-manager vs mac-app-util). 76 + # extraModules — platform-specific HM modules appended to sharedHMModules. 77 + mkHMConfig = 78 + { 79 + isDarwin, 80 + extraModules ? [ ], 81 + }: 82 + { 83 + home-manager.useGlobalPkgs = true; 84 + home-manager.useUserPackages = true; 85 + home-manager.extraSpecialArgs = { inherit isDarwin; }; 86 + home-manager.sharedModules = sharedHMModules ++ extraModules; 87 + home-manager.users.ewan = ./home/default.nix; 88 + home-manager.backupFileExtension = "hm-bak"; 89 + home-manager.overwriteBackup = true; 90 + }; 91 + 92 + # Instantiate nixpkgs-unstable for a given system with allowUnfree = true. 93 + mkUnstablePkgs = 94 + system: 95 + import nixpkgs-unstable { 96 + inherit system; 97 + config.allowUnfree = true; 98 + }; 99 + 67 100 # Modules common to every NixOS host. 68 101 nixosModules = [ 69 102 ./modules/options.nix ··· 71 104 sops-nix.nixosModules.sops 72 105 nix-topology.nixosModules.default 73 106 home-manager.nixosModules.home-manager 74 - { 75 - nixpkgs.config.allowUnfree = true; 76 - nixpkgs.overlays = [ nix-vscode-extensions.overlays.default ]; 77 - } 78 - { 79 - home-manager.useGlobalPkgs = true; 80 - home-manager.useUserPackages = true; 81 - home-manager.extraSpecialArgs = { 82 - isDarwin = false; 83 - }; 84 - home-manager.sharedModules = sharedHMModules ++ [ 85 - plasma-manager.homeModules.plasma-manager 86 - ]; 87 - home-manager.users.ewan = ./home/default.nix; 88 - home-manager.backupFileExtension = "hm-bak"; 89 - home-manager.overwriteBackup = true; 90 - } 107 + sharedNixpkgsConfig 108 + (mkHMConfig { 109 + isDarwin = false; 110 + extraModules = [ plasma-manager.homeModules.plasma-manager ]; 111 + }) 91 112 ]; 92 113 93 114 # Modules common to every nix-darwin host. ··· 95 116 ./modules/options.nix 96 117 mac-app-util.darwinModules.default 97 118 home-manager.darwinModules.home-manager 98 - { 99 - nixpkgs.config.allowUnfree = true; 100 - nixpkgs.overlays = [ nix-vscode-extensions.overlays.default ]; 101 - } 102 - { 103 - home-manager.useGlobalPkgs = true; 104 - home-manager.useUserPackages = true; 105 - home-manager.extraSpecialArgs = { 106 - isDarwin = true; 107 - }; 108 - home-manager.sharedModules = sharedHMModules ++ [ 109 - mac-app-util.homeManagerModules.default 110 - ]; 111 - home-manager.users.ewan = ./home/default.nix; 112 - home-manager.backupFileExtension = "hm-bak"; 113 - home-manager.overwriteBackup = true; 114 - } 119 + sharedNixpkgsConfig 120 + (mkHMConfig { 121 + isDarwin = true; 122 + extraModules = [ mac-app-util.homeManagerModules.default ]; 123 + }) 115 124 ]; 116 125 117 126 forAllSystems = ··· 147 156 server = nixpkgs.lib.nixosSystem { 148 157 specialArgs = { 149 158 inherit self; 150 - pkgs-unstable = import nixpkgs-unstable { 151 - system = "x86_64-linux"; 152 - config.allowUnfree = true; 153 - }; 159 + pkgs-unstable = mkUnstablePkgs "x86_64-linux"; 154 160 }; 155 161 modules = nixosModules ++ [ 156 162 ./hosts/server ··· 161 167 server-arm = nixpkgs.lib.nixosSystem { 162 168 specialArgs = { 163 169 inherit self; 164 - pkgs-unstable = import nixpkgs-unstable { 165 - system = "aarch64-linux"; 166 - config.allowUnfree = true; 167 - }; 170 + pkgs-unstable = mkUnstablePkgs "aarch64-linux"; 168 171 }; 169 172 modules = nixosModules ++ [ 170 173 ./hosts/server
+15 -13
hosts/server/default.nix
··· 9 9 imports = [ 10 10 ./minimal-hardware.nix 11 11 ../../modules/users.nix 12 - ../../modules/caddy.nix 13 - ../../modules/split-dns.nix 14 - ../../modules/cloudflare-tunnel.nix 15 - ../../modules/pds.nix 16 - ../../modules/forgejo.nix 17 - ../../modules/nextcloud.nix 18 - ../../modules/immich.nix 19 - ../../modules/jellyfin.nix 20 - ../../modules/grafana.nix 12 + ../../modules/server/caddy.nix 13 + ../../modules/server/split-dns.nix 14 + ../../modules/server/cloudflare-tunnel.nix 15 + ../../modules/server/pds.nix 16 + ../../modules/server/forgejo.nix 17 + ../../modules/server/nextcloud.nix 18 + ../../modules/server/immich.nix 19 + ../../modules/server/jellyfin.nix 20 + ../../modules/server/grafana.nix 21 21 ../../profiles/server-hardened.nix 22 22 ]; 23 23 ··· 29 29 myConfig.services.pds.enable = true; 30 30 myConfig.services.cloudflare.enable = true; 31 31 32 - # Ignore laptop lid — treat as headless, never suspend 33 - services.logind.settings.Login.HandleLidSwitch = "ignore"; 34 - services.logind.settings.Login.HandleLidSwitchExternalPower = "ignore"; 35 - services.logind.settings.Login.HandleLidSwitchDocked = "ignore"; 32 + # Ignore laptop lid — treat as headless, never suspend. 33 + services.logind.settings.Login = { 34 + HandleLidSwitch = "ignore"; 35 + HandleLidSwitchExternalPower = "ignore"; 36 + HandleLidSwitchDocked = "ignore"; 37 + }; 36 38 systemd.targets.sleep.enable = false; 37 39 systemd.targets.suspend.enable = false; 38 40 systemd.targets.hibernate.enable = false;
+23 -7
lib/USAGE.md
··· 1 1 # Module System — Developer Guide 2 2 3 - > **Note**: The `cfgLib` helper library has been removed. All configuration is now accessed directly through the standard NixOS module system via `config.myConfig.*` (system modules) or `osConfig.myConfig.*` (home-manager modules). No custom abstraction or manual wiring is needed. 3 + > **Note**: The `cfgLib` helper library has been removed. All configuration is now accessed directly 4 + > through the standard NixOS module system via `config.myConfig.*` (system modules) or 5 + > `osConfig.myConfig.*` (home-manager modules). No custom abstraction or manual wiring is needed. 4 6 5 7 ## Accessing config in system modules 6 8 ··· 31 33 32 34 ## Resolving packages from a list of names 33 35 34 - The old `cfgLib.resolvePackages` helper is replaced by a simple inline expression using `builtins.filter` and `pkgs ? name`: 36 + Use `resolveFrom` from `lib/default.nix`. It skips missing packages with a 37 + warning instead of failing the build, and works with any package set. 35 38 36 39 ```nix 37 - { config, pkgs, lib, ... }: 40 + { config, pkgs, ... }: 38 41 let 39 42 cfg = config.myConfig; 40 - resolve = names: 41 - map (n: pkgs.${n}) (builtins.filter (n: pkgs ? ${n}) names); 43 + resolvePackages = (import ../lib).resolveFrom pkgs; 42 44 in 43 45 { 44 - environment.systemPackages = resolve cfg.packages.common; 46 + environment.systemPackages = 47 + resolvePackages cfg.packages.common 48 + ++ resolvePackages cfg.packages.development; 49 + } 50 + ``` 51 + 52 + For `kdePackages` or another nested set, pass the sub-set as the first arg: 53 + 54 + ```nix 55 + let resolveKde = (import ../lib).resolveFrom pkgs.kdePackages; 56 + in 57 + { 58 + environment.plasma6.excludePackages = 59 + resolveKde cfg.desktop.plasma.excludePackages; 45 60 } 46 61 ``` 47 62 ··· 63 78 64 79 ## All option declarations 65 80 66 - All options and their defaults live in `modules/options.nix`. See [`docs/settings-config.md`](../docs/settings-config.md) for a full reference table. 81 + All options and their defaults live in `modules/options.nix`. See 82 + [`docs/settings-config.md`](../docs/settings-config.md) for a full reference table. 67 83 68 84 ## Adding a new option 69 85
+29
lib/default.nix
··· 1 + # Utility functions for nix-config modules. 2 + # 3 + # Import as an attrset — no arguments required: 4 + # 5 + # let myLib = import ../lib; in 6 + # { environment.systemPackages = myLib.resolveFrom pkgs cfg.packages.common; } 7 + # 8 + # The functions here are pure helpers; all NixOS option wiring still uses the 9 + # standard module system directly (see USAGE.md for patterns). 10 + { 11 + # Resolve a list of attribute names against any package set, skipping names 12 + # that are absent rather than failing the build. Traces a warning for each 13 + # missing entry so problems are visible without being fatal. 14 + # 15 + # Examples: 16 + # resolveFrom pkgs cfg.packages.common 17 + # resolveFrom pkgs.kdePackages cfg.desktop.plasma.excludePackages 18 + resolveFrom = 19 + pkgSet: names: 20 + builtins.filter (x: x != null) ( 21 + map ( 22 + name: 23 + if pkgSet ? ${name} then 24 + pkgSet.${name} 25 + else 26 + builtins.trace "WARNING: '${name}' not found in package set, skipping" null 27 + ) names 28 + ); 29 + }
+1 -1
modules/caddy.nix modules/server/caddy.nix
··· 54 54 # containing the raw token value only (no KEY= prefix, no trailing newline). 55 55 # The token needs Zone.DNS edit permission for ewancroft.uk. 56 56 sops.secrets."cloudflare-acme.env" = lib.mkIf hasTailnet { 57 - sopsFile = ../secrets/cloudflare-acme.env; 57 + sopsFile = ../../secrets/cloudflare-acme.env; 58 58 format = "binary"; 59 59 owner = "acme"; 60 60 mode = "0440";
+2 -2
modules/cloudflare-tunnel.nix modules/server/cloudflare-tunnel.nix
··· 59 59 # JSON credentials file created by `cloudflared tunnel create server`. 60 60 # Encrypt with: sops --encrypt --age <age-pubkey> cf-tunnel.json > secrets/cf-tunnel.json 61 61 sops.secrets."cf-tunnel.json" = { 62 - sopsFile = ../secrets/cf-tunnel.json; 62 + sopsFile = ../../secrets/cf-tunnel.json; 63 63 format = "binary"; 64 64 owner = "cloudflared"; 65 65 group = "cloudflared"; ··· 69 69 }; 70 70 71 71 sops.secrets."cloudflare.token" = { 72 - sopsFile = ../secrets/cloudflare.token; 72 + sopsFile = ../../secrets/cloudflare.token; 73 73 format = "binary"; 74 74 owner = "root"; 75 75 };
+4 -15
modules/darwin/packages.nix
··· 6 6 }: 7 7 let 8 8 cfg = config.myConfig; 9 - 10 - resolvePackages = 11 - names: 12 - builtins.filter (x: x != null) ( 13 - map ( 14 - name: 15 - if pkgs ? ${name} then 16 - pkgs.${name} 17 - else 18 - builtins.trace "WARNING: package '${name}' not found in nixpkgs, skipping" null 19 - ) names 20 - ); 9 + resolvePackages = (import ../../lib).resolveFrom pkgs; 21 10 22 11 # Packages from the shared development list that are too large or that are 23 12 # better managed via Homebrew on a space-constrained 256 GB Mac. ··· 32 21 "openjdk21" 33 22 ]; 34 23 35 - developmentForDarwin = lib.filter 36 - (name: !(builtins.elem name darwinDevExclude)) 37 - cfg.packages.development; 24 + developmentForDarwin = lib.filter ( 25 + name: !(builtins.elem name darwinDevExclude) 26 + ) cfg.packages.development; 38 27 in 39 28 { 40 29 environment.systemPackages =
+1 -12
modules/desktop.nix
··· 7 7 }: 8 8 let 9 9 cfg = config.myConfig; 10 - 11 - resolveKde = 12 - names: 13 - builtins.filter (x: x != null) ( 14 - map ( 15 - name: 16 - if pkgs.kdePackages ? ${name} then 17 - pkgs.kdePackages.${name} 18 - else 19 - builtins.trace "WARNING: kdePackages.${name} not found, skipping" null 20 - ) names 21 - ); 10 + resolveKde = (import ../lib).resolveFrom pkgs.kdePackages; 22 11 in 23 12 { 24 13 # X11/Wayland base — required even in Wayland sessions.
+1 -1
modules/forgejo.nix modules/server/forgejo.nix
··· 29 29 lib.mkIf cfg.services.forgejo.enable { 30 30 31 31 sops.secrets."forgejo.env" = { 32 - sopsFile = ../secrets/forgejo.env; 32 + sopsFile = ../../secrets/forgejo.env; 33 33 format = "dotenv"; 34 34 owner = "forgejo"; 35 35 group = "forgejo";
modules/grafana-dashboard.json modules/server/grafana-dashboard.json
+1 -1
modules/grafana.nix modules/server/grafana.nix
··· 95 95 # then: sops secrets/nextcloud-metrics-token (binary, raw token). 96 96 # Only activated when myConfig.server.grafana.nextcloudMetrics = true. 97 97 sops.secrets."nextcloud-metrics-token" = lib.mkIf (cfg.services.nextcloud.enable && cfg.server.grafana.nextcloudMetrics) { 98 - sopsFile = ../secrets/nextcloud-metrics-token; 98 + sopsFile = ../../secrets/nextcloud-metrics-token; 99 99 format = "binary"; 100 100 owner = "nextcloud-exporter"; 101 101 mode = "0440";
modules/immich.nix modules/server/immich.nix
modules/jellyfin.nix modules/server/jellyfin.nix
+2 -2
modules/nextcloud.nix modules/server/nextcloud.nix
··· 41 41 lib.mkIf cfg.services.nextcloud.enable { 42 42 43 43 sops.secrets."nextcloud-smtp-pass" = { 44 - sopsFile = ../secrets/nextcloud-smtp-pass; 44 + sopsFile = ../../secrets/nextcloud-smtp-pass; 45 45 format = "binary"; 46 46 owner = "nextcloud"; 47 47 group = "nextcloud"; ··· 49 49 }; 50 50 51 51 sops.secrets."nextcloud-admin-pass" = { 52 - sopsFile = ../secrets/nextcloud-admin-pass; 52 + sopsFile = ../../secrets/nextcloud-admin-pass; 53 53 format = "binary"; 54 54 owner = "nextcloud"; 55 55 group = "nextcloud";
+5 -3
modules/options.nix
··· 19 19 int = types.int; 20 20 bool = types.bool; 21 21 listStr = types.listOf types.str; 22 + listInt = types.listOf types.int; 22 23 nullStr = types.nullOr types.str; 24 + attrsInt = types.attrsOf types.int; 23 25 24 26 in 25 27 { ··· 676 678 default = true; 677 679 }; 678 680 allowedTCPPorts = mkOption { 679 - type = types.listOf int; 681 + type = listInt; 680 682 default = [ 22 ]; 681 683 }; 682 684 allowedUDPPorts = mkOption { 683 - type = types.listOf int; 685 + type = listInt; 684 686 default = [ ]; 685 687 }; 686 688 }; ··· 856 858 ]; 857 859 }; 858 860 masApps = mkOption { 859 - type = types.attrsOf int; 861 + type = attrsInt; 860 862 default = { 861 863 "Amphetamine" = 937984704; 862 864 "OneDrive" = 823766827;
+1 -12
modules/packages.nix
··· 7 7 }: 8 8 let 9 9 cfg = config.myConfig; 10 - 11 - resolvePackages = 12 - names: 13 - builtins.filter (x: x != null) ( 14 - map ( 15 - name: 16 - if pkgs ? ${name} then 17 - pkgs.${name} 18 - else 19 - builtins.trace "WARNING: package '${name}' not found in nixpkgs, skipping" null 20 - ) names 21 - ); 10 + resolvePackages = (import ../lib).resolveFrom pkgs; 22 11 in 23 12 { 24 13 programs = {
modules/pds-landing/assets/icon/android-icon-192x192.png modules/server/pds-landing/assets/icon/android-icon-192x192.png
modules/pds-landing/assets/icon/apple-icon-114x114.png modules/server/pds-landing/assets/icon/apple-icon-114x114.png
modules/pds-landing/assets/icon/apple-icon-120x120.png modules/server/pds-landing/assets/icon/apple-icon-120x120.png
modules/pds-landing/assets/icon/apple-icon-144x144.png modules/server/pds-landing/assets/icon/apple-icon-144x144.png
modules/pds-landing/assets/icon/apple-icon-152x152.png modules/server/pds-landing/assets/icon/apple-icon-152x152.png
modules/pds-landing/assets/icon/apple-icon-180x180.png modules/server/pds-landing/assets/icon/apple-icon-180x180.png
modules/pds-landing/assets/icon/apple-icon-57x57.png modules/server/pds-landing/assets/icon/apple-icon-57x57.png
modules/pds-landing/assets/icon/apple-icon-60x60.png modules/server/pds-landing/assets/icon/apple-icon-60x60.png
modules/pds-landing/assets/icon/apple-icon-72x72.png modules/server/pds-landing/assets/icon/apple-icon-72x72.png
modules/pds-landing/assets/icon/apple-icon-76x76.png modules/server/pds-landing/assets/icon/apple-icon-76x76.png
modules/pds-landing/assets/icon/browserconfig.xml modules/server/pds-landing/assets/icon/browserconfig.xml
modules/pds-landing/assets/icon/favicon-16x16.png modules/server/pds-landing/assets/icon/favicon-16x16.png
modules/pds-landing/assets/icon/favicon-256x256.png modules/server/pds-landing/assets/icon/favicon-256x256.png
modules/pds-landing/assets/icon/favicon-32x32.png modules/server/pds-landing/assets/icon/favicon-32x32.png
modules/pds-landing/assets/icon/favicon-96x96.png modules/server/pds-landing/assets/icon/favicon-96x96.png
modules/pds-landing/assets/icon/favicon.ico modules/server/pds-landing/assets/icon/favicon.ico
modules/pds-landing/assets/icon/manifest.json modules/server/pds-landing/assets/icon/manifest.json
modules/pds-landing/assets/icon/ms-icon-144x144.png modules/server/pds-landing/assets/icon/ms-icon-144x144.png
modules/pds-landing/assets/icon/ms-icon-150x150.png modules/server/pds-landing/assets/icon/ms-icon-150x150.png
modules/pds-landing/assets/icon/ms-icon-310x310.png modules/server/pds-landing/assets/icon/ms-icon-310x310.png
modules/pds-landing/assets/icon/ms-icon-70x70.png modules/server/pds-landing/assets/icon/ms-icon-70x70.png
modules/pds-landing/assets/thumb.svg modules/server/pds-landing/assets/thumb.svg
modules/pds-landing/index.html modules/server/pds-landing/index.html
modules/pds-landing/script.js modules/server/pds-landing/script.js
modules/pds-landing/status.js modules/server/pds-landing/status.js
modules/pds-landing/styles/input.css modules/server/pds-landing/styles/input.css
modules/pds-landing/utils.js modules/server/pds-landing/utils.js
+1 -1
modules/pds.nix modules/server/pds.nix
··· 106 106 lib.mkIf cfg.services.pds.enable { 107 107 108 108 sops.secrets."pds.env" = { 109 - sopsFile = ../secrets/pds.env; 109 + sopsFile = ../../secrets/pds.env; 110 110 format = "dotenv"; 111 111 owner = "pds"; 112 112 group = "pds";
+1 -12
modules/server/packages.nix
··· 5 5 }: 6 6 let 7 7 cfg = config.myConfig; 8 - 9 - resolvePackages = 10 - names: 11 - builtins.filter (x: x != null) ( 12 - map ( 13 - name: 14 - if pkgs ? ${name} then 15 - pkgs.${name} 16 - else 17 - builtins.trace "WARNING: package '${name}' not found in nixpkgs, skipping" null 18 - ) names 19 - ); 8 + resolvePackages = (import ../../lib).resolveFrom pkgs; 20 9 in 21 10 { 22 11 environment.systemPackages =
modules/split-dns.nix modules/server/split-dns.nix