From 0ea11e023624b067a584abcd0500dd9813e9dfaf Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Tue, 7 Apr 2026 15:39:45 -0500 Subject: [PATCH] refactor: moved nixos modules to dendrite pattern --- legacy-modules/nixos-modules/ai.nix | 46 -- legacy-modules/nixos-modules/default.nix | 23 - legacy-modules/nixos-modules/desktop.nix | 84 ---- legacy-modules/nixos-modules/hardware.nix | 34 -- .../nixos-modules/home-manager/default.nix | 9 - .../home-manager/flipperzero.nix | 9 - .../nixos-modules/home-manager/i18n.nix | 26 -- .../nixos-modules/home-manager/openssh.nix | 11 - .../nixos-modules/home-manager/steam.nix | 18 - legacy-modules/nixos-modules/i18n.nix | 3 - .../nixos-modules/ollama/default.nix | 6 - .../nixos-modules/ollama/ollama.nix | 32 -- .../nixos-modules/ollama/storage.nix | 37 -- .../nixos-modules/server/actual/actual.nix | 24 - .../nixos-modules/server/actual/const.nix | 3 - .../nixos-modules/server/actual/default.nix | 8 - .../nixos-modules/server/actual/fail2ban.nix | 9 - .../nixos-modules/server/actual/proxy.nix | 34 -- .../nixos-modules/server/actual/storage.nix | 22 - .../nixos-modules/server/bazarr/default.nix | 5 - .../nixos-modules/server/bazarr/storage.nix | 21 - .../server/crab-hole/crab-hole.nix | 193 -------- .../server/crab-hole/default.nix | 6 - .../server/crab-hole/storage.nix | 21 - .../nixos-modules/server/default.nix | 26 -- .../nixos-modules/server/fail2ban/default.nix | 6 - .../server/fail2ban/fail2ban.nix | 51 -- .../nixos-modules/server/fail2ban/storage.nix | 22 - .../server/flaresolverr/default.nix | 5 - .../server/flaresolverr/storage.nix | 19 - .../nixos-modules/server/forgejo/const.nix | 4 - .../nixos-modules/server/forgejo/database.nix | 32 -- .../nixos-modules/server/forgejo/default.nix | 9 - .../nixos-modules/server/forgejo/fail2ban.nix | 41 -- .../nixos-modules/server/forgejo/forgejo.nix | 46 -- .../nixos-modules/server/forgejo/proxy.nix | 43 -- .../nixos-modules/server/forgejo/storage.nix | 21 - .../server/home-assistant/database.nix | 53 --- .../server/home-assistant/default.nix | 10 - .../home-assistant/extensions/default.nix | 12 - .../home-assistant/extensions/jellyfin.nix | 9 - .../home-assistant/extensions/sonos.nix | 11 - .../home-assistant/extensions/wyoming.nix | 9 - .../server/home-assistant/fail2ban.nix | 49 -- .../server/home-assistant/home-assistant.nix | 104 ----- .../server/home-assistant/proxy.nix | 43 -- .../server/home-assistant/storage.nix | 21 - .../nixos-modules/server/immich/database.nix | 30 -- .../nixos-modules/server/immich/default.nix | 20 - .../nixos-modules/server/immich/fail2ban.nix | 35 -- .../nixos-modules/server/immich/proxy.nix | 44 -- .../nixos-modules/server/immich/storage.nix | 21 - .../nixos-modules/server/jackett/default.nix | 17 - .../nixos-modules/server/jackett/storage.nix | 21 - .../nixos-modules/server/jellyfin/default.nix | 8 - .../server/jellyfin/fail2ban.nix | 32 -- .../server/jellyfin/jellyfin.nix | 32 -- .../nixos-modules/server/jellyfin/proxy.nix | 41 -- .../nixos-modules/server/jellyfin/storage.nix | 56 --- .../nixos-modules/server/lidarr/default.nix | 5 - .../nixos-modules/server/lidarr/storage.nix | 21 - .../server/network_storage/default.nix | 6 - .../network_storage/network_storage.nix | 86 ---- .../server/network_storage/nfs.nix | 107 ----- .../server/panoramax/database.nix | 48 -- .../server/panoramax/default.nix | 9 - .../server/panoramax/fail2ban.nix | 11 - .../server/panoramax/panoramax.nix | 359 --------------- .../nixos-modules/server/panoramax/proxy.nix | 39 -- .../server/panoramax/storage.nix | 19 - .../server/paperless/database.nix | 30 -- .../server/paperless/default.nix | 9 - .../server/paperless/fail2ban.nix | 34 -- .../server/paperless/paperless.nix | 27 -- .../nixos-modules/server/paperless/proxy.nix | 33 -- .../server/paperless/storage.nix | 21 - .../nixos-modules/server/postgres/default.nix | 6 - .../server/postgres/postgres.nix | 122 ----- .../nixos-modules/server/postgres/storage.nix | 21 - .../server/qbittorent/default.nix | 6 - .../server/qbittorent/qbittorent.nix | 18 - .../server/qbittorent/storage.nix | 46 -- .../nixos-modules/server/radarr/default.nix | 5 - .../nixos-modules/server/radarr/storage.nix | 21 - .../server/reverseProxy/default.nix | 6 - .../server/reverseProxy/reverseProxy.nix | 176 ------- .../server/reverseProxy/storage.nix | 21 - .../nixos-modules/server/searx/default.nix | 6 - .../nixos-modules/server/searx/proxy.nix | 31 -- .../nixos-modules/server/searx/searx.nix | 59 --- .../nixos-modules/server/sonarr/default.nix | 5 - .../nixos-modules/server/sonarr/storage.nix | 21 - .../nixos-modules/server/wyoming.nix | 63 --- legacy-modules/nixos-modules/ssh.nix | 44 -- legacy-modules/nixos-modules/steam.nix | 9 - .../nixos-modules/storage/impermanence.nix | 142 ------ .../nixos-modules/storage/storage.nix | 216 --------- .../storage/submodules/dataset.nix | 86 ---- .../submodules/impermanenceDataset.nix | 56 --- legacy-modules/nixos-modules/storage/zfs.nix | 347 -------------- legacy-modules/nixos-modules/sync/default.nix | 6 - legacy-modules/nixos-modules/sync/storage.nix | 32 -- legacy-modules/nixos-modules/sync/sync.nix | 36 -- legacy-modules/nixos-modules/system.nix | 13 - .../nixos-modules/tailscale/default.nix | 6 - .../nixos-modules/tailscale/storage.nix | 24 - .../nixos-modules/tailscale/tailscale.nix | 19 - legacy-modules/nixos-modules/users.nix | 432 ----------------- modules/hosts/home/eve/packages.nix | 2 +- modules/hosts/home/leyla/packages/default.nix | 4 +- .../home/leyla/packages/vscode/default.nix | 2 +- modules/hosts/nixos/defiant/configuration.nix | 38 +- .../hosts/nixos/emergent/configuration.nix | 2 +- modules/hosts/nixos/horizon/configuration.nix | 59 +-- .../hosts/nixos/twilight/configuration.nix | 79 +--- modules/nixos/desktop.nix | 86 ++++ modules/nixos/hardware.nix | 36 ++ .../nixos/home-manager-adaptors/default.nix | 13 + .../home-manager-adaptors/flipperzero.nix | 11 + modules/nixos/home-manager-adaptors/i18n.nix | 28 ++ .../nixos/home-manager-adaptors/openssh.nix | 13 + modules/nixos/home-manager-adaptors/steam.nix | 20 + modules/nixos/i18n.nix | 5 + modules/nixos/nixos-modules.nix | 21 + modules/nixos/programs/actual/actual.nix | 25 + modules/nixos/programs/actual/default.nix | 12 + modules/nixos/programs/actual/fail2ban.nix | 11 + modules/nixos/programs/actual/proxy.nix | 36 ++ modules/nixos/programs/actual/storage.nix | 23 + modules/nixos/programs/bazarr/default.nix | 9 + modules/nixos/programs/bazarr/storage.nix | 23 + .../nixos/programs/crab-hole/crab-hole.nix | 195 ++++++++ modules/nixos/programs/crab-hole/default.nix | 10 + modules/nixos/programs/crab-hole/storage.nix | 23 + modules/nixos/programs/default.nix | 32 ++ modules/nixos/programs/fail2ban/default.nix | 10 + modules/nixos/programs/fail2ban/fail2ban.nix | 53 +++ modules/nixos/programs/fail2ban/storage.nix | 23 + .../nixos/programs/flaresolverr/default.nix | 9 + .../nixos/programs/flaresolverr/storage.nix | 21 + modules/nixos/programs/forgejo/database.nix | 34 ++ modules/nixos/programs/forgejo/default.nix | 13 + modules/nixos/programs/forgejo/fail2ban.nix | 43 ++ modules/nixos/programs/forgejo/forgejo.nix | 47 ++ modules/nixos/programs/forgejo/proxy.nix | 44 ++ modules/nixos/programs/forgejo/storage.nix | 23 + .../programs/home-assistant/database.nix | 55 +++ .../nixos/programs/home-assistant/default.nix | 14 + .../home-assistant/extensions/default.nix | 11 + .../home-assistant/extensions/jellyfin.nix | 11 + .../home-assistant/extensions/sonos.nix | 13 + .../home-assistant/extensions/wyoming.nix | 11 + .../programs/home-assistant/fail2ban.nix | 51 ++ .../home-assistant/home-assistant.nix | 106 +++++ .../nixos/programs/home-assistant/proxy.nix | 45 ++ .../nixos/programs/home-assistant/storage.nix | 23 + modules/nixos/programs/immich/database.nix | 32 ++ modules/nixos/programs/immich/default.nix | 12 + modules/nixos/programs/immich/fail2ban.nix | 37 ++ modules/nixos/programs/immich/proxy.nix | 46 ++ modules/nixos/programs/immich/storage.nix | 23 + modules/nixos/programs/jackett/default.nix | 21 + modules/nixos/programs/jackett/storage.nix | 23 + modules/nixos/programs/jellyfin/default.nix | 12 + modules/nixos/programs/jellyfin/fail2ban.nix | 34 ++ modules/nixos/programs/jellyfin/jellyfin.nix | 34 ++ modules/nixos/programs/jellyfin/proxy.nix | 43 ++ modules/nixos/programs/jellyfin/storage.nix | 58 +++ modules/nixos/programs/lidarr/default.nix | 9 + modules/nixos/programs/lidarr/storage.nix | 23 + .../programs/network_storage/default.nix | 10 + .../network_storage/network_storage.nix | 88 ++++ .../nixos/programs/network_storage/nfs.nix | 109 +++++ modules/nixos/programs/panoramax/database.nix | 50 ++ modules/nixos/programs/panoramax/default.nix | 13 + modules/nixos/programs/panoramax/fail2ban.nix | 13 + .../nixos/programs/panoramax/panoramax.nix | 361 +++++++++++++++ modules/nixos/programs/panoramax/proxy.nix | 41 ++ modules/nixos/programs/panoramax/storage.nix | 21 + modules/nixos/programs/paperless/database.nix | 32 ++ modules/nixos/programs/paperless/default.nix | 13 + modules/nixos/programs/paperless/fail2ban.nix | 36 ++ .../nixos/programs/paperless/paperless.nix | 29 ++ modules/nixos/programs/paperless/proxy.nix | 35 ++ modules/nixos/programs/paperless/storage.nix | 23 + modules/nixos/programs/postgres/default.nix | 10 + modules/nixos/programs/postgres/postgres.nix | 124 +++++ modules/nixos/programs/postgres/storage.nix | 23 + modules/nixos/programs/qbittorent/default.nix | 10 + .../nixos/programs/qbittorent/qbittorent.nix | 20 + modules/nixos/programs/qbittorent/storage.nix | 48 ++ modules/nixos/programs/radarr/default.nix | 9 + modules/nixos/programs/radarr/storage.nix | 23 + .../nixos/programs/reverseProxy/default.nix | 10 + .../programs/reverseProxy/reverseProxy.nix | 178 +++++++ .../nixos/programs/reverseProxy/storage.nix | 23 + modules/nixos/programs/searx/default.nix | 10 + modules/nixos/programs/searx/proxy.nix | 33 ++ modules/nixos/programs/searx/searx.nix | 61 +++ modules/nixos/programs/sonarr/default.nix | 9 + modules/nixos/programs/sonarr/storage.nix | 23 + modules/nixos/programs/steam.nix | 11 + modules/nixos/programs/sync/default.nix | 10 + modules/nixos/programs/sync/storage.nix | 34 ++ modules/nixos/programs/sync/sync.nix | 38 ++ modules/nixos/programs/tailscale/default.nix | 10 + modules/nixos/programs/tailscale/storage.nix | 26 ++ .../nixos/programs/tailscale/tailscale.nix | 21 + modules/nixos/programs/wyoming.nix | 65 +++ modules/nixos/ssh.nix | 46 ++ modules/nixos/storage/dataset.nix | 90 ++++ .../nixos}/storage/default.nix | 16 +- .../nixos/storage/impermanence-dataset.nix | 60 +++ modules/nixos/storage/impermanence.nix | 147 ++++++ modules/nixos/storage/storage.nix | 221 +++++++++ modules/nixos/storage/zfs.nix | 351 ++++++++++++++ modules/nixos/system.nix | 15 + modules/nixos/users.nix | 434 ++++++++++++++++++ modules/parts.nix | 2 +- 219 files changed, 4802 insertions(+), 4820 deletions(-) delete mode 100644 legacy-modules/nixos-modules/ai.nix delete mode 100644 legacy-modules/nixos-modules/default.nix delete mode 100644 legacy-modules/nixos-modules/desktop.nix delete mode 100644 legacy-modules/nixos-modules/hardware.nix delete mode 100644 legacy-modules/nixos-modules/home-manager/default.nix delete mode 100644 legacy-modules/nixos-modules/home-manager/flipperzero.nix delete mode 100644 legacy-modules/nixos-modules/home-manager/i18n.nix delete mode 100644 legacy-modules/nixos-modules/home-manager/openssh.nix delete mode 100644 legacy-modules/nixos-modules/home-manager/steam.nix delete mode 100644 legacy-modules/nixos-modules/i18n.nix delete mode 100644 legacy-modules/nixos-modules/ollama/default.nix delete mode 100644 legacy-modules/nixos-modules/ollama/ollama.nix delete mode 100644 legacy-modules/nixos-modules/ollama/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/actual/actual.nix delete mode 100644 legacy-modules/nixos-modules/server/actual/const.nix delete mode 100644 legacy-modules/nixos-modules/server/actual/default.nix delete mode 100644 legacy-modules/nixos-modules/server/actual/fail2ban.nix delete mode 100644 legacy-modules/nixos-modules/server/actual/proxy.nix delete mode 100644 legacy-modules/nixos-modules/server/actual/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/bazarr/default.nix delete mode 100644 legacy-modules/nixos-modules/server/bazarr/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/crab-hole/crab-hole.nix delete mode 100644 legacy-modules/nixos-modules/server/crab-hole/default.nix delete mode 100644 legacy-modules/nixos-modules/server/crab-hole/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/default.nix delete mode 100644 legacy-modules/nixos-modules/server/fail2ban/default.nix delete mode 100644 legacy-modules/nixos-modules/server/fail2ban/fail2ban.nix delete mode 100644 legacy-modules/nixos-modules/server/fail2ban/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/flaresolverr/default.nix delete mode 100644 legacy-modules/nixos-modules/server/flaresolverr/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/forgejo/const.nix delete mode 100644 legacy-modules/nixos-modules/server/forgejo/database.nix delete mode 100644 legacy-modules/nixos-modules/server/forgejo/default.nix delete mode 100644 legacy-modules/nixos-modules/server/forgejo/fail2ban.nix delete mode 100644 legacy-modules/nixos-modules/server/forgejo/forgejo.nix delete mode 100644 legacy-modules/nixos-modules/server/forgejo/proxy.nix delete mode 100644 legacy-modules/nixos-modules/server/forgejo/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/home-assistant/database.nix delete mode 100644 legacy-modules/nixos-modules/server/home-assistant/default.nix delete mode 100644 legacy-modules/nixos-modules/server/home-assistant/extensions/default.nix delete mode 100644 legacy-modules/nixos-modules/server/home-assistant/extensions/jellyfin.nix delete mode 100644 legacy-modules/nixos-modules/server/home-assistant/extensions/sonos.nix delete mode 100644 legacy-modules/nixos-modules/server/home-assistant/extensions/wyoming.nix delete mode 100644 legacy-modules/nixos-modules/server/home-assistant/fail2ban.nix delete mode 100644 legacy-modules/nixos-modules/server/home-assistant/home-assistant.nix delete mode 100644 legacy-modules/nixos-modules/server/home-assistant/proxy.nix delete mode 100644 legacy-modules/nixos-modules/server/home-assistant/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/immich/database.nix delete mode 100644 legacy-modules/nixos-modules/server/immich/default.nix delete mode 100644 legacy-modules/nixos-modules/server/immich/fail2ban.nix delete mode 100644 legacy-modules/nixos-modules/server/immich/proxy.nix delete mode 100644 legacy-modules/nixos-modules/server/immich/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/jackett/default.nix delete mode 100644 legacy-modules/nixos-modules/server/jackett/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/jellyfin/default.nix delete mode 100644 legacy-modules/nixos-modules/server/jellyfin/fail2ban.nix delete mode 100644 legacy-modules/nixos-modules/server/jellyfin/jellyfin.nix delete mode 100644 legacy-modules/nixos-modules/server/jellyfin/proxy.nix delete mode 100644 legacy-modules/nixos-modules/server/jellyfin/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/lidarr/default.nix delete mode 100644 legacy-modules/nixos-modules/server/lidarr/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/network_storage/default.nix delete mode 100644 legacy-modules/nixos-modules/server/network_storage/network_storage.nix delete mode 100644 legacy-modules/nixos-modules/server/network_storage/nfs.nix delete mode 100644 legacy-modules/nixos-modules/server/panoramax/database.nix delete mode 100644 legacy-modules/nixos-modules/server/panoramax/default.nix delete mode 100644 legacy-modules/nixos-modules/server/panoramax/fail2ban.nix delete mode 100644 legacy-modules/nixos-modules/server/panoramax/panoramax.nix delete mode 100644 legacy-modules/nixos-modules/server/panoramax/proxy.nix delete mode 100644 legacy-modules/nixos-modules/server/panoramax/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/paperless/database.nix delete mode 100644 legacy-modules/nixos-modules/server/paperless/default.nix delete mode 100644 legacy-modules/nixos-modules/server/paperless/fail2ban.nix delete mode 100644 legacy-modules/nixos-modules/server/paperless/paperless.nix delete mode 100644 legacy-modules/nixos-modules/server/paperless/proxy.nix delete mode 100644 legacy-modules/nixos-modules/server/paperless/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/postgres/default.nix delete mode 100644 legacy-modules/nixos-modules/server/postgres/postgres.nix delete mode 100644 legacy-modules/nixos-modules/server/postgres/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/qbittorent/default.nix delete mode 100644 legacy-modules/nixos-modules/server/qbittorent/qbittorent.nix delete mode 100644 legacy-modules/nixos-modules/server/qbittorent/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/radarr/default.nix delete mode 100644 legacy-modules/nixos-modules/server/radarr/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/reverseProxy/default.nix delete mode 100644 legacy-modules/nixos-modules/server/reverseProxy/reverseProxy.nix delete mode 100644 legacy-modules/nixos-modules/server/reverseProxy/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/searx/default.nix delete mode 100644 legacy-modules/nixos-modules/server/searx/proxy.nix delete mode 100644 legacy-modules/nixos-modules/server/searx/searx.nix delete mode 100644 legacy-modules/nixos-modules/server/sonarr/default.nix delete mode 100644 legacy-modules/nixos-modules/server/sonarr/storage.nix delete mode 100644 legacy-modules/nixos-modules/server/wyoming.nix delete mode 100644 legacy-modules/nixos-modules/ssh.nix delete mode 100644 legacy-modules/nixos-modules/steam.nix delete mode 100644 legacy-modules/nixos-modules/storage/impermanence.nix delete mode 100644 legacy-modules/nixos-modules/storage/storage.nix delete mode 100644 legacy-modules/nixos-modules/storage/submodules/dataset.nix delete mode 100644 legacy-modules/nixos-modules/storage/submodules/impermanenceDataset.nix delete mode 100644 legacy-modules/nixos-modules/storage/zfs.nix delete mode 100644 legacy-modules/nixos-modules/sync/default.nix delete mode 100644 legacy-modules/nixos-modules/sync/storage.nix delete mode 100644 legacy-modules/nixos-modules/sync/sync.nix delete mode 100644 legacy-modules/nixos-modules/system.nix delete mode 100644 legacy-modules/nixos-modules/tailscale/default.nix delete mode 100644 legacy-modules/nixos-modules/tailscale/storage.nix delete mode 100644 legacy-modules/nixos-modules/tailscale/tailscale.nix delete mode 100644 legacy-modules/nixos-modules/users.nix create mode 100644 modules/nixos/desktop.nix create mode 100644 modules/nixos/hardware.nix create mode 100644 modules/nixos/home-manager-adaptors/default.nix create mode 100644 modules/nixos/home-manager-adaptors/flipperzero.nix create mode 100644 modules/nixos/home-manager-adaptors/i18n.nix create mode 100644 modules/nixos/home-manager-adaptors/openssh.nix create mode 100644 modules/nixos/home-manager-adaptors/steam.nix create mode 100644 modules/nixos/i18n.nix create mode 100644 modules/nixos/nixos-modules.nix create mode 100644 modules/nixos/programs/actual/actual.nix create mode 100644 modules/nixos/programs/actual/default.nix create mode 100644 modules/nixos/programs/actual/fail2ban.nix create mode 100644 modules/nixos/programs/actual/proxy.nix create mode 100644 modules/nixos/programs/actual/storage.nix create mode 100644 modules/nixos/programs/bazarr/default.nix create mode 100644 modules/nixos/programs/bazarr/storage.nix create mode 100644 modules/nixos/programs/crab-hole/crab-hole.nix create mode 100644 modules/nixos/programs/crab-hole/default.nix create mode 100644 modules/nixos/programs/crab-hole/storage.nix create mode 100644 modules/nixos/programs/default.nix create mode 100644 modules/nixos/programs/fail2ban/default.nix create mode 100644 modules/nixos/programs/fail2ban/fail2ban.nix create mode 100644 modules/nixos/programs/fail2ban/storage.nix create mode 100644 modules/nixos/programs/flaresolverr/default.nix create mode 100644 modules/nixos/programs/flaresolverr/storage.nix create mode 100644 modules/nixos/programs/forgejo/database.nix create mode 100644 modules/nixos/programs/forgejo/default.nix create mode 100644 modules/nixos/programs/forgejo/fail2ban.nix create mode 100644 modules/nixos/programs/forgejo/forgejo.nix create mode 100644 modules/nixos/programs/forgejo/proxy.nix create mode 100644 modules/nixos/programs/forgejo/storage.nix create mode 100644 modules/nixos/programs/home-assistant/database.nix create mode 100644 modules/nixos/programs/home-assistant/default.nix create mode 100644 modules/nixos/programs/home-assistant/extensions/default.nix create mode 100644 modules/nixos/programs/home-assistant/extensions/jellyfin.nix create mode 100644 modules/nixos/programs/home-assistant/extensions/sonos.nix create mode 100644 modules/nixos/programs/home-assistant/extensions/wyoming.nix create mode 100644 modules/nixos/programs/home-assistant/fail2ban.nix create mode 100644 modules/nixos/programs/home-assistant/home-assistant.nix create mode 100644 modules/nixos/programs/home-assistant/proxy.nix create mode 100644 modules/nixos/programs/home-assistant/storage.nix create mode 100644 modules/nixos/programs/immich/database.nix create mode 100644 modules/nixos/programs/immich/default.nix create mode 100644 modules/nixos/programs/immich/fail2ban.nix create mode 100644 modules/nixos/programs/immich/proxy.nix create mode 100644 modules/nixos/programs/immich/storage.nix create mode 100644 modules/nixos/programs/jackett/default.nix create mode 100644 modules/nixos/programs/jackett/storage.nix create mode 100644 modules/nixos/programs/jellyfin/default.nix create mode 100644 modules/nixos/programs/jellyfin/fail2ban.nix create mode 100644 modules/nixos/programs/jellyfin/jellyfin.nix create mode 100644 modules/nixos/programs/jellyfin/proxy.nix create mode 100644 modules/nixos/programs/jellyfin/storage.nix create mode 100644 modules/nixos/programs/lidarr/default.nix create mode 100644 modules/nixos/programs/lidarr/storage.nix create mode 100644 modules/nixos/programs/network_storage/default.nix create mode 100644 modules/nixos/programs/network_storage/network_storage.nix create mode 100644 modules/nixos/programs/network_storage/nfs.nix create mode 100644 modules/nixos/programs/panoramax/database.nix create mode 100644 modules/nixos/programs/panoramax/default.nix create mode 100644 modules/nixos/programs/panoramax/fail2ban.nix create mode 100644 modules/nixos/programs/panoramax/panoramax.nix create mode 100644 modules/nixos/programs/panoramax/proxy.nix create mode 100644 modules/nixos/programs/panoramax/storage.nix create mode 100644 modules/nixos/programs/paperless/database.nix create mode 100644 modules/nixos/programs/paperless/default.nix create mode 100644 modules/nixos/programs/paperless/fail2ban.nix create mode 100644 modules/nixos/programs/paperless/paperless.nix create mode 100644 modules/nixos/programs/paperless/proxy.nix create mode 100644 modules/nixos/programs/paperless/storage.nix create mode 100644 modules/nixos/programs/postgres/default.nix create mode 100644 modules/nixos/programs/postgres/postgres.nix create mode 100644 modules/nixos/programs/postgres/storage.nix create mode 100644 modules/nixos/programs/qbittorent/default.nix create mode 100644 modules/nixos/programs/qbittorent/qbittorent.nix create mode 100644 modules/nixos/programs/qbittorent/storage.nix create mode 100644 modules/nixos/programs/radarr/default.nix create mode 100644 modules/nixos/programs/radarr/storage.nix create mode 100644 modules/nixos/programs/reverseProxy/default.nix create mode 100644 modules/nixos/programs/reverseProxy/reverseProxy.nix create mode 100644 modules/nixos/programs/reverseProxy/storage.nix create mode 100644 modules/nixos/programs/searx/default.nix create mode 100644 modules/nixos/programs/searx/proxy.nix create mode 100644 modules/nixos/programs/searx/searx.nix create mode 100644 modules/nixos/programs/sonarr/default.nix create mode 100644 modules/nixos/programs/sonarr/storage.nix create mode 100644 modules/nixos/programs/steam.nix create mode 100644 modules/nixos/programs/sync/default.nix create mode 100644 modules/nixos/programs/sync/storage.nix create mode 100644 modules/nixos/programs/sync/sync.nix create mode 100644 modules/nixos/programs/tailscale/default.nix create mode 100644 modules/nixos/programs/tailscale/storage.nix create mode 100644 modules/nixos/programs/tailscale/tailscale.nix create mode 100644 modules/nixos/programs/wyoming.nix create mode 100644 modules/nixos/ssh.nix create mode 100644 modules/nixos/storage/dataset.nix rename {legacy-modules/nixos-modules => modules/nixos}/storage/default.nix (67%) create mode 100644 modules/nixos/storage/impermanence-dataset.nix create mode 100644 modules/nixos/storage/impermanence.nix create mode 100644 modules/nixos/storage/storage.nix create mode 100644 modules/nixos/storage/zfs.nix create mode 100644 modules/nixos/system.nix create mode 100644 modules/nixos/users.nix diff --git a/legacy-modules/nixos-modules/ai.nix b/legacy-modules/nixos-modules/ai.nix deleted file mode 100644 index d8cd63d..0000000 --- a/legacy-modules/nixos-modules/ai.nix +++ /dev/null @@ -1,46 +0,0 @@ -{lib, ...}: { - options.host = { - ai = { - enable = lib.mkEnableOption "should we use AI on this machine"; - models = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { - options = { - name = lib.mkOption { - type = lib.types.str; - default = name; - }; - model = lib.mkOption { - type = lib.types.str; - }; - provider = lib.mkOption { - type = lib.types.str; - default = "ollama"; - }; - apiBase = lib.mkOption { - type = lib.types.str; - default = "http://localhost:11434"; - }; - roles = lib.mkOption { - type = lib.types.listOf (lib.types.enum [ - "chat" - "autocomplete" - "embed" - "rerank" - "edit" - "apply" - "summarize" - ]); - default = []; - }; - }; - })); - }; - default = {}; - }; - }; - - config = { - # TODO: configure ollama to download any modules listed in options.host.ai.models.{name}.model if options.host.ai.models.{name}.apiBase is localhost - # TODO: if we have any models that have a non localhost options.host.ai.models.{name}.apiBase then set services.ollama.enable to a lib.mkAfter true - }; -} diff --git a/legacy-modules/nixos-modules/default.nix b/legacy-modules/nixos-modules/default.nix deleted file mode 100644 index 34e041e..0000000 --- a/legacy-modules/nixos-modules/default.nix +++ /dev/null @@ -1,23 +0,0 @@ -# this folder container modules that are for nixos only -{...}: { - imports = [ - ./home-manager - ./system.nix - ./hardware.nix - ./users.nix - ./desktop.nix - ./ssh.nix - ./i18n.nix - ./sync - ./ollama - ./ai.nix - ./tailscale - ./steam.nix - ./server - ./storage - ]; - - nixpkgs.config.permittedInsecurePackages = [ - "dotnet-sdk-6.0.428" - ]; -} diff --git a/legacy-modules/nixos-modules/desktop.nix b/legacy-modules/nixos-modules/desktop.nix deleted file mode 100644 index 66a2433..0000000 --- a/legacy-modules/nixos-modules/desktop.nix +++ /dev/null @@ -1,84 +0,0 @@ -{ - lib, - pkgs, - config, - ... -}: { - options.host.desktop.enable = lib.mkEnableOption "should desktop configuration be enabled"; - - config = lib.mkMerge [ - { - host.desktop.enable = lib.mkDefault true; - } - (lib.mkIf config.host.desktop.enable { - environment.gnome.excludePackages = with pkgs; [ - xterm # default terminal - atomix # puzzle game - cheese # webcam tool - epiphany # web browser - geary # email reader - gedit # text editor - decibels # audio player - gnome-characters # character set viewer - gnome-music # music player - gnome-photos # photo viewer - gnome-logs # log viewer - gnome-maps # map viewer - gnome-tour # welcome tour - hitori # sudoku game - iagno # go game - tali # poker game - yelp # help viewer - ]; - services = { - # Enable CUPS to print documents. - printing = { - enable = true; - drivers = [ - pkgs.hplip - pkgs.gutenprint - pkgs.gutenprintBin - ]; - }; - - xserver = { - # Enable the X11 windowing system. - enable = true; - - # Get rid of xTerm - desktopManager.xterm.enable = false; - excludePackages = with pkgs; [ - xterm - ]; - }; - - # Enable the GNOME Desktop Environment. - displayManager.gdm.enable = true; - desktopManager.gnome.enable = true; - - pipewire = { - enable = true; - alsa.enable = true; - alsa.support32Bit = true; - pulse.enable = true; - - # If you want to use JACK applications, uncomment this - #jack.enable = true; - - # use the example session manager (no others are packaged yet so this is enabled by default, - # no need to redefine it in your config for now) - #media-session.enable = true; - }; - automatic-timezoned = { - enable = true; - }; - - # Enable sound with pipewire. - pulseaudio.enable = false; - }; - - # enable RealtimeKit for pulse audio - security.rtkit.enable = true; - }) - ]; -} diff --git a/legacy-modules/nixos-modules/hardware.nix b/legacy-modules/nixos-modules/hardware.nix deleted file mode 100644 index 07e6fa8..0000000 --- a/legacy-modules/nixos-modules/hardware.nix +++ /dev/null @@ -1,34 +0,0 @@ -{ - lib, - config, - pkgs, - ... -}: { - options.host.hardware = { - piperMouse = { - enable = lib.mkEnableOption "host has a piper mouse"; - }; - viaKeyboard = { - enable = lib.mkEnableOption "host has a via keyboard"; - }; - openRGB = { - enable = lib.mkEnableOption "host has open rgb hardware"; - }; - graphicsAcceleration = { - enable = lib.mkEnableOption "host has a gpu for graphical acceleration"; - }; - directAccess = { - enable = lib.mkEnableOption "can a host be used on its own"; - }; - }; - config = lib.mkMerge [ - (lib.mkIf config.host.hardware.piperMouse.enable { - services.ratbagd.enable = true; - }) - (lib.mkIf config.host.hardware.viaKeyboard.enable { - hardware.keyboard.qmk.enable = true; - - services.udev.packages = [pkgs.via]; - }) - ]; -} diff --git a/legacy-modules/nixos-modules/home-manager/default.nix b/legacy-modules/nixos-modules/home-manager/default.nix deleted file mode 100644 index 10f86c7..0000000 --- a/legacy-modules/nixos-modules/home-manager/default.nix +++ /dev/null @@ -1,9 +0,0 @@ -# modules in this folder are to adapt home-manager modules configs to nixos-module configs -{...}: { - imports = [ - ./flipperzero.nix - ./i18n.nix - ./openssh.nix - ./steam.nix - ]; -} diff --git a/legacy-modules/nixos-modules/home-manager/flipperzero.nix b/legacy-modules/nixos-modules/home-manager/flipperzero.nix deleted file mode 100644 index 6c94773..0000000 --- a/legacy-modules/nixos-modules/home-manager/flipperzero.nix +++ /dev/null @@ -1,9 +0,0 @@ -{ - lib, - config, - ... -}: let - home-users = lib.attrsets.mapAttrsToList (_: user: user) config.home-manager.users; -in { - hardware.flipperzero.enable = lib.lists.any (home-user: home-user.hardware.flipperzero.enable) home-users; -} diff --git a/legacy-modules/nixos-modules/home-manager/i18n.nix b/legacy-modules/nixos-modules/home-manager/i18n.nix deleted file mode 100644 index 78b86fa..0000000 --- a/legacy-modules/nixos-modules/home-manager/i18n.nix +++ /dev/null @@ -1,26 +0,0 @@ -{ - lib, - config, - ... -}: let - home-users = lib.attrsets.mapAttrsToList (_: user: user) config.home-manager.users; -in { - config = { - i18n.supportedLocales = - lib.unique - (builtins.map (l: (lib.replaceStrings ["utf8" "utf-8" "UTF8"] ["UTF-8" "UTF-8" "UTF-8"] l) + "/UTF-8") ( - [ - "C.UTF-8" - "en_US.UTF-8" - config.i18n.defaultLocale - ] - ++ (lib.attrValues (lib.filterAttrs (n: v: n != "LANGUAGE") config.i18n.extraLocaleSettings)) - ++ ( - map (user-config: user-config.i18n.defaultLocale) home-users - ) - ++ (lib.lists.flatten ( - map (user-config: lib.attrValues (lib.filterAttrs (n: v: n != "LANGUAGE") user-config.i18n.extraLocaleSettings)) home-users - )) - )); - }; -} diff --git a/legacy-modules/nixos-modules/home-manager/openssh.nix b/legacy-modules/nixos-modules/home-manager/openssh.nix deleted file mode 100644 index 31a785f..0000000 --- a/legacy-modules/nixos-modules/home-manager/openssh.nix +++ /dev/null @@ -1,11 +0,0 @@ -{ - config, - lib, - ... -}: { - users.users = - lib.attrsets.mapAttrs (name: value: { - openssh.authorizedKeys.keys = value.programs.openssh.authorizedKeys; - }) - config.home-manager.users; -} diff --git a/legacy-modules/nixos-modules/home-manager/steam.nix b/legacy-modules/nixos-modules/home-manager/steam.nix deleted file mode 100644 index d151bca..0000000 --- a/legacy-modules/nixos-modules/home-manager/steam.nix +++ /dev/null @@ -1,18 +0,0 @@ -{ - lib, - config, - ... -}: let - setupSteam = - lib.lists.any - (value: value) - (lib.attrsets.mapAttrsToList (name: value: value.programs.steam.enable) config.home-manager.users); -in { - config = lib.mkIf setupSteam { - programs.steam = { - enable = true; - # TODO: figure out how to not install steam here - # package = lib.mkDefault pkgs.emptyFile; - }; - }; -} diff --git a/legacy-modules/nixos-modules/i18n.nix b/legacy-modules/nixos-modules/i18n.nix deleted file mode 100644 index eada12c..0000000 --- a/legacy-modules/nixos-modules/i18n.nix +++ /dev/null @@ -1,3 +0,0 @@ -{...}: { - i18n.defaultLocale = "en_IE.UTF-8"; -} diff --git a/legacy-modules/nixos-modules/ollama/default.nix b/legacy-modules/nixos-modules/ollama/default.nix deleted file mode 100644 index 896526a..0000000 --- a/legacy-modules/nixos-modules/ollama/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{...}: { - imports = [ - ./ollama.nix - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/ollama/ollama.nix b/legacy-modules/nixos-modules/ollama/ollama.nix deleted file mode 100644 index dc7cdd9..0000000 --- a/legacy-modules/nixos-modules/ollama/ollama.nix +++ /dev/null @@ -1,32 +0,0 @@ -{ - config, - lib, - ... -}: { - options = { - services.ollama.exposePort = lib.mkEnableOption "should we expose ollama on tailscale"; - }; - - config = lib.mkIf config.services.ollama.enable ( - lib.mkMerge [ - { - services.ollama = { - # TODO: these should match whats set in the users file - group = "ollama"; - user = "ollama"; - }; - } - (lib.mkIf config.services.ollama.exposePort (let - ports = [ - config.services.ollama.port - ]; - in { - services.ollama.host = "0.0.0.0"; - networking.firewall.interfaces.${config.services.tailscale.interfaceName} = { - allowedTCPPorts = ports; - allowedUDPPorts = ports; - }; - })) - ] - ); -} diff --git a/legacy-modules/nixos-modules/ollama/storage.nix b/legacy-modules/nixos-modules/ollama/storage.nix deleted file mode 100644 index 6ab0fc8..0000000 --- a/legacy-modules/nixos-modules/ollama/storage.nix +++ /dev/null @@ -1,37 +0,0 @@ -{ - config, - lib, - ... -}: { - options = { - services.ollama.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.ollama.enable && config.storage.impermanence.enable; - }; - }; - - config = lib.mkIf (config.services.ollama.enable) { - storage.datasets.replicate."system/root" = { - directories."/var/lib/private/ollama" = lib.mkIf config.services.ollama.impermanence.enable { - enable = true; - owner.name = config.services.ollama.user; - group.name = config.services.ollama.group; - owner.permissions = { - read = true; - write = true; - execute = false; - }; - group.permissions = { - read = false; - write = false; - execute = false; - }; - other.permissions = { - read = false; - write = false; - execute = false; - }; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/actual/actual.nix b/legacy-modules/nixos-modules/server/actual/actual.nix deleted file mode 100644 index 4cca449..0000000 --- a/legacy-modules/nixos-modules/server/actual/actual.nix +++ /dev/null @@ -1,24 +0,0 @@ -{ - lib, - config, - ... -}: let - const = import ./const.nix; - dataDirectory = const.dataDirectory; -in { - options.services.actual = { - port = lib.mkOption { - type = lib.types.port; - description = "The port to listen on"; - default = 5006; - }; - }; - config = lib.mkIf config.services.actual.enable { - services.actual = { - settings = { - port = config.services.actual.port; - dataDir = dataDirectory; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/actual/const.nix b/legacy-modules/nixos-modules/server/actual/const.nix deleted file mode 100644 index 14b715e..0000000 --- a/legacy-modules/nixos-modules/server/actual/const.nix +++ /dev/null @@ -1,3 +0,0 @@ -{ - dataDirectory = "/var/lib/private/actual"; -} diff --git a/legacy-modules/nixos-modules/server/actual/default.nix b/legacy-modules/nixos-modules/server/actual/default.nix deleted file mode 100644 index 99778af..0000000 --- a/legacy-modules/nixos-modules/server/actual/default.nix +++ /dev/null @@ -1,8 +0,0 @@ -{ - imports = [ - ./actual.nix - ./proxy.nix - ./fail2ban.nix - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/actual/fail2ban.nix b/legacy-modules/nixos-modules/server/actual/fail2ban.nix deleted file mode 100644 index 3ad754e..0000000 --- a/legacy-modules/nixos-modules/server/actual/fail2ban.nix +++ /dev/null @@ -1,9 +0,0 @@ -{ - lib, - config, - ... -}: { - config = lib.mkIf (config.services.actual.enable && config.services.fail2ban.enable) { - # TODO: configuration for fail2ban for actual - }; -} diff --git a/legacy-modules/nixos-modules/server/actual/proxy.nix b/legacy-modules/nixos-modules/server/actual/proxy.nix deleted file mode 100644 index 9d37574..0000000 --- a/legacy-modules/nixos-modules/server/actual/proxy.nix +++ /dev/null @@ -1,34 +0,0 @@ -{ - lib, - config, - ... -}: { - options.services.actual = { - domain = lib.mkOption { - type = lib.types.str; - description = "domain that actual will be hosted at"; - default = "actual.arpa"; - }; - extraDomains = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "extra domains that should be configured for actual"; - default = []; - }; - reverseProxy.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.actual.enable && config.services.reverseProxy.enable; - }; - }; - - config = lib.mkIf config.services.actual.reverseProxy.enable { - services.reverseProxy.services.actual = { - target = "http://localhost:${toString config.services.actual.settings.port}"; - domain = config.services.actual.domain; - extraDomains = config.services.actual.extraDomains; - - settings = { - forwardHeaders.enable = true; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/actual/storage.nix b/legacy-modules/nixos-modules/server/actual/storage.nix deleted file mode 100644 index d6b904e..0000000 --- a/legacy-modules/nixos-modules/server/actual/storage.nix +++ /dev/null @@ -1,22 +0,0 @@ -{ - lib, - config, - ... -}: let - const = import ./const.nix; - dataDirectory = const.dataDirectory; -in { - options.services.actual.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.actual.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.actual.enable { - storage.datasets.replicate."system/root" = { - directories."${dataDirectory}" = lib.mkIf config.services.actual.impermanence.enable { - owner.name = "actual"; - group.name = "actual"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/bazarr/default.nix b/legacy-modules/nixos-modules/server/bazarr/default.nix deleted file mode 100644 index cb2a5f0..0000000 --- a/legacy-modules/nixos-modules/server/bazarr/default.nix +++ /dev/null @@ -1,5 +0,0 @@ -{...}: { - imports = [ - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/bazarr/storage.nix b/legacy-modules/nixos-modules/server/bazarr/storage.nix deleted file mode 100644 index a243d4c..0000000 --- a/legacy-modules/nixos-modules/server/bazarr/storage.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - lib, - config, - ... -}: let - bazarr_data_directory = "/var/lib/bazarr"; -in { - options.services.bazarr.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.bazarr.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.bazarr.enable { - storage.datasets.replicate."system/root" = { - directories."${bazarr_data_directory}" = lib.mkIf config.services.bazarr.impermanence.enable { - owner.name = "bazarr"; - group.name = "bazarr"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/crab-hole/crab-hole.nix b/legacy-modules/nixos-modules/server/crab-hole/crab-hole.nix deleted file mode 100644 index d76323a..0000000 --- a/legacy-modules/nixos-modules/server/crab-hole/crab-hole.nix +++ /dev/null @@ -1,193 +0,0 @@ -{ - config, - lib, - ... -}: let - cfg = config.services.crab-hole; -in { - options.services.crab-hole = { - port = lib.mkOption { - type = lib.types.port; - default = 8080; - description = "Port for the crab-hole API to listen on."; - }; - - openFirewall = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Whether to open the firewall for the crab-hole API port."; - }; - - listen = lib.mkOption { - type = lib.types.str; - default = "0.0.0.0"; - description = "Address for the crab-hole API to listen on."; - }; - - show_doc = lib.mkEnableOption "OpenAPI documentation (loads content from third party websites)"; - - downstreams = { - host = { - enable = lib.mkEnableOption "host downstream DNS server accessible from network on all interfaces"; - port = lib.mkOption { - type = lib.types.port; - default = 53; - description = "Port for the host downstream DNS server to listen on."; - }; - openFirewall = lib.mkEnableOption "automatic port forwarding for the host downstream"; - disableSystemdResolved = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to automatically disable systemd-resolved when using port 53. Set to false if you want to handle the conflict manually."; - }; - }; - }; - - extraDownstreams = lib.mkOption { - type = lib.types.listOf (lib.types.submodule { - options = { - protocol = lib.mkOption { - type = lib.types.enum ["udp" "tcp" "tls" "https" "quic"]; - description = "Protocol for the downstream server."; - }; - - listen = lib.mkOption { - type = lib.types.str; - description = "Address to listen on for downstream connections."; - }; - - port = lib.mkOption { - type = lib.types.port; - description = "Port to listen on for downstream connections."; - }; - }; - }); - default = []; - description = "List of additional downstream DNS server configurations."; - }; - - upstreams = { - cloudFlare = { - enable = lib.mkEnableOption "Cloudflare DNS over TLS upstream servers (1.1.1.1 and 1.0.0.1)"; - }; - }; - - extraUpstreams = lib.mkOption { - type = lib.types.listOf (lib.types.submodule { - options = { - socket_addr = lib.mkOption { - type = lib.types.str; - description = "Socket address of the upstream DNS server (e.g., \"1.1.1.1:853\" or \"[2606:4700:4700::1111]:853\")."; - }; - - protocol = lib.mkOption { - type = lib.types.enum ["udp" "tcp" "tls" "https" "quic"]; - description = "Protocol to use for upstream DNS queries."; - }; - }; - }); - default = []; - description = "List of additional upstream DNS server configurations."; - }; - - blocklists = { - ad_malware = { - enable = lib.mkEnableOption "Host file for blocking ads and malware"; - url = lib.mkOption { - type = lib.types.str; - default = "http://sbc.io/hosts/hosts"; - description = "URL of the ad and malware blocklist host file"; - }; - }; - }; - - extraBlocklists = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = []; - description = "Additional blocklist URLs to be added to the configuration"; - }; - }; - - config = lib.mkIf cfg.enable { - # Assertions for proper configuration - assertions = [ - { - assertion = !(cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && config.services.resolved.enable && cfg.downstreams.host.disableSystemdResolved); - message = "crab-hole host downstream cannot use port 53 while systemd-resolved is enabled. Either disable systemd-resolved or use a different port."; - } - { - assertion = !(cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && !cfg.downstreams.host.disableSystemdResolved && config.services.resolved.enable); - message = "crab-hole host downstream is configured to use port 53 but systemd-resolved is still enabled and disableSystemdResolved is false. Set disableSystemdResolved = true or manually disable systemd-resolved."; - } - ]; - - # Automatically disable systemd-resolved if using port 53 - services.resolved.enable = lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && cfg.downstreams.host.disableSystemdResolved) (lib.mkForce false); - - # Configure DNS nameservers when disabling systemd-resolved - networking.nameservers = lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && cfg.downstreams.host.disableSystemdResolved) (lib.mkDefault ["127.0.0.1" "1.1.1.1" "8.8.8.8"]); - - services.crab-hole.settings = lib.mkMerge [ - { - api = { - port = cfg.port; - listen = cfg.listen; - show_doc = cfg.show_doc; - }; - downstream = cfg.extraDownstreams; - upstream.name_servers = cfg.extraUpstreams; - blocklist.lists = cfg.extraBlocklists; - } - (lib.mkIf cfg.blocklists.ad_malware.enable { - blocklist.lists = [cfg.blocklists.ad_malware.url]; - }) - (lib.mkIf cfg.downstreams.host.enable { - downstream = [ - { - protocol = "udp"; - listen = "0.0.0.0"; - port = cfg.downstreams.host.port; - } - ]; - }) - (lib.mkIf cfg.upstreams.cloudFlare.enable { - upstream.name_servers = [ - { - socket_addr = "1.1.1.1:853"; - protocol = "tls"; - tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; - trust_nx_responses = false; - } - { - socket_addr = "1.0.0.1:853"; - protocol = "tls"; - tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; - trust_nx_responses = false; - } - { - socket_addr = "[2606:4700:4700::1111]:853"; - protocol = "tls"; - tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; - trust_nx_responses = false; - } - { - socket_addr = "[2606:4700:4700::1001]:853"; - protocol = "tls"; - tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; - trust_nx_responses = false; - } - ]; - }) - ]; - - # Open firewall if requested - networking.firewall = lib.mkMerge [ - (lib.mkIf cfg.openFirewall { - allowedTCPPorts = [cfg.port]; - }) - (lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.openFirewall) { - allowedUDPPorts = [cfg.downstreams.host.port]; - }) - ]; - }; -} diff --git a/legacy-modules/nixos-modules/server/crab-hole/default.nix b/legacy-modules/nixos-modules/server/crab-hole/default.nix deleted file mode 100644 index 9f990c5..0000000 --- a/legacy-modules/nixos-modules/server/crab-hole/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{...}: { - imports = [ - ./crab-hole.nix - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/crab-hole/storage.nix b/legacy-modules/nixos-modules/server/crab-hole/storage.nix deleted file mode 100644 index 827fb25..0000000 --- a/legacy-modules/nixos-modules/server/crab-hole/storage.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - lib, - config, - ... -}: let - workingDirectory = "/var/lib/private/crab-hole"; -in { - options.services.crab-hole.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.crab-hole.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.crab-hole.enable { - storage.datasets.replicate."system/root" = { - directories."${workingDirectory}" = lib.mkIf config.services.crab-hole.impermanence.enable { - owner.name = "crab-hole"; - group.name = "crab-hole"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/default.nix b/legacy-modules/nixos-modules/server/default.nix deleted file mode 100644 index 2b33089..0000000 --- a/legacy-modules/nixos-modules/server/default.nix +++ /dev/null @@ -1,26 +0,0 @@ -{...}: { - imports = [ - ./reverseProxy - ./fail2ban - ./postgres - ./network_storage - - ./actual - ./bazarr - ./crab-hole - ./flaresolverr - ./forgejo - ./home-assistant - ./immich - ./jackett - ./jellyfin - ./lidarr - ./panoramax - ./paperless - ./qbittorent - ./radarr - ./searx - ./sonarr - ./wyoming.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/fail2ban/default.nix b/legacy-modules/nixos-modules/server/fail2ban/default.nix deleted file mode 100644 index 84a46d4..0000000 --- a/legacy-modules/nixos-modules/server/fail2ban/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{...}: { - imports = [ - ./fail2ban.nix - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/fail2ban/fail2ban.nix b/legacy-modules/nixos-modules/server/fail2ban/fail2ban.nix deleted file mode 100644 index 261c68f..0000000 --- a/legacy-modules/nixos-modules/server/fail2ban/fail2ban.nix +++ /dev/null @@ -1,51 +0,0 @@ -{ - lib, - pkgs, - config, - ... -}: { - config = lib.mkIf config.services.fail2ban.enable { - environment.etc = { - "fail2ban/filter.d/nginx.local".text = lib.mkIf config.services.nginx.enable ( - pkgs.lib.mkDefault (pkgs.lib.mkAfter '' - [Definition] - failregex = "limiting requests, excess:.* by zone.*client: " - '') - ); - }; - - services.fail2ban = { - maxretry = 5; - ignoreIP = [ - # Whitelist local networks - "10.0.0.0/8" - "172.16.0.0/12" - "192.168.0.0/16" - - # tail scale tailnet - "100.64.0.0/10" - "fd7a:115c:a1e0::/48" - ]; - bantime = "24h"; # Ban IPs for one day on the first ban - bantime-increment = { - enable = true; # Enable increment of bantime after each violation - formula = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)"; - maxtime = "168h"; # Do not ban for more than 1 week - overalljails = true; # Calculate the ban time based on all the violations - }; - jails = { - nginx-iptables.settings = lib.mkIf config.services.nginx.enable { - enabled = true; - filter = "nginx"; - action = ''iptables-multiport[name=HTTP, port="http,https"]''; - backend = "auto"; - findtime = 600; - bantime = 600; - maxretry = 5; - }; - # TODO; figure out if there is any fail2ban things we can do on searx - # searx-iptables.settings = lib.mkIf config.services.searx.enable {}; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/fail2ban/storage.nix b/legacy-modules/nixos-modules/server/fail2ban/storage.nix deleted file mode 100644 index 1ef02c7..0000000 --- a/legacy-modules/nixos-modules/server/fail2ban/storage.nix +++ /dev/null @@ -1,22 +0,0 @@ -{ - lib, - config, - ... -}: let - dataFolder = "/var/lib/fail2ban"; - dataFile = "fail2ban.sqlite3"; -in { - options.services.fail2ban.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.fail2ban.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.fail2ban.enable { - storage.datasets.replicate."system/root" = { - directories."${dataFolder}" = lib.mkIf config.services.fail2ban.impermanence.enable { - owner.name = "fail2ban"; - group.name = "fail2ban"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/flaresolverr/default.nix b/legacy-modules/nixos-modules/server/flaresolverr/default.nix deleted file mode 100644 index cb2a5f0..0000000 --- a/legacy-modules/nixos-modules/server/flaresolverr/default.nix +++ /dev/null @@ -1,5 +0,0 @@ -{...}: { - imports = [ - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/flaresolverr/storage.nix b/legacy-modules/nixos-modules/server/flaresolverr/storage.nix deleted file mode 100644 index 919318c..0000000 --- a/legacy-modules/nixos-modules/server/flaresolverr/storage.nix +++ /dev/null @@ -1,19 +0,0 @@ -{ - lib, - config, - ... -}: { - options.services.flaresolverr.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.flaresolverr.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.flaresolverr.enable { - storage.datasets.replicate."system/root" = { - directories."/var/lib/flaresolverr" = lib.mkIf config.services.flaresolverr.impermanence.enable { - owner.name = "flaresolverr"; - group.name = "flaresolverr"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/forgejo/const.nix b/legacy-modules/nixos-modules/server/forgejo/const.nix deleted file mode 100644 index 10e3974..0000000 --- a/legacy-modules/nixos-modules/server/forgejo/const.nix +++ /dev/null @@ -1,4 +0,0 @@ -{ - httpPort = 8081; - sshPort = 22222; -} diff --git a/legacy-modules/nixos-modules/server/forgejo/database.nix b/legacy-modules/nixos-modules/server/forgejo/database.nix deleted file mode 100644 index bb8781c..0000000 --- a/legacy-modules/nixos-modules/server/forgejo/database.nix +++ /dev/null @@ -1,32 +0,0 @@ -{ - lib, - config, - ... -}: let - usingPostgres = config.services.forgejo.database.type == "postgres"; -in { - config = lib.mkIf config.services.forgejo.enable { - assertions = [ - { - assertion = !usingPostgres || config.services.postgresql.enable; - message = "PostgreSQL must be enabled when Forgejo database type is postgres"; - } - { - assertion = !(usingPostgres && config.services.forgejo.database.createDatabase) || (builtins.any (db: db == "forgejo") config.services.postgresql.ensureDatabases); - message = "Forgejo built-in database creation failed - expected 'forgejo' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}"; - } - { - assertion = !(usingPostgres && config.services.forgejo.database.createDatabase) || (builtins.any (user: user.name == "forgejo") config.services.postgresql.ensureUsers); - message = "Forgejo built-in user creation failed - expected user 'forgejo' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}"; - } - ]; - - services.forgejo.database.createDatabase = lib.mkDefault usingPostgres; - - systemd.services.forgejo = lib.mkIf usingPostgres { - requires = [ - config.systemd.services.postgresql.name - ]; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/forgejo/default.nix b/legacy-modules/nixos-modules/server/forgejo/default.nix deleted file mode 100644 index c990e57..0000000 --- a/legacy-modules/nixos-modules/server/forgejo/default.nix +++ /dev/null @@ -1,9 +0,0 @@ -{ - imports = [ - ./forgejo.nix - ./proxy.nix - ./database.nix - ./fail2ban.nix - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/forgejo/fail2ban.nix b/legacy-modules/nixos-modules/server/forgejo/fail2ban.nix deleted file mode 100644 index dfe221a..0000000 --- a/legacy-modules/nixos-modules/server/forgejo/fail2ban.nix +++ /dev/null @@ -1,41 +0,0 @@ -{ - lib, - config, - pkgs, - ... -}: { - options.services.forgejo = { - fail2ban = { - enable = lib.mkOption { - type = lib.types.bool; - default = config.services.forgejo.enable && config.services.fail2ban.enable; - }; - }; - }; - - config = lib.mkIf config.services.forgejo.fail2ban.enable { - environment.etc = { - "fail2ban/filter.d/forgejo.local".text = lib.mkIf config.services.forgejo.enable ( - pkgs.lib.mkDefault (pkgs.lib.mkAfter '' - [Definition] - failregex = ".*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from " - '') - ); - }; - - services.fail2ban = { - jails = { - forgejo-iptables.settings = lib.mkIf config.services.forgejo.enable { - enabled = true; - filter = "forgejo"; - action = ''iptables-multiport[name=HTTP, port="http,https"]''; - logpath = "${config.services.forgejo.settings.log.ROOT_PATH}/*.log"; - backend = "auto"; - findtime = 600; - bantime = 600; - maxretry = 5; - }; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/forgejo/forgejo.nix b/legacy-modules/nixos-modules/server/forgejo/forgejo.nix deleted file mode 100644 index 70d3087..0000000 --- a/legacy-modules/nixos-modules/server/forgejo/forgejo.nix +++ /dev/null @@ -1,46 +0,0 @@ -{ - lib, - config, - ... -}: let - const = import ./const.nix; - httpPort = const.httpPort; - sshPort = const.sshPort; - db_user = "forgejo"; -in { - config = lib.mkIf config.services.forgejo.enable { - assertions = [ - { - assertion = config.services.forgejo.settings.server.BUILTIN_SSH_SERVER_USER == config.users.users.git.name; - message = "Forgejo BUILTIN_SSH_SERVER_USER hardcoded value does not match expected git user name"; - } - ]; - - services.forgejo = { - database = { - type = "postgres"; - socket = "/run/postgresql"; - }; - lfs.enable = true; - settings = { - server = { - DOMAIN = config.services.forgejo.reverseProxy.domain; - HTTP_PORT = httpPort; - START_SSH_SERVER = true; - SSH_LISTEN_PORT = sshPort; - SSH_PORT = 22; - BUILTIN_SSH_SERVER_USER = "git"; - ROOT_URL = "https://git.jan-leila.com"; - }; - service = { - DISABLE_REGISTRATION = true; - }; - database = { - DB_TYPE = "postgres"; - NAME = db_user; - USER = db_user; - }; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/forgejo/proxy.nix b/legacy-modules/nixos-modules/server/forgejo/proxy.nix deleted file mode 100644 index c2d3131..0000000 --- a/legacy-modules/nixos-modules/server/forgejo/proxy.nix +++ /dev/null @@ -1,43 +0,0 @@ -{ - lib, - config, - ... -}: let - const = import ./const.nix; - httpPort = const.httpPort; -in { - options.services.forgejo = { - reverseProxy = { - enable = lib.mkOption { - type = lib.types.bool; - default = config.services.forgejo.enable && config.services.reverseProxy.enable; - }; - domain = lib.mkOption { - type = lib.types.str; - description = "domain that forgejo will be hosted at"; - default = "git.jan-leila.com"; - }; - extraDomains = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "extra domains that should be configured for forgejo"; - default = []; - }; - }; - }; - - config = lib.mkIf config.services.forgejo.reverseProxy.enable { - services.reverseProxy.services.forgejo = { - target = "http://localhost:${toString httpPort}"; - domain = config.services.forgejo.reverseProxy.domain; - extraDomains = config.services.forgejo.reverseProxy.extraDomains; - - settings = { - forwardHeaders.enable = true; - }; - }; - - networking.firewall.allowedTCPPorts = [ - config.services.forgejo.settings.server.SSH_LISTEN_PORT - ]; - }; -} diff --git a/legacy-modules/nixos-modules/server/forgejo/storage.nix b/legacy-modules/nixos-modules/server/forgejo/storage.nix deleted file mode 100644 index da30ed9..0000000 --- a/legacy-modules/nixos-modules/server/forgejo/storage.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - lib, - config, - ... -}: let - stateDir = "/var/lib/forgejo"; -in { - options.services.forgejo.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.forgejo.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.forgejo.enable { - storage.datasets.replicate."system/root" = { - directories."${stateDir}" = lib.mkIf config.services.forgejo.impermanence.enable { - owner.name = "forgejo"; - group.name = "forgejo"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/home-assistant/database.nix b/legacy-modules/nixos-modules/server/home-assistant/database.nix deleted file mode 100644 index f1927ed..0000000 --- a/legacy-modules/nixos-modules/server/home-assistant/database.nix +++ /dev/null @@ -1,53 +0,0 @@ -{ - lib, - config, - ... -}: { - options.services.home-assistant = { - postgres = { - enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Use PostgreSQL instead of SQLite"; - }; - user = lib.mkOption { - type = lib.types.str; - default = "hass"; - description = "Database user name"; - }; - database = lib.mkOption { - type = lib.types.str; - default = "hass"; - description = "Database name"; - }; - }; - }; - - config = lib.mkIf config.services.home-assistant.enable { - assertions = [ - { - assertion = !config.services.home-assistant.postgres.enable || config.services.postgresql.enable; - message = "PostgreSQL must be enabled when using postgres database for Home Assistant"; - } - ]; - - services.postgresql.databases.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable { - enable = true; - user = config.services.home-assistant.postgres.user; - database = config.services.home-assistant.postgres.database; - }; - - services.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable { - extraPackages = python3Packages: - with python3Packages; [ - psycopg2 - ]; - }; - - systemd.services.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable { - requires = [ - config.systemd.services.postgresql.name - ]; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/home-assistant/default.nix b/legacy-modules/nixos-modules/server/home-assistant/default.nix deleted file mode 100644 index d213964..0000000 --- a/legacy-modules/nixos-modules/server/home-assistant/default.nix +++ /dev/null @@ -1,10 +0,0 @@ -{ - imports = [ - ./home-assistant.nix - ./proxy.nix - ./database.nix - ./fail2ban.nix - ./storage.nix - ./extensions - ]; -} diff --git a/legacy-modules/nixos-modules/server/home-assistant/extensions/default.nix b/legacy-modules/nixos-modules/server/home-assistant/extensions/default.nix deleted file mode 100644 index 9ef84a3..0000000 --- a/legacy-modules/nixos-modules/server/home-assistant/extensions/default.nix +++ /dev/null @@ -1,12 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: { - imports = [ - ./sonos.nix - ./jellyfin.nix - ./wyoming.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/home-assistant/extensions/jellyfin.nix b/legacy-modules/nixos-modules/server/home-assistant/extensions/jellyfin.nix deleted file mode 100644 index 29af274..0000000 --- a/legacy-modules/nixos-modules/server/home-assistant/extensions/jellyfin.nix +++ /dev/null @@ -1,9 +0,0 @@ -{ - lib, - config, - ... -}: -lib.mkIf (config.services.home-assistant.extensions.jellyfin.enable) { - services.home-assistant.extraComponents = ["jellyfin"]; - # TODO: configure port, address, and login information here -} diff --git a/legacy-modules/nixos-modules/server/home-assistant/extensions/sonos.nix b/legacy-modules/nixos-modules/server/home-assistant/extensions/sonos.nix deleted file mode 100644 index c70649f..0000000 --- a/legacy-modules/nixos-modules/server/home-assistant/extensions/sonos.nix +++ /dev/null @@ -1,11 +0,0 @@ -{ - lib, - config, - ... -}: -lib.mkIf (config.services.home-assistant.extensions.sonos.enable) { - services.home-assistant.extraComponents = ["sonos"]; - networking.firewall.allowedTCPPorts = [ - config.services.home-assistant.extensions.sonos.port - ]; -} diff --git a/legacy-modules/nixos-modules/server/home-assistant/extensions/wyoming.nix b/legacy-modules/nixos-modules/server/home-assistant/extensions/wyoming.nix deleted file mode 100644 index 840d360..0000000 --- a/legacy-modules/nixos-modules/server/home-assistant/extensions/wyoming.nix +++ /dev/null @@ -1,9 +0,0 @@ -{ - lib, - config, - ... -}: -lib.mkIf (config.services.home-assistant.extensions.wyoming.enable) { - services.home-assistant.extraComponents = ["wyoming"]; - services.wyoming.enable = true; -} diff --git a/legacy-modules/nixos-modules/server/home-assistant/fail2ban.nix b/legacy-modules/nixos-modules/server/home-assistant/fail2ban.nix deleted file mode 100644 index 25194ef..0000000 --- a/legacy-modules/nixos-modules/server/home-assistant/fail2ban.nix +++ /dev/null @@ -1,49 +0,0 @@ -{ - lib, - pkgs, - config, - ... -}: { - options.services.home-assistant = { - fail2ban = { - enable = lib.mkOption { - type = lib.types.bool; - default = config.services.fail2ban.enable && config.services.home-assistant.enable; - }; - }; - }; - - config = lib.mkIf config.services.home-assistant.fail2ban.enable { - environment.etc = { - "fail2ban/filter.d/hass.local".text = ( - pkgs.lib.mkDefault (pkgs.lib.mkAfter '' - [INCLUDES] - before = common.conf - - [Definition] - failregex = ^%(__prefix_line)s.*Login attempt or request with invalid authentication from .*$ - - ignoreregex = - - [Init] - datepattern = ^%%Y-%%m-%%d %%H:%%M:%%S - '') - ); - }; - - services.fail2ban = { - jails = { - home-assistant-iptables.settings = { - enabled = true; - filter = "hass"; - action = ''iptables-multiport[name=HTTP, port="http,https"]''; - logpath = "${config.services.home-assistant.configDir}/*.log"; - backend = "auto"; - findtime = 600; - bantime = 600; - maxretry = 5; - }; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/home-assistant/home-assistant.nix b/legacy-modules/nixos-modules/server/home-assistant/home-assistant.nix deleted file mode 100644 index fa58d5e..0000000 --- a/legacy-modules/nixos-modules/server/home-assistant/home-assistant.nix +++ /dev/null @@ -1,104 +0,0 @@ -{ - lib, - config, - ... -}: { - options.services.home-assistant = { - database = lib.mkOption { - type = lib.types.enum [ - "builtin" - "postgres" - ]; - description = "what database do we want to use"; - default = "builtin"; - }; - - extensions = { - sonos = { - enable = lib.mkEnableOption "enable the sonos plugin"; - port = lib.mkOption { - type = lib.types.int; - default = 1400; - description = "what port to use for sonos discovery"; - }; - }; - jellyfin = { - enable = lib.mkEnableOption "enable the jellyfin plugin"; - }; - wyoming = { - enable = lib.mkEnableOption "enable wyoming"; - }; - }; - }; - - config = lib.mkIf config.services.home-assistant.enable (lib.mkMerge [ - { - services.home-assistant = { - configDir = "/var/lib/hass"; - extraComponents = [ - "default_config" - "esphome" - "met" - "radio_browser" - "isal" - "zha" - "webostv" - "tailscale" - "syncthing" - "analytics_insights" - "unifi" - "openweathermap" - "ollama" - "mobile_app" - "logbook" - "ssdp" - "usb" - "webhook" - "bluetooth" - "dhcp" - "energy" - "history" - "backup" - "assist_pipeline" - "conversation" - "sun" - "zeroconf" - "cpuspeed" - ]; - config = { - http = { - server_port = 8123; - use_x_forwarded_for = true; - trusted_proxies = ["127.0.0.1" "::1"]; - ip_ban_enabled = true; - login_attempts_threshold = 10; - }; - homeassistant = { - external_url = "https://${config.services.home-assistant.domain}"; - # internal_url = "http://192.168.1.2:8123"; - }; - recorder.db_url = "postgresql://@/${config.services.home-assistant.configDir}"; - "automation manual" = []; - "automation ui" = "!include automations.yaml"; - mobile_app = {}; - }; - extraPackages = python3Packages: - with python3Packages; [ - hassil - numpy - gtts - ]; - }; - - # TODO: configure /var/lib/hass/secrets.yaml via sops - - networking.firewall.allowedUDPPorts = [ - 1900 - ]; - - systemd.tmpfiles.rules = [ - "f ${config.services.home-assistant.configDir}/automations.yaml 0755 hass hass" - ]; - } - ]); -} diff --git a/legacy-modules/nixos-modules/server/home-assistant/proxy.nix b/legacy-modules/nixos-modules/server/home-assistant/proxy.nix deleted file mode 100644 index b756459..0000000 --- a/legacy-modules/nixos-modules/server/home-assistant/proxy.nix +++ /dev/null @@ -1,43 +0,0 @@ -{ - lib, - config, - ... -}: { - options.services.home-assistant = { - domain = lib.mkOption { - type = lib.types.str; - description = "domain that home-assistant will be hosted at"; - default = "home-assistant.arpa"; - }; - extraDomains = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "extra domains that should be configured for home-assistant"; - default = []; - }; - reverseProxy = { - enable = lib.mkOption { - type = lib.types.bool; - default = config.services.reverseProxy.enable && config.services.home-assistant.enable; - }; - }; - }; - - config = lib.mkIf config.services.home-assistant.reverseProxy.enable { - services.reverseProxy.services.home-assistant = { - target = "http://localhost:${toString config.services.home-assistant.config.http.server_port}"; - domain = config.services.home-assistant.domain; - extraDomains = config.services.home-assistant.extraDomains; - - settings = { - proxyWebsockets.enable = true; - forwardHeaders.enable = true; - - # Custom timeout settings - proxyHeaders = { - enable = true; - timeout = 90; - }; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/home-assistant/storage.nix b/legacy-modules/nixos-modules/server/home-assistant/storage.nix deleted file mode 100644 index 60e5085..0000000 --- a/legacy-modules/nixos-modules/server/home-assistant/storage.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - lib, - config, - ... -}: let - configDir = "/var/lib/hass"; -in { - options.services.home-assistant.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.home-assistant.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.home-assistant.enable { - storage.datasets.replicate."system/root" = { - directories."${configDir}" = lib.mkIf config.services.home-assistant.impermanence.enable { - owner.name = "hass"; - group.name = "hass"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/immich/database.nix b/legacy-modules/nixos-modules/server/immich/database.nix deleted file mode 100644 index 52af51e..0000000 --- a/legacy-modules/nixos-modules/server/immich/database.nix +++ /dev/null @@ -1,30 +0,0 @@ -{ - lib, - config, - ... -}: { - config = lib.mkIf config.services.immich.enable { - assertions = [ - { - assertion = !config.services.immich.database.enable || config.services.postgresql.enable; - message = "PostgreSQL must be enabled when using postgres database for Immich"; - } - { - assertion = !(config.services.immich.database.enable && config.services.immich.database.createDB) || (builtins.any (db: db == "immich") config.services.postgresql.ensureDatabases); - message = "Immich built-in database creation failed - expected 'immich' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}"; - } - { - assertion = !(config.services.immich.database.enable && config.services.immich.database.createDB) || (builtins.any (user: user.name == "immich") config.services.postgresql.ensureUsers); - message = "Immich built-in user creation failed - expected user 'immich' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}"; - } - ]; - - # Note: Immich has built-in database creation via services.immich.database.createDB we only add the systemd dependency - - systemd.services.immich-server = lib.mkIf config.services.immich.database.enable { - requires = [ - config.systemd.services.postgresql.name - ]; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/immich/default.nix b/legacy-modules/nixos-modules/server/immich/default.nix deleted file mode 100644 index 75ae2fd..0000000 --- a/legacy-modules/nixos-modules/server/immich/default.nix +++ /dev/null @@ -1,20 +0,0 @@ -{...}: { - imports = [ - ./proxy.nix - ./database.nix - ./fail2ban.nix - ./storage.nix - ]; - - # NOTE: This shouldn't be needed now that we are out of testing - # config = lib.mkIf config.services.immich.enable { - # networking.firewall.interfaces.${config.services.tailscale.interfaceName} = { - # allowedUDPPorts = [ - # config.services.immich.port - # ]; - # allowedTCPPorts = [ - # config.services.immich.port - # ]; - # }; - # }; -} diff --git a/legacy-modules/nixos-modules/server/immich/fail2ban.nix b/legacy-modules/nixos-modules/server/immich/fail2ban.nix deleted file mode 100644 index 21593e7..0000000 --- a/legacy-modules/nixos-modules/server/immich/fail2ban.nix +++ /dev/null @@ -1,35 +0,0 @@ -{ - lib, - config, - pkgs, - ... -}: { - options.services.immich = { - fail2ban = { - enable = lib.mkOption { - type = lib.types.bool; - default = config.services.fail2ban.enable && config.services.immich.enable; - }; - }; - }; - - config = lib.mkIf config.services.immich.fail2ban.enable { - environment.etc = { - "fail2ban/filter.d/immich.local".text = pkgs.lib.mkDefault (pkgs.lib.mkAfter '' - [Definition] - failregex = immich-server.*Failed login attempt for user.+from ip address\s? - journalmatch = CONTAINER_TAG=immich-server - ''); - }; - - services.fail2ban = { - jails = { - immich-iptables.settings = { - enabled = true; - filter = "immich"; - backend = "systemd"; - }; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/immich/proxy.nix b/legacy-modules/nixos-modules/server/immich/proxy.nix deleted file mode 100644 index 9c8c165..0000000 --- a/legacy-modules/nixos-modules/server/immich/proxy.nix +++ /dev/null @@ -1,44 +0,0 @@ -{ - lib, - config, - ... -}: { - options.services.immich = { - domain = lib.mkOption { - type = lib.types.str; - description = "domain that immich will be hosted at"; - default = "immich.arpa"; - }; - extraDomains = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "extra domains that should be configured for immich"; - default = []; - }; - reverseProxy = { - enable = lib.mkOption { - type = lib.types.bool; - default = config.services.immich.enable && config.services.reverseProxy.enable; - }; - }; - }; - - config = lib.mkIf config.services.immich.reverseProxy.enable { - services.reverseProxy.services.immich = { - target = "http://localhost:${toString config.services.immich.port}"; - domain = config.services.immich.domain; - extraDomains = config.services.immich.extraDomains; - - settings = { - proxyWebsockets.enable = true; - forwardHeaders.enable = true; - maxBodySize = 50000; - - # Custom timeout settings - proxyHeaders = { - enable = true; - timeout = 600; - }; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/immich/storage.nix b/legacy-modules/nixos-modules/server/immich/storage.nix deleted file mode 100644 index de24329..0000000 --- a/legacy-modules/nixos-modules/server/immich/storage.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - lib, - config, - ... -}: let - mediaLocation = "/var/lib/immich"; -in { - options.services.immich.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.immich.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.immich.enable { - storage.datasets.replicate."system/root" = { - directories."${mediaLocation}" = lib.mkIf config.services.immich.impermanence.enable { - owner.name = "immich"; - group.name = "immich"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/jackett/default.nix b/legacy-modules/nixos-modules/server/jackett/default.nix deleted file mode 100644 index 5043814..0000000 --- a/legacy-modules/nixos-modules/server/jackett/default.nix +++ /dev/null @@ -1,17 +0,0 @@ -{...}: { - imports = [ - ./storage.nix - ]; - - config = { - nixpkgs.overlays = [ - # Disable jackett tests due to date-related test failures - # (ParseDateTimeGoLangTest expects 2024-09-14 but gets 2025-09-14 due to year rollover logic) - (final: prev: { - jackett = prev.jackett.overrideAttrs (oldAttrs: { - doCheck = false; - }); - }) - ]; - }; -} diff --git a/legacy-modules/nixos-modules/server/jackett/storage.nix b/legacy-modules/nixos-modules/server/jackett/storage.nix deleted file mode 100644 index 5f202e6..0000000 --- a/legacy-modules/nixos-modules/server/jackett/storage.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - lib, - config, - ... -}: let - jackett_data_directory = "/var/lib/jackett/.config/Jackett"; -in { - options.services.jackett.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.jackett.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.jackett.enable { - storage.datasets.replicate."system/root" = { - directories."${jackett_data_directory}" = lib.mkIf config.services.jackett.impermanence.enable { - owner.name = "jackett"; - group.name = "jackett"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/jellyfin/default.nix b/legacy-modules/nixos-modules/server/jellyfin/default.nix deleted file mode 100644 index 4770ae1..0000000 --- a/legacy-modules/nixos-modules/server/jellyfin/default.nix +++ /dev/null @@ -1,8 +0,0 @@ -{ - imports = [ - ./jellyfin.nix - ./proxy.nix - ./fail2ban.nix - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/jellyfin/fail2ban.nix b/legacy-modules/nixos-modules/server/jellyfin/fail2ban.nix deleted file mode 100644 index ba8d8ba..0000000 --- a/legacy-modules/nixos-modules/server/jellyfin/fail2ban.nix +++ /dev/null @@ -1,32 +0,0 @@ -{ - lib, - pkgs, - config, - ... -}: { - config = lib.mkIf (config.services.jellyfin.enable && config.services.fail2ban.enable) { - environment.etc = { - "fail2ban/filter.d/jellyfin.local".text = ( - pkgs.lib.mkDefault (pkgs.lib.mkAfter '' - [Definition] - failregex = "^.*Authentication request for .* has been denied \\\\\\(IP: \\\"\\\"\\\\\\)\\\\\\." - '') - ); - }; - - services.fail2ban = { - jails = { - jellyfin-iptables.settings = { - enabled = true; - filter = "jellyfin"; - action = ''iptables-multiport[name=HTTP, port="http,https"]''; - logpath = "${config.services.jellyfin.dataDir}/log/*.log"; - backend = "auto"; - findtime = 600; - bantime = 600; - maxretry = 5; - }; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/jellyfin/jellyfin.nix b/legacy-modules/nixos-modules/server/jellyfin/jellyfin.nix deleted file mode 100644 index 9bfa921..0000000 --- a/legacy-modules/nixos-modules/server/jellyfin/jellyfin.nix +++ /dev/null @@ -1,32 +0,0 @@ -{ - lib, - pkgs, - config, - ... -}: let - jellyfinPort = 8096; - dlanPort = 1900; -in { - options.services.jellyfin = { - media_directory = lib.mkOption { - type = lib.types.str; - description = "directory jellyfin media will be hosted at"; - default = "/srv/jellyfin/media"; - }; - }; - - config = lib.mkIf config.services.jellyfin.enable { - environment.systemPackages = [ - pkgs.jellyfin - pkgs.jellyfin-web - pkgs.jellyfin-ffmpeg - ]; - - networking.firewall.allowedTCPPorts = [jellyfinPort dlanPort]; - - systemd.tmpfiles.rules = [ - "d ${config.services.jellyfin.media_directory} 2770 jellyfin jellyfin_media" - "A ${config.services.jellyfin.media_directory} - - - - u:jellyfin:rwX,g:jellyfin_media:rwX,o::-" - ]; - }; -} diff --git a/legacy-modules/nixos-modules/server/jellyfin/proxy.nix b/legacy-modules/nixos-modules/server/jellyfin/proxy.nix deleted file mode 100644 index 35289e7..0000000 --- a/legacy-modules/nixos-modules/server/jellyfin/proxy.nix +++ /dev/null @@ -1,41 +0,0 @@ -{ - lib, - config, - ... -}: let - jellyfinPort = 8096; -in { - options.services.jellyfin = { - domain = lib.mkOption { - type = lib.types.str; - description = "domain that jellyfin will be hosted at"; - default = "jellyfin.arpa"; - }; - extraDomains = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "extra domains that should be configured for jellyfin"; - default = []; - }; - reverseProxy = { - enable = lib.mkOption { - type = lib.types.bool; - default = config.services.jellyfin.enable && config.services.reverseProxy.enable; - }; - }; - }; - - config = lib.mkIf config.services.jellyfin.reverseProxy.enable { - services.reverseProxy.services.jellyfin = { - target = "http://localhost:${toString jellyfinPort}"; - domain = config.services.jellyfin.domain; - extraDomains = config.services.jellyfin.extraDomains; - - settings = { - forwardHeaders.enable = true; - maxBodySize = 20; - noSniff.enable = true; - proxyBuffering.enable = false; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/jellyfin/storage.nix b/legacy-modules/nixos-modules/server/jellyfin/storage.nix deleted file mode 100644 index 5cff3e8..0000000 --- a/legacy-modules/nixos-modules/server/jellyfin/storage.nix +++ /dev/null @@ -1,56 +0,0 @@ -{ - lib, - config, - ... -}: let - jellyfin_data_directory = "/var/lib/jellyfin"; - jellyfin_cache_directory = "/var/cache/jellyfin"; -in { - options.services.jellyfin.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.jellyfin.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.jellyfin.enable { - storage.datasets.replicate = { - "system/root" = { - directories = { - "${jellyfin_data_directory}" = lib.mkIf config.services.jellyfin.impermanence.enable { - enable = true; - owner.name = "jellyfin"; - group.name = "jellyfin"; - }; - "${jellyfin_cache_directory}" = lib.mkIf config.services.jellyfin.impermanence.enable { - enable = true; - owner.name = "jellyfin"; - group.name = "jellyfin"; - }; - }; - }; - "system/media" = { - mount = "/persist/replicate/system/media"; - - directories."${config.services.jellyfin.media_directory}" = lib.mkIf config.services.jellyfin.impermanence.enable { - enable = true; - owner.name = "jellyfin"; - group.name = "jellyfin_media"; - owner.permissions = { - read = true; - write = true; - execute = true; - }; - group.permissions = { - read = true; - write = true; - execute = true; - }; - other.permissions = { - read = false; - write = false; - execute = false; - }; - }; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/lidarr/default.nix b/legacy-modules/nixos-modules/server/lidarr/default.nix deleted file mode 100644 index cb2a5f0..0000000 --- a/legacy-modules/nixos-modules/server/lidarr/default.nix +++ /dev/null @@ -1,5 +0,0 @@ -{...}: { - imports = [ - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/lidarr/storage.nix b/legacy-modules/nixos-modules/server/lidarr/storage.nix deleted file mode 100644 index c4c020e..0000000 --- a/legacy-modules/nixos-modules/server/lidarr/storage.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - lib, - config, - ... -}: let - lidarr_data_directory = "/var/lib/lidarr/.config/Lidarr"; -in { - options.services.lidarr.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.lidarr.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.lidarr.enable { - storage.datasets.replicate."system/root" = { - directories."${lidarr_data_directory}" = lib.mkIf config.services.lidarr.impermanence.enable { - owner.name = "lidarr"; - group.name = "lidarr"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/network_storage/default.nix b/legacy-modules/nixos-modules/server/network_storage/default.nix deleted file mode 100644 index cd100ab..0000000 --- a/legacy-modules/nixos-modules/server/network_storage/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{ - imports = [ - ./network_storage.nix - ./nfs.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/network_storage/network_storage.nix b/legacy-modules/nixos-modules/server/network_storage/network_storage.nix deleted file mode 100644 index b9d0446..0000000 --- a/legacy-modules/nixos-modules/server/network_storage/network_storage.nix +++ /dev/null @@ -1,86 +0,0 @@ -{ - config, - lib, - ... -}: let - export_directory = config.host.network_storage.export_directory; -in { - options = { - host.network_storage = { - enable = lib.mkEnableOption "is this machine going to export network storage"; - export_directory = lib.mkOption { - type = lib.types.path; - description = "what are exports going to be stored in"; - default = "/exports"; - }; - directories = lib.mkOption { - type = lib.types.listOf (lib.types.submodule ({config, ...}: { - options = { - folder = lib.mkOption { - type = lib.types.str; - description = "what is the name of this export directory"; - }; - bind = lib.mkOption { - type = lib.types.nullOr lib.types.path; - description = "is this directory bound to anywhere"; - default = null; - }; - user = lib.mkOption { - type = lib.types.str; - description = "what user owns this directory"; - default = "nouser"; - }; - group = lib.mkOption { - type = lib.types.str; - description = "what group owns this directory"; - default = "nogroup"; - }; - _directory = lib.mkOption { - internal = true; - readOnly = true; - type = lib.types.path; - default = "${export_directory}/${config.folder}"; - }; - }; - })); - description = "list of directory names to export"; - }; - }; - }; - - config = lib.mkIf config.host.network_storage.enable (lib.mkMerge [ - { - # create any folders that we need to have for our exports - systemd.tmpfiles.rules = - [ - "d ${config.host.network_storage.export_directory} 2775 nobody nogroup -" - ] - ++ ( - builtins.map ( - directory: "d ${directory._directory} 2770 ${directory.user} ${directory.group}" - ) - config.host.network_storage.directories - ); - - # set up any bind mounts that we need for our exports - fileSystems = builtins.listToAttrs ( - builtins.map (directory: - lib.attrsets.nameValuePair directory._directory { - device = directory.bind; - options = ["bind"]; - }) ( - builtins.filter (directory: directory.bind != null) config.host.network_storage.directories - ) - ); - } - # (lib.mkIf config.host.impermanence.enable { - # environment.persistence."/persist/replicate/system/root" = { - # enable = true; - # hideMounts = true; - # directories = [ - # config.host.network_storage.export_directory - # ]; - # }; - # }) - ]); -} diff --git a/legacy-modules/nixos-modules/server/network_storage/nfs.nix b/legacy-modules/nixos-modules/server/network_storage/nfs.nix deleted file mode 100644 index 297dc1a..0000000 --- a/legacy-modules/nixos-modules/server/network_storage/nfs.nix +++ /dev/null @@ -1,107 +0,0 @@ -{ - config, - lib, - ... -}: { - options = { - host.network_storage.nfs = { - enable = lib.mkEnableOption "is this server going to export network storage as nfs shares"; - port = lib.mkOption { - type = lib.types.int; - default = 2049; - description = "port that nfs will run on"; - }; - directories = lib.mkOption { - type = lib.types.listOf ( - lib.types.enum ( - builtins.map ( - directory: directory.folder - ) - config.host.network_storage.directories - ) - ); - description = "list of exported directories to be exported via nfs"; - }; - }; - }; - config = lib.mkMerge [ - { - assertions = [ - { - assertion = !(config.host.network_storage.nfs.enable && !config.host.network_storage.enable); - message = "nfs cant be enabled with network storage disabled"; - } - ]; - } - ( - lib.mkIf (config.host.network_storage.nfs.enable && config.host.network_storage.enable) { - services.nfs = { - settings = { - nfsd = { - threads = 32; - port = config.host.network_storage.nfs.port; - }; - }; - server = { - enable = true; - - lockdPort = 4001; - mountdPort = 4002; - statdPort = 4000; - - exports = lib.strings.concatLines ( - [ - "${config.host.network_storage.export_directory} 100.64.0.0/10(rw,fsid=0,no_subtree_check)" - ] - ++ ( - lib.lists.imap0 ( - i: directory: let - createOptions = fsid: "(rw,fsid=${toString fsid},nohide,insecure,no_subtree_check)"; - addresses = [ - # loopback - "127.0.0.1" - "::1" - # tailscale - "100.64.0.0/10" - "fd7a:115c:a1e0::/48" - ]; - options = lib.strings.concatStrings ( - lib.strings.intersperse " " ( - lib.lists.imap0 (index: address: "${address}${createOptions (1 + (i * (builtins.length addresses)) + index)}") addresses - ) - ); - in "${directory._directory} ${options}" - ) - ( - builtins.filter ( - directory: lib.lists.any (target: target == directory.folder) config.host.network_storage.nfs.directories - ) - config.host.network_storage.directories - ) - ) - ); - }; - }; - networking.firewall = let - ports = [ - 111 - config.host.network_storage.nfs.port - config.services.nfs.server.lockdPort - config.services.nfs.server.mountdPort - config.services.nfs.server.statdPort - 20048 - ]; - in { - # Allow NFS on Tailscale interface - interfaces.${config.services.tailscale.interfaceName} = { - allowedTCPPorts = ports; - allowedUDPPorts = ports; - }; - # Allow NFS on local network (assuming default interface) - allowedTCPPorts = ports; - allowedUDPPorts = ports; - }; - } - ) - ]; -} diff --git a/legacy-modules/nixos-modules/server/panoramax/database.nix b/legacy-modules/nixos-modules/server/panoramax/database.nix deleted file mode 100644 index 1721726..0000000 --- a/legacy-modules/nixos-modules/server/panoramax/database.nix +++ /dev/null @@ -1,48 +0,0 @@ -{ - lib, - config, - ... -}: { - options.services.panoramax = { - database = { - postgres = { - enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Use PostgreSQL instead of SQLite"; - }; - user = lib.mkOption { - type = lib.types.str; - default = "panoramax"; - description = "Database user name"; - }; - database = lib.mkOption { - type = lib.types.str; - default = "panoramax"; - description = "Database name"; - }; - }; - }; - }; - - config = lib.mkIf config.services.panoramax.enable { - assertions = [ - { - assertion = !config.services.panoramax.database.postgres.enable || config.services.postgresql.enable; - message = "PostgreSQL must be enabled when using postgres database for Panoramax"; - } - ]; - - services.postgresql.databases.panoramax = lib.mkIf config.services.panoramax.database.postgres.enable { - enable = true; - user = config.services.panoramax.database.postgres.user; - database = config.services.panoramax.database.postgres.database; - }; - - systemd.services.panoramax = lib.mkIf config.services.panoramax.database.postgres.enable { - requires = [ - config.systemd.services.postgresql.name - ]; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/panoramax/default.nix b/legacy-modules/nixos-modules/server/panoramax/default.nix deleted file mode 100644 index f5a514f..0000000 --- a/legacy-modules/nixos-modules/server/panoramax/default.nix +++ /dev/null @@ -1,9 +0,0 @@ -{...}: { - imports = [ - ./proxy.nix - ./fail2ban.nix - ./storage.nix - ./panoramax.nix - ./database.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/panoramax/fail2ban.nix b/legacy-modules/nixos-modules/server/panoramax/fail2ban.nix deleted file mode 100644 index 649b53a..0000000 --- a/legacy-modules/nixos-modules/server/panoramax/fail2ban.nix +++ /dev/null @@ -1,11 +0,0 @@ -{ - lib, - config, - ... -}: { - config = lib.mkIf (config.services.panoramax.enable && config.services.fail2ban.enable) { - # TODO: configure options for fail2ban - # This is a placeholder - panoramax fail2ban configuration would need to be defined - # based on the specific log patterns and security requirements - }; -} diff --git a/legacy-modules/nixos-modules/server/panoramax/panoramax.nix b/legacy-modules/nixos-modules/server/panoramax/panoramax.nix deleted file mode 100644 index fd77db7..0000000 --- a/legacy-modules/nixos-modules/server/panoramax/panoramax.nix +++ /dev/null @@ -1,359 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: { - options.services = { - panoramax = { - enable = lib.mkEnableOption "panoramax"; - - package = lib.mkOption { - type = lib.types.package; - default = pkgs.panoramax; - description = "The panoramax package to use"; - }; - - user = lib.mkOption { - type = lib.types.str; - default = "panoramax"; - description = "The user panoramax should run as."; - }; - - group = lib.mkOption { - type = lib.types.str; - default = "panoramax"; - description = "The group panoramax should run as."; - }; - - host = lib.mkOption { - type = lib.types.str; - default = "127.0.0.1"; - description = "Host to bind the panoramax service to"; - }; - - port = lib.mkOption { - type = lib.types.nullOr lib.types.port; - default = 5000; - description = "Port for the panoramax service"; - }; - - openFirewall = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Whether to open the panoramax port in the firewall"; - }; - - settings = { - urlScheme = lib.mkOption { - type = lib.types.enum ["http" "https"]; - default = "https"; - description = "URL scheme for the application"; - }; - - storage = { - fsUrl = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = "/var/lib/panoramax/storage"; - description = "File system URL for storage"; - }; - }; - - infrastructure = { - nbProxies = lib.mkOption { - type = lib.types.nullOr lib.types.int; - default = 1; - description = "Number of proxies in front of the application"; - }; - }; - - flask = { - secretKey = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = "Flask secret key for session security"; - }; - - sessionCookieDomain = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = "Flask session cookie domain"; - }; - }; - - api = { - pictures = { - licenseSpdxId = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = "SPDX license identifier for API pictures"; - }; - - licenseUrl = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = "License URL for API pictures"; - }; - }; - }; - - extraEnvironment = lib.mkOption { - type = lib.types.attrsOf lib.types.str; - default = {}; - description = "Additional environment variables"; - example = { - CUSTOM_SETTING = "value"; - DEBUG = "true"; - }; - }; - }; - - database = { - createDB = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to automatically create the database and user"; - }; - - name = lib.mkOption { - type = lib.types.str; - default = "panoramax"; - description = "The name of the panoramax database"; - }; - - host = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = "/run/postgresql"; - description = "Hostname or address of the postgresql server. If an absolute path is given here, it will be interpreted as a unix socket path."; - }; - - port = lib.mkOption { - type = lib.types.nullOr lib.types.port; - default = 5432; - description = "Port of the postgresql server."; - }; - - user = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = "panoramax"; - description = "The database user for panoramax."; - }; - - # TODO: password file for external database - }; - - sgblur = { - # TODO: configs to bind to sgblur - }; - }; - sgblur = { - enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Whether to enable sgblur integration for face and license plate blurring"; - }; - - package = lib.mkOption { - type = lib.types.package; - default = pkgs.sgblur; - description = "The sgblur package to use"; - }; - - port = lib.mkOption { - type = lib.types.port; - default = 8080; - description = "Port for the sgblur service"; - }; - - host = lib.mkOption { - type = lib.types.str; - default = "127.0.0.1"; - description = "Host to bind the sgblur service to"; - }; - - url = lib.mkOption { - type = lib.types.str; - default = "http://127.0.0.1:8080"; - description = "URL where sgblur service is accessible"; - }; - }; - }; - - config = lib.mkIf config.services.panoramax.enable (lib.mkMerge [ - { - # Create panoramax user and group - users.users.${config.services.panoramax.user} = { - isSystemUser = true; - group = config.services.panoramax.group; - home = "/var/lib/panoramax"; - createHome = true; - }; - - users.groups.${config.services.panoramax.group} = {}; - - # Ensure storage directory exists with correct permissions - systemd.tmpfiles.rules = [ - "d '${config.services.panoramax.settings.storage.fsUrl}' 0755 ${config.services.panoramax.user} ${config.services.panoramax.group} - -" - ]; - - systemd.services.panoramax-api = { - description = "Panoramax API server (self hosted map street view)"; - after = ["network.target" "postgresql.service"]; - wantedBy = ["multi-user.target"]; - - environment = - { - # Core Flask configuration - FLASK_APP = "geovisio"; - - # Storage configuration - FS_URL = config.services.panoramax.settings.storage.fsUrl; - - # Infrastructure configuration - INFRA_NB_PROXIES = toString config.services.panoramax.settings.infrastructure.nbProxies; - - # Application configuration - PORT = toString config.services.panoramax.port; - - # Python path to include the panoramax package - PYTHONPATH = "${config.services.panoramax.package}/${pkgs.python3.sitePackages}"; - } - // ( - if config.services.panoramax.database.host == "/run/postgresql" - then { - DB_URL = "postgresql://${config.services.panoramax.database.user}@/${config.services.panoramax.database.name}?host=/run/postgresql"; - } - else { - DB_HOST = config.services.panoramax.database.host; - DB_PORT = toString config.services.panoramax.database.port; - DB_USERNAME = config.services.panoramax.database.user; - DB_NAME = config.services.panoramax.database.name; - } - ) - // (lib.optionalAttrs (config.services.panoramax.settings.flask.secretKey != null) { - FLASK_SECRET_KEY = config.services.panoramax.settings.flask.secretKey; - }) - // (lib.optionalAttrs (config.services.panoramax.settings.flask.sessionCookieDomain != null) { - FLASK_SESSION_COOKIE_DOMAIN = config.services.panoramax.settings.flask.sessionCookieDomain; - }) - // (lib.optionalAttrs (config.services.panoramax.settings.api.pictures.licenseSpdxId != null) { - API_PICTURES_LICENSE_SPDX_ID = config.services.panoramax.settings.api.pictures.licenseSpdxId; - }) - // (lib.optionalAttrs (config.services.panoramax.settings.api.pictures.licenseUrl != null) { - API_PICTURES_LICENSE_URL = config.services.panoramax.settings.api.pictures.licenseUrl; - }) - // (lib.optionalAttrs config.services.sgblur.enable { - SGBLUR_API_URL = config.services.sgblur.url; - }) - // config.services.panoramax.settings.extraEnvironment; - - path = with pkgs; [ - (python3.withPackages (ps: with ps; [config.services.panoramax.package waitress])) - ]; - - serviceConfig = { - ExecStart = "${pkgs.python3.withPackages (ps: with ps; [config.services.panoramax.package waitress])}/bin/waitress-serve --port ${toString config.services.panoramax.port} --call geovisio:create_app"; - User = config.services.panoramax.user; - Group = config.services.panoramax.group; - WorkingDirectory = "/var/lib/panoramax"; - Restart = "always"; - RestartSec = 5; - - # Security hardening - PrivateTmp = true; - ProtectSystem = "strict"; - ProtectHome = true; - ReadWritePaths = [ - "/var/lib/panoramax" - config.services.panoramax.settings.storage.fsUrl - ]; - NoNewPrivileges = true; - PrivateDevices = true; - ProtectKernelTunables = true; - ProtectKernelModules = true; - ProtectControlGroups = true; - RestrictSUIDSGID = true; - RestrictRealtime = true; - RestrictNamespaces = true; - LockPersonality = true; - MemoryDenyWriteExecute = true; - SystemCallArchitectures = "native"; - }; - }; - - # Open firewall if requested - networking.firewall.allowedTCPPorts = lib.mkIf config.services.panoramax.openFirewall [ - config.services.panoramax.port - ]; - } - (lib.mkIf config.services.sgblur.enable { - # SGBlur service configuration - systemd.services.sgblur = { - description = "SGBlur face and license plate blurring service"; - after = ["network.target"]; - wantedBy = ["multi-user.target"]; - - path = with pkgs; [ - config.services.sgblur.package - python3 - python3Packages.waitress - ]; - - serviceConfig = { - ExecStart = "${pkgs.python3Packages.waitress}/bin/waitress-serve --host ${config.services.sgblur.host} --port ${toString config.services.sgblur.port} src.detect.detect_api:app"; - WorkingDirectory = "${config.services.sgblur.package}"; - Restart = "always"; - RestartSec = 5; - - # Basic security hardening - PrivateTmp = true; - ProtectSystem = "strict"; - ProtectHome = true; - NoNewPrivileges = true; - PrivateDevices = true; - ProtectKernelTunables = true; - ProtectKernelModules = true; - ProtectControlGroups = true; - RestrictSUIDSGID = true; - RestrictRealtime = true; - RestrictNamespaces = true; - LockPersonality = true; - MemoryDenyWriteExecute = true; - SystemCallArchitectures = "native"; - }; - }; - - networking.firewall.allowedTCPPorts = lib.mkIf config.services.panoramax.openFirewall [ - config.services.sgblur.port - ]; - }) - (lib.mkIf config.services.panoramax.database.createDB { - services.postgresql = { - enable = true; - ensureDatabases = lib.mkIf config.services.panoramax.database.createDB [config.services.panoramax.database.name]; - ensureUsers = lib.mkIf config.services.panoramax.database.createDB [ - { - name = config.services.panoramax.database.user; - ensureDBOwnership = true; - ensureClauses.login = true; - } - ]; - extensions = ps: with ps; [postgis]; - }; - systemd.services.postgresql.serviceConfig.ExecStartPost = let - sqlFile = pkgs.writeText "panoramax-postgis-setup.sql" '' - CREATE EXTENSION IF NOT EXISTS postgis; - - -- TODO: how can we ensure that this runs after the databases have been created - -- ALTER DATABASE ${config.services.panoramax.database.name} SET TIMEZONE TO 'UTC'; - - GRANT SET ON PARAMETER session_replication_role TO ${config.services.panoramax.database.user}; - ''; - in [ - '' - ${lib.getExe' config.services.postgresql.package "psql"} -d "${config.services.panoramax.database.user}" -f "${sqlFile}" - '' - ]; - }) - ]); -} diff --git a/legacy-modules/nixos-modules/server/panoramax/proxy.nix b/legacy-modules/nixos-modules/server/panoramax/proxy.nix deleted file mode 100644 index 7cd7111..0000000 --- a/legacy-modules/nixos-modules/server/panoramax/proxy.nix +++ /dev/null @@ -1,39 +0,0 @@ -{ - lib, - config, - ... -}: { - options.services.panoramax = { - domain = lib.mkOption { - type = lib.types.str; - description = "domain that panoramax will be hosted at"; - default = "panoramax.arpa"; - }; - extraDomains = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "extra domains that should be configured for panoramax"; - default = []; - }; - reverseProxy = { - enable = lib.mkOption { - type = lib.types.bool; - default = config.services.panoramax.enable && config.services.reverseProxy.enable; - }; - }; - }; - - config = lib.mkIf config.services.panoramax.reverseProxy.enable { - services.reverseProxy.services.panoramax = { - target = "http://localhost:${toString config.services.panoramax.port}"; - domain = config.services.panoramax.domain; - extraDomains = config.services.panoramax.extraDomains; - - settings = { - proxyWebsockets.enable = true; - forwardHeaders.enable = true; - maxBodySize = 100000; - timeout = 300; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/panoramax/storage.nix b/legacy-modules/nixos-modules/server/panoramax/storage.nix deleted file mode 100644 index b36e087..0000000 --- a/legacy-modules/nixos-modules/server/panoramax/storage.nix +++ /dev/null @@ -1,19 +0,0 @@ -{ - lib, - config, - ... -}: { - options.services.panoramax.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.panoramax.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.panoramax.enable { - storage.datasets.replicate."system/root" = { - directories."/var/lib/panoramax" = lib.mkIf config.services.panoramax.impermanence.enable { - owner.name = "panoramax"; - group.name = "panoramax"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/paperless/database.nix b/legacy-modules/nixos-modules/server/paperless/database.nix deleted file mode 100644 index c63e59d..0000000 --- a/legacy-modules/nixos-modules/server/paperless/database.nix +++ /dev/null @@ -1,30 +0,0 @@ -{ - config, - lib, - ... -}: { - config = lib.mkIf config.services.paperless.enable { - assertions = [ - { - assertion = !config.services.paperless.database.createLocally || config.services.postgresql.enable; - message = "PostgreSQL must be enabled when using local postgres database for Paperless"; - } - { - assertion = !config.services.paperless.database.createLocally || (builtins.any (db: db == "paperless") config.services.postgresql.ensureDatabases); - message = "Paperless built-in database creation failed - expected 'paperless' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}"; - } - { - assertion = !config.services.paperless.database.createLocally || (builtins.any (user: user.name == "paperless") config.services.postgresql.ensureUsers); - message = "Paperless built-in user creation failed - expected user 'paperless' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}"; - } - ]; - - services.paperless.database.createLocally = lib.mkDefault true; - - systemd.services.paperless-scheduler = lib.mkIf config.services.paperless.database.createLocally { - requires = [ - config.systemd.services.postgresql.name - ]; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/paperless/default.nix b/legacy-modules/nixos-modules/server/paperless/default.nix deleted file mode 100644 index f7a5aa7..0000000 --- a/legacy-modules/nixos-modules/server/paperless/default.nix +++ /dev/null @@ -1,9 +0,0 @@ -{ - imports = [ - ./paperless.nix - ./proxy.nix - ./database.nix - ./fail2ban.nix - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/paperless/fail2ban.nix b/legacy-modules/nixos-modules/server/paperless/fail2ban.nix deleted file mode 100644 index e1a70f9..0000000 --- a/legacy-modules/nixos-modules/server/paperless/fail2ban.nix +++ /dev/null @@ -1,34 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: { - config = lib.mkIf (config.services.paperless.enable && config.services.fail2ban.enable) { - environment.etc = { - "fail2ban/filter.d/paperless.local".text = ( - pkgs.lib.mkDefault (pkgs.lib.mkAfter '' - [Definition] - failregex = Login failed for user `.*` from (?:IP|private IP) ``\.$ - ignoreregex = - - '') - ); - }; - - services.fail2ban = { - jails = { - paperless.settings = { - enabled = true; - filter = "paperless"; - action = ''iptables-multiport[name=HTTP, port="http,https"]''; - logpath = "${config.services.paperless.dataDir}/log/*.log"; - backend = "auto"; - findtime = 600; - bantime = 600; - maxretry = 5; - }; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/paperless/paperless.nix b/legacy-modules/nixos-modules/server/paperless/paperless.nix deleted file mode 100644 index 5bcbfed..0000000 --- a/legacy-modules/nixos-modules/server/paperless/paperless.nix +++ /dev/null @@ -1,27 +0,0 @@ -{ - config, - lib, - ... -}: { - options.services.paperless = { - database = { - user = lib.mkOption { - type = lib.types.str; - description = "what is the user and database that we are going to use for paperless"; - default = "paperless"; - }; - }; - }; - - config = lib.mkIf config.services.paperless.enable { - services.paperless = { - configureTika = true; - settings = { - PAPERLESS_DBENGINE = "postgresql"; - PAPERLESS_DBHOST = "/run/postgresql"; - PAPERLESS_DBNAME = config.services.paperless.database.user; - PAPERLESS_DBUSER = config.services.paperless.database.user; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/paperless/proxy.nix b/legacy-modules/nixos-modules/server/paperless/proxy.nix deleted file mode 100644 index 9d152c9..0000000 --- a/legacy-modules/nixos-modules/server/paperless/proxy.nix +++ /dev/null @@ -1,33 +0,0 @@ -{ - config, - lib, - ... -}: { - options.services.paperless = { - extraDomains = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "extra domains that should be configured for paperless"; - default = []; - }; - reverseProxy = { - enable = lib.mkOption { - type = lib.types.bool; - default = config.services.paperless.enable && config.services.reverseProxy.enable; - }; - }; - }; - - config = lib.mkIf config.services.paperless.reverseProxy.enable { - services.reverseProxy.services.paperless = { - target = "http://${config.services.paperless.address}:${toString config.services.paperless.port}"; - domain = config.services.paperless.domain; - extraDomains = config.services.paperless.extraDomains; - - settings = { - proxyWebsockets.enable = true; - forwardHeaders.enable = true; - maxBodySize = 50000; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/paperless/storage.nix b/legacy-modules/nixos-modules/server/paperless/storage.nix deleted file mode 100644 index 6e17bc2..0000000 --- a/legacy-modules/nixos-modules/server/paperless/storage.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - config, - lib, - ... -}: let - dataDir = "/var/lib/paperless"; -in { - options.services.paperless.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.paperless.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.paperless.enable { - storage.datasets.replicate."system/root" = { - directories."${dataDir}" = lib.mkIf config.services.paperless.impermanence.enable { - owner.name = "paperless"; - group.name = "paperless"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/postgres/default.nix b/legacy-modules/nixos-modules/server/postgres/default.nix deleted file mode 100644 index 50d90d4..0000000 --- a/legacy-modules/nixos-modules/server/postgres/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{...}: { - imports = [ - ./postgres.nix - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/postgres/postgres.nix b/legacy-modules/nixos-modules/server/postgres/postgres.nix deleted file mode 100644 index af7d1b4..0000000 --- a/legacy-modules/nixos-modules/server/postgres/postgres.nix +++ /dev/null @@ -1,122 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: let - enabledDatabases = lib.filterAttrs (_: db: db.enable) config.services.postgresql.databases; - extraDatabasesList = config.services.postgresql.extraDatabases; - - serviceDatabaseUsers = lib.mapAttrsToList (_: db: { - name = db.user; - ensureDBOwnership = true; - }) (lib.filterAttrs (_: db: db.ensureUser) enabledDatabases); - - extraDatabaseUsers = - builtins.map (dbName: { - name = dbName; - ensureDBOwnership = true; - }) - extraDatabasesList; - - serviceDatabases = lib.mapAttrsToList (_: db: db.database) enabledDatabases; - extraDatabaseNames = extraDatabasesList; - - serviceUserMappings = lib.mapAttrsToList (_: db: "user_map ${db.user} ${db.user}") enabledDatabases; - extraUserMappings = builtins.map (dbName: "user_map ${dbName} ${dbName}") extraDatabasesList; - - builtinServiceMappings = let - forgejoMapping = lib.optional (config.services.forgejo.enable && config.services.forgejo.database.type == "postgres") "user_map forgejo forgejo"; - immichMapping = lib.optional (config.services.immich.enable && config.services.immich.database.enable) "user_map immich immich"; - paperlessMapping = lib.optional (config.services.paperless.enable && config.services.paperless.database.createLocally) "user_map paperless paperless"; - in - forgejoMapping ++ immichMapping ++ paperlessMapping; -in { - options = { - services.postgresql = { - databases = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { - options = { - enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Whether to create this database and user"; - }; - user = lib.mkOption { - type = lib.types.str; - default = name; - description = "Database user name"; - }; - database = lib.mkOption { - type = lib.types.str; - default = name; - description = "Database name"; - }; - ensureUser = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to ensure the user exists"; - }; - }; - })); - default = {}; - description = "Databases to create for services"; - }; - - extraDatabases = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = []; - description = "Additional databases to create (user name will match database name)"; - example = ["custom_db" "test_db"]; - }; - - adminUsers = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = []; - description = "System users who should have PostgreSQL superuser access"; - example = ["leyla" "admin"]; - }; - }; - }; - - config = lib.mkIf config.services.postgresql.enable { - services = { - postgresql = { - package = pkgs.postgresql_16; - - ensureUsers = - [ - {name = "postgres";} - ] - ++ serviceDatabaseUsers ++ extraDatabaseUsers; - - ensureDatabases = serviceDatabases ++ extraDatabaseNames; - - identMap = - '' - # ArbitraryMapName systemUser DBUser - - # Administration Users - superuser_map root postgres - superuser_map postgres postgres - '' - + ( - lib.strings.concatLines (builtins.map (user: "superuser_map ${user} postgres") config.services.postgresql.adminUsers) - ) - + '' - - # Client Users - '' - + ( - lib.strings.concatLines (serviceUserMappings ++ extraUserMappings ++ builtinServiceMappings) - ); - - authentication = pkgs.lib.mkOverride 10 '' - # type database DBuser origin-address auth-method optional_ident_map - local all postgres peer map=superuser_map - local sameuser all peer map=user_map - ''; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/postgres/storage.nix b/legacy-modules/nixos-modules/server/postgres/storage.nix deleted file mode 100644 index 58a84a6..0000000 --- a/legacy-modules/nixos-modules/server/postgres/storage.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - config, - lib, - ... -}: let - dataDir = "/var/lib/postgresql/16"; -in { - options.services.postgresql.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.postgresql.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.postgresql.enable { - storage.datasets.replicate."system/root" = { - directories."${dataDir}" = lib.mkIf config.services.postgresql.impermanence.enable { - owner.name = "postgres"; - group.name = "postgres"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/qbittorent/default.nix b/legacy-modules/nixos-modules/server/qbittorent/default.nix deleted file mode 100644 index 11cc449..0000000 --- a/legacy-modules/nixos-modules/server/qbittorent/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{...}: { - imports = [ - ./qbittorent.nix - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/qbittorent/qbittorent.nix b/legacy-modules/nixos-modules/server/qbittorent/qbittorent.nix deleted file mode 100644 index 44603c8..0000000 --- a/legacy-modules/nixos-modules/server/qbittorent/qbittorent.nix +++ /dev/null @@ -1,18 +0,0 @@ -{ - lib, - config, - ... -}: { - options.services.qbittorrent = { - mediaDir = lib.mkOption { - type = lib.types.path; - description = lib.mdDoc '' - The directory to create to store qbittorrent media. - ''; - }; - }; - - config = lib.mkIf config.services.qbittorrent.enable { - # Main qbittorrent configuration goes here if needed - }; -} diff --git a/legacy-modules/nixos-modules/server/qbittorent/storage.nix b/legacy-modules/nixos-modules/server/qbittorent/storage.nix deleted file mode 100644 index da82bcc..0000000 --- a/legacy-modules/nixos-modules/server/qbittorent/storage.nix +++ /dev/null @@ -1,46 +0,0 @@ -{ - lib, - config, - ... -}: let - qbittorent_profile_directory = "/var/lib/qBittorrent/"; -in { - options.services.qbittorrent.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.qbittorrent.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.qbittorrent.enable { - storage.datasets.replicate = { - "system/root" = { - directories."${qbittorent_profile_directory}" = lib.mkIf config.services.qbittorrent.impermanence.enable { - owner.name = "qbittorrent"; - group.name = "qbittorrent"; - }; - }; - "system/media" = { - mount = "/persist/replicate/system/media"; - - directories."${config.services.qbittorrent.mediaDir}" = lib.mkIf config.services.qbittorrent.impermanence.enable { - owner.name = "qbittorrent"; - group.name = "qbittorrent"; - owner.permissions = { - read = true; - write = true; - execute = true; - }; - group.permissions = { - read = true; - write = true; - execute = true; - }; - other.permissions = { - read = true; - write = false; - execute = true; - }; - }; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/radarr/default.nix b/legacy-modules/nixos-modules/server/radarr/default.nix deleted file mode 100644 index cb2a5f0..0000000 --- a/legacy-modules/nixos-modules/server/radarr/default.nix +++ /dev/null @@ -1,5 +0,0 @@ -{...}: { - imports = [ - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/radarr/storage.nix b/legacy-modules/nixos-modules/server/radarr/storage.nix deleted file mode 100644 index 8f991c0..0000000 --- a/legacy-modules/nixos-modules/server/radarr/storage.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - lib, - config, - ... -}: let - radarr_data_directory = "/var/lib/radarr/.config/Radarr"; -in { - options.services.radarr.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.radarr.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.radarr.enable { - storage.datasets.replicate."system/root" = { - directories."${radarr_data_directory}" = lib.mkIf config.services.radarr.impermanence.enable { - owner.name = "radarr"; - group.name = "radarr"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/reverseProxy/default.nix b/legacy-modules/nixos-modules/server/reverseProxy/default.nix deleted file mode 100644 index 336e28b..0000000 --- a/legacy-modules/nixos-modules/server/reverseProxy/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{...}: { - imports = [ - ./reverseProxy.nix - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/reverseProxy/reverseProxy.nix b/legacy-modules/nixos-modules/server/reverseProxy/reverseProxy.nix deleted file mode 100644 index eecc9bf..0000000 --- a/legacy-modules/nixos-modules/server/reverseProxy/reverseProxy.nix +++ /dev/null @@ -1,176 +0,0 @@ -{ - lib, - config, - ... -}: { - options.services.reverseProxy = { - enable = lib.mkEnableOption "turn on the reverse proxy"; - openFirewall = lib.mkEnableOption "open the firewall"; - refuseUnmatchedDomains = lib.mkOption { - type = lib.types.bool; - description = "refuse connections for domains that don't match any configured virtual hosts"; - default = true; - }; - ports = { - http = lib.mkOption { - type = lib.types.port; - description = "HTTP port for the reverse proxy"; - default = 80; - }; - https = lib.mkOption { - type = lib.types.port; - description = "HTTPS port for the reverse proxy"; - default = 443; - }; - }; - acme = { - enable = lib.mkOption { - type = lib.types.bool; - description = "enable ACME certificate management"; - default = true; - }; - email = lib.mkOption { - type = lib.types.str; - description = "email address for ACME certificate registration"; - }; - }; - services = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { - options = { - target = lib.mkOption { - type = lib.types.str; - description = "what url will all traffic to this application be forwarded to"; - }; - domain = lib.mkOption { - type = lib.types.str; - description = "what is the default subdomain to be used for this application to be used for"; - default = name; - }; - extraDomains = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "extra domains that should be configured for this domain"; - default = []; - }; - settings = { - certificateRenewal.enable = lib.mkOption { - type = lib.types.bool; - description = "auto renew certificates"; - default = true; - }; - forceSSL.enable = lib.mkOption { - type = lib.types.bool; - description = "auto renew certificates"; - default = true; - }; - proxyHeaders = { - enable = lib.mkEnableOption "should we proxy headers"; - timeout = lib.mkOption { - type = lib.types.int; - default = 60; - }; - }; - proxyWebsockets.enable = lib.mkEnableOption "should the default config proxy websockets"; - forwardHeaders.enable = lib.mkEnableOption "should the default config contain forward headers"; - noSniff.enable = lib.mkEnableOption "should the no sniff flags be set"; - proxyBuffering.enable = lib.mkOption { - type = lib.types.bool; - description = "should proxy buffering be enabled"; - default = true; - }; - maxBodySize = lib.mkOption { - type = lib.types.nullOr lib.types.int; - description = ""; - default = null; - }; - }; - }; - })); - }; - }; - - config = let - httpPort = config.services.reverseProxy.ports.http; - httpsPort = config.services.reverseProxy.ports.https; - in - lib.mkIf config.services.reverseProxy.enable { - security.acme = lib.mkIf config.services.reverseProxy.acme.enable { - acceptTerms = true; - defaults.email = config.services.reverseProxy.acme.email; - }; - - services.nginx = { - enable = true; - virtualHosts = lib.mkMerge ( - (lib.optionals config.services.reverseProxy.refuseUnmatchedDomains [ - { - "_" = { - default = true; - serverName = "_"; - locations."/" = { - extraConfig = '' - return 444; - ''; - }; - }; - } - ]) - ++ lib.lists.flatten ( - lib.attrsets.mapAttrsToList ( - name: service: let - hostConfig = { - forceSSL = service.settings.forceSSL.enable; - enableACME = service.settings.certificateRenewal.enable; - locations = { - "/" = { - proxyPass = service.target; - proxyWebsockets = service.settings.proxyWebsockets.enable; - recommendedProxySettings = service.settings.forwardHeaders.enable; - extraConfig = let - # Client upload size configuration - maxBodySizeConfig = - lib.optionalString (service.settings.maxBodySize != null) - "client_max_body_size ${toString service.settings.maxBodySize}M;"; - - # Security header configuration - noSniffConfig = - lib.optionalString service.settings.noSniff.enable - "add_header X-Content-Type-Options nosniff;"; - - # Proxy buffering configuration - proxyBufferingConfig = - lib.optionalString (!service.settings.proxyBuffering.enable) - "proxy_buffering off;"; - - # Proxy timeout configuration - proxyTimeoutConfig = - lib.optionalString service.settings.proxyHeaders.enable - '' - proxy_read_timeout ${toString service.settings.proxyHeaders.timeout}s; - proxy_connect_timeout ${toString service.settings.proxyHeaders.timeout}s; - proxy_send_timeout ${toString service.settings.proxyHeaders.timeout}s; - ''; - in - maxBodySizeConfig + noSniffConfig + proxyBufferingConfig + proxyTimeoutConfig; - }; - }; - }; - in ( - [ - { - ${service.domain} = hostConfig; - } - ] - ++ builtins.map (domain: {${domain} = hostConfig;}) - service.extraDomains - ) - ) - config.services.reverseProxy.services - ) - ); - }; - networking.firewall.allowedTCPPorts = lib.mkIf config.services.reverseProxy.openFirewall [ - httpPort - httpsPort - ]; - }; -} diff --git a/legacy-modules/nixos-modules/server/reverseProxy/storage.nix b/legacy-modules/nixos-modules/server/reverseProxy/storage.nix deleted file mode 100644 index 62b5451..0000000 --- a/legacy-modules/nixos-modules/server/reverseProxy/storage.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - lib, - config, - ... -}: let - dataDir = "/var/lib/acme"; -in { - options.services.reverseProxy.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.reverseProxy.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.reverseProxy.enable { - storage.datasets.replicate."system/root" = { - directories."${dataDir}" = lib.mkIf config.services.reverseProxy.impermanence.enable { - owner.name = "acme"; - group.name = "acme"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/searx/default.nix b/legacy-modules/nixos-modules/server/searx/default.nix deleted file mode 100644 index 5426380..0000000 --- a/legacy-modules/nixos-modules/server/searx/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{ - imports = [ - ./searx.nix - ./proxy.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/searx/proxy.nix b/legacy-modules/nixos-modules/server/searx/proxy.nix deleted file mode 100644 index e994e4a..0000000 --- a/legacy-modules/nixos-modules/server/searx/proxy.nix +++ /dev/null @@ -1,31 +0,0 @@ -{ - config, - lib, - ... -}: { - options.services.searx = { - extraDomains = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "extra domains that should be configured for searx"; - default = []; - }; - reverseProxy = { - enable = lib.mkOption { - type = lib.types.bool; - default = config.services.searx.enable && config.services.reverseProxy.enable; - }; - }; - }; - - config = lib.mkIf config.services.searx.reverseProxy.enable { - services.reverseProxy.services.searx = { - target = "http://localhost:${toString config.services.searx.settings.server.port}"; - domain = config.services.searx.domain; - extraDomains = config.services.searx.extraDomains; - - settings = { - forwardHeaders.enable = true; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/searx/searx.nix b/legacy-modules/nixos-modules/server/searx/searx.nix deleted file mode 100644 index d4d4012..0000000 --- a/legacy-modules/nixos-modules/server/searx/searx.nix +++ /dev/null @@ -1,59 +0,0 @@ -{ - config, - lib, - inputs, - ... -}: { - config = lib.mkIf config.services.searx.enable { - sops.secrets = { - "services/searx" = { - sopsFile = "${inputs.secrets}/defiant-services.yaml"; - }; - }; - - services.searx = { - environmentFile = config.sops.secrets."services/searx".path; - - # Rate limiting - limiterSettings = { - real_ip = { - x_for = 1; - ipv4_prefix = 32; - ipv6_prefix = 56; - }; - - botdetection = { - ip_limit = { - filter_link_local = true; - link_token = true; - }; - }; - }; - - settings = { - server = { - port = 8083; - secret_key = "@SEARXNG_SECRET@"; - }; - - # Search engine settings - search = { - safe_search = 2; - autocomplete_min = 2; - autocomplete = "duckduckgo"; - }; - - # Enabled plugins - enabled_plugins = [ - "Basic Calculator" - "Hash plugin" - "Tor check plugin" - "Open Access DOI rewrite" - "Hostnames plugin" - "Unit converter plugin" - "Tracker URL remover" - ]; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/sonarr/default.nix b/legacy-modules/nixos-modules/server/sonarr/default.nix deleted file mode 100644 index cb2a5f0..0000000 --- a/legacy-modules/nixos-modules/server/sonarr/default.nix +++ /dev/null @@ -1,5 +0,0 @@ -{...}: { - imports = [ - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/server/sonarr/storage.nix b/legacy-modules/nixos-modules/server/sonarr/storage.nix deleted file mode 100644 index 8587751..0000000 --- a/legacy-modules/nixos-modules/server/sonarr/storage.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - lib, - config, - ... -}: let - sonarr_data_directory = "/var/lib/sonarr/.config/NzbDrone"; -in { - options.services.sonarr.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.sonarr.enable && config.storage.impermanence.enable; - }; - - config = lib.mkIf config.services.sonarr.enable { - storage.datasets.replicate."system/root" = { - directories."${sonarr_data_directory}" = lib.mkIf config.services.sonarr.impermanence.enable { - owner.name = "sonarr"; - group.name = "sonarr"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/server/wyoming.nix b/legacy-modules/nixos-modules/server/wyoming.nix deleted file mode 100644 index 1df6877..0000000 --- a/legacy-modules/nixos-modules/server/wyoming.nix +++ /dev/null @@ -1,63 +0,0 @@ -{ - lib, - config, - ... -}: { - options.services.wyoming.enable = lib.mkEnableOption "should wyoming be enabled on this device"; - config = lib.mkIf config.services.wyoming.enable (lib.mkMerge [ - { - services.wyoming = { - # Text to speech - piper = { - servers = { - "en" = { - enable = true; - # see https://github.com/rhasspy/rhasspy3/blob/master/programs/tts/piper/script/download.py - voice = "en-us-amy-low"; - uri = "tcp://0.0.0.0:10200"; - speaker = 0; - }; - }; - }; - - # Speech to text - faster-whisper = { - servers = { - "en" = { - enable = true; - # see https://github.com/rhasspy/rhasspy3/blob/master/programs/asr/faster-whisper/script/download.py - model = "tiny-int8"; - language = "en"; - uri = "tcp://0.0.0.0:10300"; - device = "cpu"; - }; - }; - }; - - openwakeword = { - enable = true; - uri = "tcp://0.0.0.0:10400"; - # preloadModels = [ - # "ok_nabu" - # ]; - # TODO: custom models - }; - }; - - # needs access to /proc/cpuinfo - systemd.services."wyoming-faster-whisper-en".serviceConfig.ProcSubset = lib.mkForce "all"; - } - (lib.mkIf config.host.impermanence.enable { - environment.persistence."/persist/replicate/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = "/var/lib/private/wyoming"; - mode = "0700"; - } - ]; - }; - }) - ]); -} diff --git a/legacy-modules/nixos-modules/ssh.nix b/legacy-modules/nixos-modules/ssh.nix deleted file mode 100644 index 6fe8e5c..0000000 --- a/legacy-modules/nixos-modules/ssh.nix +++ /dev/null @@ -1,44 +0,0 @@ -{ - lib, - config, - ... -}: { - options = { - services.openssh.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.openssh.enable && config.storage.impermanence.enable; - }; - }; - - config = { - services = { - openssh = { - enable = true; - ports = [22]; - settings = { - PasswordAuthentication = false; - UseDns = true; - X11Forwarding = false; - }; - }; - }; - - storage.datasets.replicate."system/root" = { - files = lib.mkIf config.services.openssh.impermanence.enable (builtins.listToAttrs ( - lib.lists.flatten ( - builtins.map (hostKey: [ - { - name = hostKey.path; - value = {enable = true;}; - } - { - name = "${hostKey.path}.pub"; - value = {enable = true;}; - } - ]) - config.services.openssh.hostKeys - ) - )); - }; - }; -} diff --git a/legacy-modules/nixos-modules/steam.nix b/legacy-modules/nixos-modules/steam.nix deleted file mode 100644 index 20c0978..0000000 --- a/legacy-modules/nixos-modules/steam.nix +++ /dev/null @@ -1,9 +0,0 @@ -{...}: { - programs = { - steam = { - remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play - dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server - localNetworkGameTransfers.openFirewall = true; # Open ports in the firewall for Steam Local Network Game Transfers - }; - }; -} diff --git a/legacy-modules/nixos-modules/storage/impermanence.nix b/legacy-modules/nixos-modules/storage/impermanence.nix deleted file mode 100644 index 4fdf803..0000000 --- a/legacy-modules/nixos-modules/storage/impermanence.nix +++ /dev/null @@ -1,142 +0,0 @@ -args @ { - lib, - config, - ... -}: let - datasetSubmodules = (import ./submodules/dataset.nix) args; - impermanenceDatasetSubmodule = (import ./submodules/impermanenceDataset.nix) args; - - permissionsToMode = permissions: let - permSetToDigit = permSet: - ( - if permSet.read - then 4 - else 0 - ) - + ( - if permSet.write - then 2 - else 0 - ) - + ( - if permSet.execute - then 1 - else 0 - ); - - ownerDigit = permSetToDigit permissions.owner.permissions; - groupDigit = permSetToDigit permissions.group.permissions; - otherDigit = permSetToDigit permissions.other.permissions; - in - toString ownerDigit + toString groupDigit + toString otherDigit; - - # Get the option names from both submodules to automatically determine which are impermanence-specific - regularDatasetEval = lib.evalModules { - modules = [datasetSubmodules]; - specialArgs = args; - }; - impermanenceDatasetEval = lib.evalModules { - modules = [impermanenceDatasetSubmodule]; - specialArgs = args; - }; - - regularDatasetOptions = builtins.attrNames regularDatasetEval.options; - impermanenceDatasetOptions = builtins.attrNames impermanenceDatasetEval.options; - - # Find options that are only in impermanence datasets (not in regular ZFS datasets) - impermanenceOnlyOptions = lib.lists.subtractLists regularDatasetOptions impermanenceDatasetOptions; -in { - options.storage = { - impermanence = { - enable = lib.mkEnableOption "should impermanence be enabled for this system"; - - datasets = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodule); - default = {}; - }; - }; - }; - - config = lib.mkIf config.storage.impermanence.enable (lib.mkMerge [ - { - assertions = [ - { - assertion = config.storage.zfs.enable; - message = "storage.impermanence can not be used without storage.zfs."; - } - ]; - - system.activationScripts = { - # fixes issues with /var/lib/private not having the correct permissions https://github.com/nix-community/impermanence/issues/254 - "createPersistentStorageDirs".deps = ["var-lib-private-permissions" "users" "groups"]; - - "var-lib-private-permissions" = lib.mkIf config.storage.generateBase { - deps = ["specialfs"]; - text = '' - mkdir -p /persist/replicate/system/root/var/lib/private - chmod 0700 /persist/replicate/system/root/var/lib/private - ''; - }; - }; - - programs.fuse.userAllowOther = true; - - # Suppress sudo lecture on every boot since impermanence wipes the lecture status file - security.sudo.extraConfig = "Defaults lecture=never"; - - fileSystems = - lib.mapAttrs' ( - datasetName: dataset: - lib.nameValuePair "/${datasetName}" { - device = "rpool/${datasetName}"; - fsType = "zfs"; - neededForBoot = true; - } - ) - (lib.filterAttrs ( - datasetName: dataset: dataset.impermanence.enable - ) - config.storage.impermanence.datasets); - - environment.persistence = - lib.mapAttrs (datasetName: dataset: { - enable = true; - hideMounts = true; - persistentStoragePath = "/${datasetName}"; - directories = lib.mapAttrsToList (path: dirConfig: { - directory = path; - user = dirConfig.owner.name; - group = dirConfig.group.name; - mode = permissionsToMode dirConfig; - }) (lib.filterAttrs (_: dirConfig: dirConfig.enable) dataset.directories); - files = lib.mapAttrsToList (path: fileConfig: { - file = path; - parentDirectory = { - user = fileConfig.owner.name; - group = fileConfig.group.name; - mode = permissionsToMode fileConfig; - }; - }) (lib.filterAttrs (_: fileConfig: fileConfig.enable) dataset.files); - }) - (lib.filterAttrs ( - datasetName: dataset: let - enabledDirectories = lib.filterAttrs (_: dirConfig: dirConfig.enable) dataset.directories; - enabledFiles = lib.filterAttrs (_: fileConfig: fileConfig.enable) dataset.files; - in - (enabledDirectories != {}) || (enabledFiles != {}) - ) - (lib.filterAttrs ( - datasetName: dataset: dataset.impermanence.enable - ) - config.storage.impermanence.datasets)); - } - (lib.mkIf config.storage.zfs.enable { - storage.zfs.datasets = - lib.mapAttrs ( - datasetName: dataset: - builtins.removeAttrs dataset impermanenceOnlyOptions - ) - config.storage.impermanence.datasets; - }) - ]); -} diff --git a/legacy-modules/nixos-modules/storage/storage.nix b/legacy-modules/nixos-modules/storage/storage.nix deleted file mode 100644 index 771d661..0000000 --- a/legacy-modules/nixos-modules/storage/storage.nix +++ /dev/null @@ -1,216 +0,0 @@ -args @ { - lib, - config, - ... -}: let - datasetSubmodule = (import ./submodules/dataset.nix) args; - impermanenceDatasetSubmodule = (import ./submodules/impermanenceDataset.nix) args; - - # Get the option names from both submodules to automatically determine which are impermanence-specific - regularDatasetEval = lib.evalModules { - modules = [datasetSubmodule]; - specialArgs = args; - }; - impermanenceDatasetEval = lib.evalModules { - modules = [impermanenceDatasetSubmodule]; - specialArgs = args; - }; - - regularDatasetOptions = builtins.attrNames regularDatasetEval.options; - impermanenceDatasetOptions = builtins.attrNames impermanenceDatasetEval.options; - - # Find options that are only in impermanence datasets (not in regular ZFS datasets) - impermanenceOnlyOptions = lib.lists.subtractLists regularDatasetOptions impermanenceDatasetOptions; -in { - options.storage = { - generateBase = lib.mkOption { - type = lib.types.bool; - default = true; - description = '' - When enabled, enables automatic generation of base datasets (ephemeral, local, replicate roots). - This allows manual definition of datasets matching an existing system layout for migration purposes. - ''; - }; - datasets = { - ephemeral = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule datasetSubmodule); - default = {}; - }; - local = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodule); - default = {}; - }; - replicate = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodule); - default = {}; - }; - }; - }; - - config = lib.mkMerge [ - (lib.mkIf (config.storage.zfs.enable && config.storage.generateBase) { - # Create ZFS datasets based on storage.datasets configuration - storage.datasets = { - local = { - "nix" = { - impermanence.enable = false; - type = "zfs_fs"; - mount = "/nix"; - snapshot = { - autoSnapshot = false; - }; - atime = "off"; - relatime = "off"; - }; - }; - }; - }) - (lib.mkIf (config.storage.zfs.enable && config.storage.impermanence.enable && config.storage.generateBase) { - storage.datasets = { - ephemeral = { - "" = { - type = "zfs_fs"; - mount = null; - }; - "system/root" = { - type = "zfs_fs"; - mount = "/"; - snapshot = { - blankSnapshot = true; - }; - }; - }; - # TODO: can we auto set the mount points on these to just be `"/persist/local/${name}"` - local = { - "" = { - mount = "/persist/local"; - }; - }; - # TODO: can we auto set the mount points on these to just be `"/persist/replicate/${name}"` - replicate = { - "" = { - mount = "/persist/replicate"; - }; - "system/root" = { - mount = "/persist/replicate/system/root"; - snapshot = { - autoSnapshot = true; - }; - directories = { - "/var/lib/nixos".enable = true; - "/var/lib/systemd/coredump".enable = true; - }; - files = { - "/etc/machine-id".enable = true; - }; - }; - "home" = { - mount = "/persist/replicate/home"; - snapshot = { - autoSnapshot = true; - }; - }; - "system/var/log" = { - type = "zfs_fs"; - directories = { - "/var/log".enable = true; - }; - }; - }; - }; - - storage.zfs.datasets = lib.mkMerge [ - (lib.mapAttrs' (name: dataset: { - name = - if name == "" - then "ephemeral" - else "ephemeral/${name}"; - value = dataset; - }) - config.storage.datasets.ephemeral) - ]; - - boot.initrd.postResumeCommands = lib.mkAfter '' - zfs rollback -r rpool/ephemeral/system/root@blank - ''; - - storage.impermanence.datasets = lib.mkMerge [ - (lib.mapAttrs' (name: dataset: { - name = - if name == "" - then "persist/local" - else "persist/local/${name}"; - value = dataset; - }) - config.storage.datasets.local) - (lib.mapAttrs' (name: dataset: { - name = - if name == "" - then "persist/replicate" - else "persist/replicate/${name}"; - value = dataset; - }) - config.storage.datasets.replicate) - ]; - }) - (lib.mkIf (config.storage.zfs.enable && !config.storage.impermanence.enable && config.storage.generateBase) { - storage.datasets = { - # Base organizational datasets (only needed when impermanence is disabled) - local = { - "" = { - type = "zfs_fs"; - mount = null; - }; - "root" = { - type = "zfs_fs"; - mount = "/"; - compression = "lz4"; - acltype = "posixacl"; - relatime = "on"; - xattr = "sa"; - snapshot = { - autoSnapshot = true; - blankSnapshot = true; - }; - }; - }; - replicate = { - "" = { - type = "zfs_fs"; - mount = null; - }; - "system/var/log" = { - type = "zfs_fs"; - mount = "/var/log"; - }; - }; - }; - - storage.zfs.datasets = lib.mkMerge [ - (lib.mapAttrs' (name: dataset: { - name = - if name == "" - then "persist/local" - else "persist/local/${name}"; - value = builtins.removeAttrs dataset impermanenceOnlyOptions; - }) - config.storage.datasets.local) - (lib.mapAttrs' (name: dataset: { - name = - if name == "" - then "persist/replicate" - else "persist/replicate/${name}"; - value = builtins.removeAttrs dataset impermanenceOnlyOptions; - }) - config.storage.datasets.replicate) - ]; - }) - ]; - - # TODO: set up datasets for systemd services that want a dataset created - # TODO: home-manager.users..storage.impermanence.enable - # is false then persist the entire directory of the user - # if true persist home-manager.users..storage.impermanence.datasets - # TODO: systemd.services..storage.datasets persists - # TODO: configure other needed storage modes here -} diff --git a/legacy-modules/nixos-modules/storage/submodules/dataset.nix b/legacy-modules/nixos-modules/storage/submodules/dataset.nix deleted file mode 100644 index 2a45552..0000000 --- a/legacy-modules/nixos-modules/storage/submodules/dataset.nix +++ /dev/null @@ -1,86 +0,0 @@ -{lib, ...}: {name, ...}: { - options = { - type = lib.mkOption { - type = lib.types.enum ["zfs_fs" "zfs_volume"]; - default = "zfs_fs"; - description = "Type of ZFS dataset (filesystem or volume)"; - }; - - acltype = lib.mkOption { - type = lib.types.nullOr (lib.types.enum ["off" "nfsv4" "posixacl"]); - default = null; - description = "Access control list type"; - }; - - relatime = lib.mkOption { - type = lib.types.nullOr (lib.types.enum ["on" "off"]); - default = null; - description = "Controls when access time is updated"; - }; - - atime = lib.mkOption { - type = lib.types.nullOr (lib.types.enum ["on" "off"]); - default = null; - description = "Controls whether access time is updated"; - }; - - xattr = lib.mkOption { - type = lib.types.nullOr (lib.types.enum ["on" "off" "sa" "dir"]); - default = null; - description = "Extended attribute storage method"; - }; - - compression = lib.mkOption { - type = lib.types.nullOr (lib.types.enum ["on" "off" "lz4" "gzip" "zstd" "lzjb" "zle"]); - default = null; - description = "Compression algorithm to use"; - }; - - sync = lib.mkOption { - type = lib.types.nullOr (lib.types.enum ["standard" "always" "disabled"]); - default = null; - description = "Synchronous write behavior"; - }; - - mount = lib.mkOption { - type = lib.types.nullOr lib.types.str; - description = "Controls the mount point used for this file system"; - default = null; - }; - - encryption = { - enable = lib.mkEnableOption "should encryption be enabled"; - type = lib.mkOption { - type = lib.types.enum ["aes-128-ccm" "aes-192-ccm" "aes-256-ccm" "aes-128-gcm" "aes-192-gcm" "aes-256-gcm"]; - description = "What encryption type to use"; - }; - keyformat = lib.mkOption { - type = lib.types.enum ["raw" "hex" "passphrase"]; - description = "Format of the encryption key"; - }; - keylocation = lib.mkOption { - type = lib.types.str; - description = "Location of the encryption key"; - }; - }; - - snapshot = { - # This option should set this option flag - autoSnapshot = lib.mkEnableOption "Enable automatic snapshots for this dataset"; - # Creates a blank snapshot in the post create hook for rollback purposes - blankSnapshot = lib.mkEnableOption "Should a blank snapshot be auto created in the post create hook"; - }; - - recordSize = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = "Suggested block size for files in the file system"; - }; - - postCreateHook = lib.mkOption { - type = lib.types.str; - default = ""; - description = "Script to run after dataset creation"; - }; - }; -} diff --git a/legacy-modules/nixos-modules/storage/submodules/impermanenceDataset.nix b/legacy-modules/nixos-modules/storage/submodules/impermanenceDataset.nix deleted file mode 100644 index e4d3584..0000000 --- a/legacy-modules/nixos-modules/storage/submodules/impermanenceDataset.nix +++ /dev/null @@ -1,56 +0,0 @@ -args @ {lib, ...}: {name, ...}: let - datasetSubmodule = (import ./dataset.nix) args; - pathPermissions = { - read = lib.mkEnableOption "should the path have read permissions"; - write = lib.mkEnableOption "should the path have read permissions"; - execute = lib.mkEnableOption "should the path have read permissions"; - }; - pathTypeSubmodule = {name, ...}: { - options = { - enable = lib.mkOption { - type = lib.types.bool; - default = true; - }; - owner = { - name = lib.mkOption { - type = lib.types.str; - default = "root"; - }; - permissions = pathPermissions; - }; - group = { - name = lib.mkOption { - type = lib.types.str; - default = "root"; - }; - permissions = pathPermissions; - }; - other = { - permissions = pathPermissions; - }; - }; - }; -in { - imports = [ - datasetSubmodule - ]; - - options = { - files = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule pathTypeSubmodule); - default = {}; - }; - directories = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule pathTypeSubmodule); - default = {}; - }; - impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = true; - }; - }; - - config = { - mount = lib.mkDefault "/${name}"; - }; -} diff --git a/legacy-modules/nixos-modules/storage/zfs.nix b/legacy-modules/nixos-modules/storage/zfs.nix deleted file mode 100644 index 2fc6cb4..0000000 --- a/legacy-modules/nixos-modules/storage/zfs.nix +++ /dev/null @@ -1,347 +0,0 @@ -args @ { - lib, - pkgs, - config, - ... -}: let - datasetSubmodule = (import ./submodules/dataset.nix) args; - - # Hash function for disk names (max 27 chars to fit GPT limitations) - hashDisk = drive: (builtins.substring 0 27 (builtins.hashString "sha256" drive)); - - # Map "stripe" to "" for disko compatibility (disko uses "" for stripe mode) - diskoPoolMode = - if config.storage.zfs.pool.mode == "stripe" - then "" - else config.storage.zfs.pool.mode; - - # Helper to flatten vdevs into list of devices with names - allVdevDevices = lib.lists.flatten (builtins.map ( - vdev: - builtins.map ( - device: - lib.attrsets.nameValuePair (hashDisk device.device) device - ) - vdev - ) - config.storage.zfs.pool.vdevs); - - # Cache devices with names - allCacheDevices = builtins.map ( - device: - lib.attrsets.nameValuePair (hashDisk device.device) device - ) (config.storage.zfs.pool.cache); - - # All devices (vdevs + cache) - allDevices = allVdevDevices ++ allCacheDevices; - - # Boot devices - filter devices that have boot = true - bootDevices = builtins.filter (device: device.value.boot) allDevices; - - # Helper function to convert dataset options to ZFS properties - datasetToZfsOptions = dataset: let - baseOptions = - (lib.attrsets.optionalAttrs (dataset.acltype != null) {acltype = dataset.acltype;}) - // (lib.attrsets.optionalAttrs (dataset.relatime != null) {relatime = dataset.relatime;}) - // (lib.attrsets.optionalAttrs (dataset.atime != null) {atime = dataset.atime;}) - // (lib.attrsets.optionalAttrs (dataset.xattr != null) {xattr = dataset.xattr;}) - // (lib.attrsets.optionalAttrs (dataset.compression != null) {compression = dataset.compression;}) - // (lib.attrsets.optionalAttrs (dataset.sync != null) {sync = dataset.sync;}) - // (lib.attrsets.optionalAttrs (dataset.recordSize != null) {recordSize = dataset.recordSize;}); - - encryptionOptions = lib.attrsets.optionalAttrs (dataset.encryption.enable) ( - (lib.attrsets.optionalAttrs (dataset.encryption ? type) {encryption = dataset.encryption.type;}) - // (lib.attrsets.optionalAttrs (dataset.encryption ? keyformat) {keyformat = dataset.encryption.keyformat;}) - // (lib.attrsets.optionalAttrs (dataset.encryption ? keylocation) {keylocation = dataset.encryption.keylocation;}) - ); - - mountOptions = lib.attrsets.optionalAttrs (dataset ? mount && dataset.mount ? enable) ( - if builtins.isBool dataset.mount.enable - then { - canmount = - if dataset.mount.enable - then "on" - else "off"; - } - else {canmount = dataset.mount.enable;} - ); - - snapshotOptions = lib.attrsets.optionalAttrs (dataset ? snapshot && dataset.snapshot ? autoSnapshot) { - "com.sun:auto-snapshot" = - if dataset.snapshot.autoSnapshot - then "true" - else "false"; - }; - in - baseOptions // encryptionOptions // mountOptions // snapshotOptions; - - # Helper to generate post create hooks - generatePostCreateHook = name: dataset: - dataset.postCreateHook - + (lib.optionalString dataset.snapshot.blankSnapshot '' - zfs snapshot rpool/${name}@blank - ''); - - # Convert datasets to disko format - convertedDatasets = builtins.listToAttrs ( - (lib.attrsets.mapAttrsToList ( - name: dataset: - lib.attrsets.nameValuePair name { - type = dataset.type; - options = datasetToZfsOptions dataset; - mountpoint = dataset.mount or null; - postCreateHook = generatePostCreateHook name dataset; - } - ) - config.storage.zfs.datasets) - ++ (lib.optional (config.storage.zfs.rootDataset != null) ( - lib.attrsets.nameValuePair "" { - type = config.storage.zfs.rootDataset.type; - options = datasetToZfsOptions config.storage.zfs.rootDataset; - mountpoint = config.storage.zfs.rootDataset.mount or null; - postCreateHook = generatePostCreateHook "" config.storage.zfs.rootDataset; - } - )) - ); -in { - options.storage = { - zfs = { - enable = lib.mkEnableOption "Should zfs be enabled on this system."; - - notifications = { - enable = lib.mkEnableOption "are notifications enabled"; - host = lib.mkOption { - type = lib.types.str; - description = "what is the host that we are going to send the email to"; - }; - port = lib.mkOption { - type = lib.types.port; - description = "what port is the host using to receive mail on"; - }; - to = lib.mkOption { - type = lib.types.str; - description = "what account is the email going to be sent to"; - }; - user = lib.mkOption { - type = lib.types.str; - description = "what user is the email going to be set from"; - }; - tokenFile = lib.mkOption { - type = lib.types.str; - description = "file containing the password to be used by msmtp for notifications"; - }; - }; - - pool = let - deviceType = - lib.types.coercedTo lib.types.str (device: { - device = device; - boot = false; - }) (lib.types.submodule { - options = { - device = lib.mkOption { - type = lib.types.str; - }; - boot = lib.mkEnableOption "should this device be a boot device"; - }; - }); - in { - encryption = { - enable = lib.mkEnableOption "Should encryption be enabled on this pool."; - keyformat = lib.mkOption { - type = lib.types.enum ["raw" "hex" "passphrase"]; - default = "hex"; - description = "Format of the encryption key"; - }; - keylocation = lib.mkOption { - type = lib.types.str; - default = "prompt"; - description = "Location of the encryption key"; - }; - }; - mode = lib.mkOption { - type = lib.types.enum ["stripe" "mirror" "raidz1" "raidz2" "raidz3"]; - default = "raidz2"; - description = "ZFS redundancy mode for the pool"; - }; - bootPartitionSize = lib.mkOption { - type = lib.types.str; - default = "2G"; - description = "Size of the boot partition on boot drives"; - }; - vdevs = lib.mkOption { - type = lib.types.listOf (lib.types.listOf deviceType); - default = []; - description = "List of vdevs, where each vdev is a list of devices"; - }; - cache = lib.mkOption { - type = lib.types.listOf deviceType; - default = []; - }; - }; - - rootDataset = lib.mkOption { - type = lib.types.nullOr (lib.types.submodule datasetSubmodule); - description = "Root ZFS dataset to create"; - default = null; - }; - - datasets = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule datasetSubmodule); - description = "Additional ZFS datasets to create"; - default = {}; - }; - }; - }; - - config = lib.mkIf config.storage.zfs.enable (lib.mkMerge [ - { - # Assertion that we have at least one boot device - assertions = [ - { - assertion = (builtins.length bootDevices) > 0; - message = "ZFS configuration requires at least one boot device. Set boot = true for at least one device in your vdevs or cache."; - } - ]; - - # # Warning about disk/dataset mismatches - these would be runtime checks - # warnings = let - # configuredDisks = builtins.map (device: device.device) (builtins.map (dev: dev.value) allDevices); - # diskWarnings = - # lib.optional (config.storage.zfs.enable) - # "ZFS: Please ensure the following disks are available on your system: ${builtins.concatStringsSep ", " configuredDisks}"; - - # configuredDatasets = builtins.attrNames config.storage.zfs.datasets; - # datasetWarnings = - # lib.optional (config.storage.zfs.enable && (builtins.length configuredDatasets) > 0) - # "ZFS: Configured datasets: ${builtins.concatStringsSep ", " configuredDatasets}. Ensure these match your intended ZFS layout."; - # in - # diskWarnings ++ datasetWarnings; - - services.zfs = { - autoScrub.enable = true; - autoSnapshot.enable = true; - }; - - # # Configure disko for ZFS setup - disko.devices = { - disk = builtins.listToAttrs ( - builtins.map ( - drive: - lib.attrsets.nameValuePair (drive.name) { - type = "disk"; - device = "/dev/disk/by-id/${drive.value.device}"; - content = { - type = "gpt"; - partitions = { - ESP = lib.mkIf drive.value.boot { - size = config.storage.zfs.pool.bootPartitionSize; - type = "EF00"; - content = { - type = "filesystem"; - format = "vfat"; - mountpoint = "/boot"; - mountOptions = ["umask=0077"]; - }; - }; - zfs = { - size = "100%"; - content = { - type = "zfs"; - pool = "rpool"; - }; - }; - }; - }; - } - ) - allDevices - ); - - zpool = { - rpool = { - type = "zpool"; - mode = { - topology = { - type = "topology"; - vdev = - builtins.map (vdev: { - mode = diskoPoolMode; - members = builtins.map (device: hashDisk device.device) vdev; - }) - config.storage.zfs.pool.vdevs; - cache = builtins.map (device: hashDisk device.device) config.storage.zfs.pool.cache; - }; - }; - - options = { - ashift = "12"; - autotrim = "on"; - }; - - rootFsOptions = - { - canmount = "off"; - mountpoint = "none"; - xattr = "sa"; - acltype = "posixacl"; - relatime = "on"; - compression = "lz4"; - "com.sun:auto-snapshot" = "false"; - } - // (lib.attrsets.optionalAttrs config.storage.zfs.pool.encryption.enable { - encryption = "on"; - keyformat = config.storage.zfs.pool.encryption.keyformat; - keylocation = config.storage.zfs.pool.encryption.keylocation; - }); - - datasets = convertedDatasets; - }; - }; - }; - } - (lib.mkIf config.storage.zfs.notifications.enable { - programs.msmtp = { - enable = true; - setSendmail = true; - defaults = { - aliases = "/etc/aliases"; - port = config.storage.zfs.notifications.port; - tls_trust_file = "/etc/ssl/certs/ca-certificates.crt"; - tls = "on"; - auth = "login"; - tls_starttls = "off"; - }; - accounts = { - zfs_notifications = { - auth = true; - tls = true; - host = config.storage.zfs.notifications.host; - passwordeval = "cat ${config.storage.zfs.notifications.tokenFile}"; - user = config.storage.zfs.notifications.user; - from = config.storage.zfs.notifications.user; - }; - }; - }; - - services.zfs = { - zed = { - enableMail = true; - - settings = { - ZED_DEBUG_LOG = "/tmp/zed.debug.log"; - ZED_EMAIL_ADDR = [config.storage.zfs.notifications.to]; - ZED_EMAIL_PROG = "${pkgs.msmtp}/bin/msmtp"; - ZED_EMAIL_OPTS = "-a zfs_notifications @ADDRESS@"; - - ZED_NOTIFY_INTERVAL_SECS = 3600; - ZED_NOTIFY_VERBOSE = true; - - ZED_USE_ENCLOSURE_LEDS = true; - ZED_SCRUB_AFTER_RESILVER = true; - }; - }; - }; - }) - ]); -} diff --git a/legacy-modules/nixos-modules/sync/default.nix b/legacy-modules/nixos-modules/sync/default.nix deleted file mode 100644 index 5640417..0000000 --- a/legacy-modules/nixos-modules/sync/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{...}: { - imports = [ - ./sync.nix - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/sync/storage.nix b/legacy-modules/nixos-modules/sync/storage.nix deleted file mode 100644 index 61bf855..0000000 --- a/legacy-modules/nixos-modules/sync/storage.nix +++ /dev/null @@ -1,32 +0,0 @@ -{ - config, - lib, - ... -}: let - mountDir = "/mnt/sync"; - configDir = "/etc/syncthing"; -in { - options = { - services.syncthing.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.syncthing.enable && config.storage.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.syncthing.enable { - storage.datasets.replicate."system/root" = { - directories = { - "${mountDir}" = lib.mkIf config.services.syncthing.impermanence.enable { - enable = true; - owner.name = "syncthing"; - group.name = "syncthing"; - }; - "${configDir}" = lib.mkIf config.services.syncthing.impermanence.enable { - enable = true; - owner.name = "syncthing"; - group.name = "syncthing"; - }; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/sync/sync.nix b/legacy-modules/nixos-modules/sync/sync.nix deleted file mode 100644 index 28b6e38..0000000 --- a/legacy-modules/nixos-modules/sync/sync.nix +++ /dev/null @@ -1,36 +0,0 @@ -{ - config, - lib, - syncthingConfiguration, - ... -}: let - mountDir = "/mnt/sync"; - configDir = "/etc/syncthing"; -in { - config = lib.mkMerge [ - { - systemd = lib.mkIf config.services.syncthing.enable { - tmpfiles.rules = [ - "A ${mountDir} - - - - u:syncthing:rwX,g:syncthing:rwX,o::-" - "d ${mountDir} 2755 syncthing syncthing -" - "d ${config.services.syncthing.dataDir} 775 syncthing syncthing -" - "d ${config.services.syncthing.configDir} 755 syncthing syncthing -" - ]; - }; - } - (lib.mkIf config.services.syncthing.enable (lib.mkMerge [ - { - services.syncthing = { - user = "syncthing"; - group = "syncthing"; - dataDir = "${mountDir}/default"; - configDir = configDir; - overrideDevices = true; - overrideFolders = true; - configuration = syncthingConfiguration; - deviceName = config.networking.hostName; - }; - } - ])) - ]; -} diff --git a/legacy-modules/nixos-modules/system.nix b/legacy-modules/nixos-modules/system.nix deleted file mode 100644 index b839067..0000000 --- a/legacy-modules/nixos-modules/system.nix +++ /dev/null @@ -1,13 +0,0 @@ -{...}: { - nix = { - gc = { - automatic = true; - dates = "weekly"; - options = "--delete-older-than 7d"; - }; - optimise = { - automatic = true; - dates = ["weekly"]; - }; - }; -} diff --git a/legacy-modules/nixos-modules/tailscale/default.nix b/legacy-modules/nixos-modules/tailscale/default.nix deleted file mode 100644 index 7a283e8..0000000 --- a/legacy-modules/nixos-modules/tailscale/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{...}: { - imports = [ - ./tailscale.nix - ./storage.nix - ]; -} diff --git a/legacy-modules/nixos-modules/tailscale/storage.nix b/legacy-modules/nixos-modules/tailscale/storage.nix deleted file mode 100644 index 7ac7e9a..0000000 --- a/legacy-modules/nixos-modules/tailscale/storage.nix +++ /dev/null @@ -1,24 +0,0 @@ -{ - config, - lib, - ... -}: let - tailscale_data_directory = "/var/lib/tailscale"; -in { - options = { - services.tailscale.impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.tailscale.enable && config.storage.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.tailscale.enable { - storage.datasets.replicate."system/root" = { - directories."${tailscale_data_directory}" = lib.mkIf config.services.tailscale.impermanence.enable { - enable = true; - owner.name = "root"; - group.name = "root"; - }; - }; - }; -} diff --git a/legacy-modules/nixos-modules/tailscale/tailscale.nix b/legacy-modules/nixos-modules/tailscale/tailscale.nix deleted file mode 100644 index 06899b1..0000000 --- a/legacy-modules/nixos-modules/tailscale/tailscale.nix +++ /dev/null @@ -1,19 +0,0 @@ -{ - config, - lib, - ... -}: { - options = { - host.tailscale = { - enable = lib.mkEnableOption "should tailscale be enabled on this computer"; - }; - }; - - config = lib.mkIf config.services.tailscale.enable ( - lib.mkMerge [ - { - # any configs we want shared between all machines - } - ] - ); -} diff --git a/legacy-modules/nixos-modules/users.nix b/legacy-modules/nixos-modules/users.nix deleted file mode 100644 index 9cef952..0000000 --- a/legacy-modules/nixos-modules/users.nix +++ /dev/null @@ -1,432 +0,0 @@ -{ - lib, - config, - inputs, - ... -}: let - SOPS_AGE_KEY_DIRECTORY = import ../../const/sops_age_key_directory.nix; - - host = config.host; - - principleUsers = host.principleUsers; - terminalUsers = host.terminalUsers; - normalUsers = host.normalUsers; - - uids = { - leyla = 1000; - eve = 1002; - # ester = 1003; - # ivy = 1004; - jellyfin = 2000; - forgejo = 2002; - hass = 2004; - syncthing = 2007; - ollama = 2008; - git = 2009; - immich = 2010; - qbittorrent = 2011; - paperless = 2012; - actual = 2013; - radarr = 2014; - sonarr = 2015; - bazarr = 2016; - lidarr = 2017; - crab-hole = 2018; - }; - - gids = { - leyla = 1000; - eve = 1002; - # ester = 1003 - # ivy = 1004; - users = 100; - jellyfin_media = 2001; - jellyfin = 2000; - forgejo = 2002; - hass = 2004; - syncthing = 2007; - ollama = 2008; - git = 2009; - immich = 2010; - qbittorrent = 2011; - paperless = 2012; - actual = 2013; - radarr = 2014; - sonarr = 2015; - bazarr = 2016; - lidarr = 2017; - crab-hole = 2018; - }; - - users = config.users.users; - leyla = users.leyla.name; - eve = users.eve.name; -in { - config = lib.mkMerge [ - { - # principle users are by definition trusted - nix.settings.trusted-users = builtins.map (user: user.name) principleUsers; - - # we should only be able to ssh into principle users of a computer who are also set up for terminal access - services.openssh.settings.AllowUsers = builtins.map (user: user.name) (lib.lists.intersectLists terminalUsers principleUsers); - - # we need to set up env variables to nix can find keys to decrypt passwords on rebuild - environment = { - sessionVariables = { - SOPS_AGE_KEY_DIRECTORY = SOPS_AGE_KEY_DIRECTORY; - SOPS_AGE_KEY_FILE = "${SOPS_AGE_KEY_DIRECTORY}/key.txt"; - }; - }; - - # set up user passwords - sops = { - defaultSopsFormat = "yaml"; - gnupg.sshKeyPaths = []; - - age = { - keyFile = "/var/lib/sops-nix/key.txt"; - sshKeyPaths = []; - # generateKey = true; - }; - - secrets = { - "passwords/leyla" = { - neededForUsers = true; - sopsFile = "${inputs.secrets}/user-passwords.yaml"; - }; - "passwords/eve" = { - neededForUsers = true; - sopsFile = "${inputs.secrets}/user-passwords.yaml"; - }; - }; - }; - - users = { - mutableUsers = false; - users = { - leyla = { - uid = lib.mkForce uids.leyla; - name = lib.mkForce host.users.leyla.name; - description = "Leyla"; - extraGroups = - (lib.lists.optionals host.users.leyla.isNormalUser ["networkmanager"]) - ++ (lib.lists.optionals host.users.leyla.isPrincipleUser ["wheel" "dialout" "docker"]) - ++ (lib.lists.optionals host.users.leyla.isDesktopUser ["adbusers"]); - hashedPasswordFile = config.sops.secrets."passwords/leyla".path; - isNormalUser = host.users.leyla.isNormalUser; - isSystemUser = !host.users.leyla.isNormalUser; - group = config.users.users.leyla.name; - }; - - eve = { - uid = lib.mkForce uids.eve; - name = lib.mkForce host.users.eve.name; - description = "Eve"; - extraGroups = - lib.optionals host.users.eve.isNormalUser ["networkmanager"] - ++ (lib.lists.optionals host.users.eve.isPrincipleUser ["wheel"]); - hashedPasswordFile = config.sops.secrets."passwords/eve".path; - isNormalUser = host.users.eve.isNormalUser; - isSystemUser = !host.users.eve.isNormalUser; - group = config.users.users.eve.name; - }; - - jellyfin = { - uid = lib.mkForce uids.jellyfin; - isSystemUser = true; - group = config.users.users.jellyfin.name; - }; - - forgejo = { - uid = lib.mkForce uids.forgejo; - isSystemUser = true; - group = config.users.users.forgejo.name; - }; - - hass = { - uid = lib.mkForce uids.hass; - isSystemUser = true; - group = config.users.users.hass.name; - }; - - syncthing = { - uid = lib.mkForce uids.syncthing; - isSystemUser = true; - group = config.users.users.syncthing.name; - }; - - ollama = { - uid = lib.mkForce uids.ollama; - isSystemUser = true; - group = config.users.users.ollama.name; - }; - - git = { - uid = lib.mkForce uids.git; - isSystemUser = !config.services.forgejo.enable; - isNormalUser = config.services.forgejo.enable; - group = config.users.users.git.name; - }; - - immich = { - uid = lib.mkForce uids.immich; - isSystemUser = true; - group = config.users.users.immich.name; - }; - - qbittorrent = { - uid = lib.mkForce uids.qbittorrent; - isSystemUser = true; - group = config.users.users.qbittorrent.name; - }; - - paperless = { - uid = lib.mkForce uids.paperless; - isSystemUser = true; - group = config.users.users.paperless.name; - }; - - actual = { - uid = lib.mkForce uids.actual; - isSystemUser = true; - group = config.users.users.actual.name; - }; - - radarr = { - uid = lib.mkForce uids.radarr; - isSystemUser = true; - group = config.users.users.radarr.name; - }; - - sonarr = { - uid = lib.mkForce uids.sonarr; - isSystemUser = true; - group = config.users.users.sonarr.name; - }; - - bazarr = { - uid = lib.mkForce uids.bazarr; - isSystemUser = true; - group = config.users.users.bazarr.name; - }; - - lidarr = { - uid = lib.mkForce uids.lidarr; - isSystemUser = true; - group = config.users.users.lidarr.name; - }; - - crab-hole = { - uid = lib.mkForce uids.crab-hole; - isSystemUser = true; - group = config.users.users.crab-hole.name; - }; - }; - - groups = { - leyla = { - gid = lib.mkForce gids.leyla; - members = [ - leyla - ]; - }; - - eve = { - gid = lib.mkForce gids.eve; - members = [ - eve - ]; - }; - - users = { - gid = lib.mkForce gids.users; - members = [ - leyla - eve - ]; - }; - - jellyfin_media = { - gid = lib.mkForce gids.jellyfin_media; - members = [ - users.jellyfin.name - users.radarr.name - users.sonarr.name - users.bazarr.name - users.lidarr.name - leyla - eve - ]; - }; - - jellyfin = { - gid = lib.mkForce gids.jellyfin; - members = [ - users.jellyfin.name - # leyla - ]; - }; - - forgejo = { - gid = lib.mkForce gids.forgejo; - members = [ - users.forgejo.name - # leyla - ]; - }; - - hass = { - gid = lib.mkForce gids.hass; - members = [ - users.hass.name - # leyla - ]; - }; - - syncthing = { - gid = lib.mkForce gids.syncthing; - members = [ - users.syncthing.name - leyla - eve - ]; - }; - - ollama = { - gid = lib.mkForce gids.ollama; - members = [ - users.ollama.name - ]; - }; - - git = { - gid = lib.mkForce gids.git; - members = [ - users.git.name - ]; - }; - - immich = { - gid = lib.mkForce gids.immich; - members = [ - users.immich.name - # leyla - ]; - }; - - qbittorrent = { - gid = lib.mkForce gids.qbittorrent; - members = [ - users.qbittorrent.name - leyla - ]; - }; - - paperless = { - gid = lib.mkForce gids.paperless; - members = [ - users.paperless.name - ]; - }; - - actual = { - gid = lib.mkForce gids.actual; - members = [ - users.actual.name - ]; - }; - - radarr = { - gid = lib.mkForce gids.radarr; - members = [ - users.radarr.name - ]; - }; - - sonarr = { - gid = lib.mkForce gids.sonarr; - members = [ - users.sonarr.name - ]; - }; - - bazarr = { - gid = lib.mkForce gids.bazarr; - members = [ - users.bazarr.name - ]; - }; - - lidarr = { - gid = lib.mkForce gids.lidarr; - members = [ - users.lidarr.name - ]; - }; - - crab-hole = { - gid = lib.mkForce gids.crab-hole; - members = [ - users.crab-hole.name - ]; - }; - }; - }; - } - (lib.mkIf config.storage.zfs.enable (lib.mkMerge [ - { - # sops age key needs to be available to pre persist for user generation - storage.datasets.local."system/sops" = { - type = "zfs_fs"; - mount = SOPS_AGE_KEY_DIRECTORY; - atime = "off"; - relatime = "off"; - impermanence.enable = false; - }; - } - (lib.mkIf (!config.storage.impermanence.enable) { - storage.datasets.replicate = lib.mkMerge ( - builtins.map (user: { - "home/${user.name}" = { - type = "zfs_fs"; - mount = "/home/${user.name}"; - snapshot.autoSnapshot = true; - }; - }) - normalUsers - ); - }) - (lib.mkIf config.storage.impermanence.enable { - storage.datasets.ephemeral = lib.mkMerge ( - builtins.map (user: { - "home/${user.name}" = { - type = "zfs_fs"; - mount = "/home/${user.name}"; - snapshot.blankSnapshot = true; - }; - }) - normalUsers - ); - - # Post resume commands to rollback user home datasets to blank snapshots - # Only add these when generateBase is true -- when false, the legacy - # storage config is responsible for providing rollback commands with - # the correct (old) dataset paths. - boot.initrd.postResumeCommands = lib.mkIf config.storage.generateBase (lib.mkAfter ( - lib.strings.concatLines (builtins.map (user: "zfs rollback -r rpool/ephemeral/home/${user.name}@blank") - normalUsers) - )); - - # TODO: I don't think we need this anymore but I have not tested it - # Create persist home directories with proper permissions - # systemd = { - # tmpfiles.rules = - # builtins.map ( - # user: "d /persist/replicate/home/${user.name} 700 ${user.name} ${user.name} -" - # ) - # normalUsers; - # }; - }) - ])) - ]; -} diff --git a/modules/hosts/home/eve/packages.nix b/modules/hosts/home/eve/packages.nix index 60ed98f..e7362dd 100644 --- a/modules/hosts/home/eve/packages.nix +++ b/modules/hosts/home/eve/packages.nix @@ -77,7 +77,7 @@ libreoffice.enable = true; noita-entangled-worlds.enable = true; - opencode.enable = osConfig.host.ai.enable; + opencode.enable = true; e621-downloader.enable = true; diff --git a/modules/hosts/home/leyla/packages/default.nix b/modules/hosts/home/leyla/packages/default.nix index b220ab9..06b14bc 100644 --- a/modules/hosts/home/leyla/packages/default.nix +++ b/modules/hosts/home/leyla/packages/default.nix @@ -32,8 +32,8 @@ proxmark3.enable = true; openrgb.enable = hardware.openRGB.enable; via.enable = hardware.viaKeyboard.enable; - claude-code.enable = osConfig.host.ai.enable; - opencode.enable = osConfig.host.ai.enable; + claude-code.enable = true; + opencode.enable = true; davinci-resolve.enable = hardware.graphicsAcceleration.enable; mfoc.enable = true; }) diff --git a/modules/hosts/home/leyla/packages/vscode/default.nix b/modules/hosts/home/leyla/packages/vscode/default.nix index b49316a..785104f 100644 --- a/modules/hosts/home/leyla/packages/vscode/default.nix +++ b/modules/hosts/home/leyla/packages/vscode/default.nix @@ -7,7 +7,7 @@ ... }: let nix-development-enabled = osConfig.host.nix-development.enable; - ai-tooling-enabled = osConfig.host.ai.enable; + ai-tooling-enabled = true; in { config = lib.mkIf config.user.isDesktopUser { programs = { diff --git a/modules/hosts/nixos/defiant/configuration.nix b/modules/hosts/nixos/defiant/configuration.nix index c95280a..16c3a90 100644 --- a/modules/hosts/nixos/defiant/configuration.nix +++ b/modules/hosts/nixos/defiant/configuration.nix @@ -260,40 +260,10 @@ }; }; - ollama = { - enable = true; - exposePort = true; - impermanence.enable = false; - - environmentVariables = { - OLLAMA_KEEP_ALIVE = "24h"; - }; - - loadModels = [ - # conversation models - "llama3.1:8b" - "deepseek-r1:8b" - "deepseek-r1:32b" - "deepseek-r1:70b" - - # auto complete models - "qwen2.5-coder:1.5b-base" - "qwen2.5-coder:7b" - "deepseek-coder:6.7b" - "deepseek-coder:33b" - - # agent models - "qwen3:8b" - "qwen3:32b" - "qwen3:235b-a22b" - - "qwen3-coder:30b" - "qwen3-coder:30b-a3b-fp16" - - # embedding models - "nomic-embed-text:latest" - ]; - }; + # ollama = { + # enable = true; + # exposePort = true; + # }; tailscale = { enable = true; authKeyFile = config.sops.secrets."vpn-keys/tailscale-authkey/defiant".path; diff --git a/modules/hosts/nixos/emergent/configuration.nix b/modules/hosts/nixos/emergent/configuration.nix index 24a5558..3f50599 100644 --- a/modules/hosts/nixos/emergent/configuration.nix +++ b/modules/hosts/nixos/emergent/configuration.nix @@ -45,7 +45,7 @@ services.desktopManager.gnome.enable = true; host = { - ai.enable = true; + # ai.enable = true; users = { eve = { isDesktopUser = true; diff --git a/modules/hosts/nixos/horizon/configuration.nix b/modules/hosts/nixos/horizon/configuration.nix index e5b41bf..17ed5c6 100644 --- a/modules/hosts/nixos/horizon/configuration.nix +++ b/modules/hosts/nixos/horizon/configuration.nix @@ -39,50 +39,9 @@ directAccess.enable = true; }; - ai = { - enable = true; - models = { - "Llama 3.1 8B" = { - model = "llama3.1:8b"; - roles = ["chat" "edit" "apply"]; - apiBase = "http://defiant:11434"; - }; - "Deepseek Coder:6.7B" = { - model = "deepseek-coder:6.7b"; - roles = ["chat" "edit" "apply"]; - apiBase = "http://defiant:11434"; - }; - "Deepseek Coder:33B" = { - model = "deepseek-coder:33b"; - roles = ["chat" "edit" "apply"]; - apiBase = "http://defiant:11434"; - }; - - "Deepseek r1:8B" = { - model = "deepseek-r1:8b"; - roles = ["chat"]; - apiBase = "http://defiant:11434"; - }; - - "Deepseek r1:32B" = { - model = "deepseek-r1:32b"; - roles = ["chat"]; - apiBase = "http://defiant:11434"; - }; - - "qwen2.5-coder:1.5b-base" = { - model = "qwen2.5-coder:1.5b-base"; - roles = ["autocomplete"]; - apiBase = "http://defiant:11434"; - }; - - "nomic-embed-text:latest" = { - model = "nomic-embed-text:latest"; - roles = ["embed"]; - apiBase = "http://defiant:11434"; - }; - }; - }; + # ai = { + # enable = true; + # }; }; virtualisation.docker.enable = true; @@ -127,12 +86,12 @@ syncthing.enable = true; - ollama = { - enable = true; - loadModels = [ - "llama3.1:8b" - ]; - }; + # ollama = { + # enable = true; + # loadModels = [ + # "llama3.1:8b" + # ]; + # }; }; # Enable network-online.target for better network dependency handling diff --git a/modules/hosts/nixos/twilight/configuration.nix b/modules/hosts/nixos/twilight/configuration.nix index 7493e41..2a74abf 100644 --- a/modules/hosts/nixos/twilight/configuration.nix +++ b/modules/hosts/nixos/twilight/configuration.nix @@ -36,80 +36,15 @@ graphicsAcceleration.enable = true; directAccess.enable = true; }; - ai = { - enable = true; - # TODO: benchmark twilight against defiant and prune this list of models that are faster on defiant - models = { - # conversation models - "Llama 3.1 8B" = { - model = "lamma3.1:8b"; - roles = ["chat" "edit" "apply"]; - }; - "deepseek-r1:8b" = { - model = "deepseek-r1:8b"; - roles = ["chat" "edit" "apply"]; - }; - "deepseek-r1:32b" = { - model = "deepseek-r1:32b"; - roles = ["chat" "edit" "apply"]; - }; - - # auto complete models - "qwen2.5-coder:1.5b-base" = { - model = "qwen2.5-coder:1.5b-base"; - roles = ["autocomplete"]; - }; - "qwen2.5-coder:7b" = { - model = "qwen2.5-coder:7b"; - roles = ["autocomplete"]; - }; - "deepseek-coder:6.7b" = { - model = "deepseek-coder:6.7b"; - roles = ["autocomplete"]; - }; - "deepseek-coder:33b" = { - model = "deepseek-coder:33b"; - roles = ["autocomplete"]; - }; - - # agent models - "qwen3:32b" = { - model = "qwen3:32b"; - roles = ["chat" "edit" "apply"]; - }; - - # embedding models - "nomic-embed-text:latest" = { - model = "nomic-embed-text:latest"; - roles = ["embed"]; - }; - }; - }; + # ai = { + # enable = true; + # }; }; services = { - ollama = { - enable = true; - exposePort = true; - - loadModels = [ - # conversation models - "llama3.1:8b" - "deepseek-r1:8b" - "deepseek-r1:32b" - - # auto complete models - "qwen2.5-coder:1.5b-base" - "qwen2.5-coder:7b" - "deepseek-coder:6.7b" - "deepseek-coder:33b" - - # agent models - "qwen3:32b" - - # embedding models - "nomic-embed-text:latest" - ]; - }; + # ollama = { + # enable = true; + # exposePort = true; + # }; tailscale = { enable = true; diff --git a/modules/nixos/desktop.nix b/modules/nixos/desktop.nix new file mode 100644 index 0000000..91b8ddc --- /dev/null +++ b/modules/nixos/desktop.nix @@ -0,0 +1,86 @@ +{...}: { + flake.nixosModules.nixos-desktop = { + lib, + pkgs, + config, + ... + }: { + options.host.desktop.enable = lib.mkEnableOption "should desktop configuration be enabled"; + + config = lib.mkMerge [ + { + host.desktop.enable = lib.mkDefault true; + } + (lib.mkIf config.host.desktop.enable { + environment.gnome.excludePackages = with pkgs; [ + xterm # default terminal + atomix # puzzle game + cheese # webcam tool + epiphany # web browser + geary # email reader + gedit # text editor + decibels # audio player + gnome-characters # character set viewer + gnome-music # music player + gnome-photos # photo viewer + gnome-logs # log viewer + gnome-maps # map viewer + gnome-tour # welcome tour + hitori # sudoku game + iagno # go game + tali # poker game + yelp # help viewer + ]; + services = { + # Enable CUPS to print documents. + printing = { + enable = true; + drivers = [ + pkgs.hplip + pkgs.gutenprint + pkgs.gutenprintBin + ]; + }; + + xserver = { + # Enable the X11 windowing system. + enable = true; + + # Get rid of xTerm + desktopManager.xterm.enable = false; + excludePackages = with pkgs; [ + xterm + ]; + }; + + # Enable the GNOME Desktop Environment. + displayManager.gdm.enable = true; + desktopManager.gnome.enable = true; + + pipewire = { + enable = true; + alsa.enable = true; + alsa.support32Bit = true; + pulse.enable = true; + + # If you want to use JACK applications, uncomment this + #jack.enable = true; + + # use the example session manager (no others are packaged yet so this is enabled by default, + # no need to redefine it in your config for now) + #media-session.enable = true; + }; + automatic-timezoned = { + enable = true; + }; + + # Enable sound with pipewire. + pulseaudio.enable = false; + }; + + # enable RealtimeKit for pulse audio + security.rtkit.enable = true; + }) + ]; + }; +} diff --git a/modules/nixos/hardware.nix b/modules/nixos/hardware.nix new file mode 100644 index 0000000..f336b8c --- /dev/null +++ b/modules/nixos/hardware.nix @@ -0,0 +1,36 @@ +{...}: { + flake.nixosModules.nixos-hardware = { + lib, + config, + pkgs, + ... + }: { + options.host.hardware = { + piperMouse = { + enable = lib.mkEnableOption "host has a piper mouse"; + }; + viaKeyboard = { + enable = lib.mkEnableOption "host has a via keyboard"; + }; + openRGB = { + enable = lib.mkEnableOption "host has open rgb hardware"; + }; + graphicsAcceleration = { + enable = lib.mkEnableOption "host has a gpu for graphical acceleration"; + }; + directAccess = { + enable = lib.mkEnableOption "can a host be used on its own"; + }; + }; + config = lib.mkMerge [ + (lib.mkIf config.host.hardware.piperMouse.enable { + services.ratbagd.enable = true; + }) + (lib.mkIf config.host.hardware.viaKeyboard.enable { + hardware.keyboard.qmk.enable = true; + + services.udev.packages = [pkgs.via]; + }) + ]; + }; +} diff --git a/modules/nixos/home-manager-adaptors/default.nix b/modules/nixos/home-manager-adaptors/default.nix new file mode 100644 index 0000000..1ff34a6 --- /dev/null +++ b/modules/nixos/home-manager-adaptors/default.nix @@ -0,0 +1,13 @@ +# modules in this folder are to adapt home-manager modules configs to nixos-module configs +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.home-manager-adaptors = { + imports = [ + mod.home-manager-flipperzero + mod.home-manager-i18n + mod.home-manager-openssh + mod.home-manager-steam + ]; + }; +} diff --git a/modules/nixos/home-manager-adaptors/flipperzero.nix b/modules/nixos/home-manager-adaptors/flipperzero.nix new file mode 100644 index 0000000..228912b --- /dev/null +++ b/modules/nixos/home-manager-adaptors/flipperzero.nix @@ -0,0 +1,11 @@ +{...}: { + flake.nixosModules.home-manager-flipperzero = { + lib, + config, + ... + }: let + home-users = lib.attrsets.mapAttrsToList (_: user: user) config.home-manager.users; + in { + hardware.flipperzero.enable = lib.lists.any (home-user: home-user.hardware.flipperzero.enable) home-users; + }; +} diff --git a/modules/nixos/home-manager-adaptors/i18n.nix b/modules/nixos/home-manager-adaptors/i18n.nix new file mode 100644 index 0000000..8d3b66a --- /dev/null +++ b/modules/nixos/home-manager-adaptors/i18n.nix @@ -0,0 +1,28 @@ +{...}: { + flake.nixosModules.home-manager-i18n = { + lib, + config, + ... + }: let + home-users = lib.attrsets.mapAttrsToList (_: user: user) config.home-manager.users; + in { + config = { + i18n.supportedLocales = + lib.unique + (builtins.map (l: (lib.replaceStrings ["utf8" "utf-8" "UTF8"] ["UTF-8" "UTF-8" "UTF-8"] l) + "/UTF-8") ( + [ + "C.UTF-8" + "en_US.UTF-8" + config.i18n.defaultLocale + ] + ++ (lib.attrValues (lib.filterAttrs (n: v: n != "LANGUAGE") config.i18n.extraLocaleSettings)) + ++ ( + map (user-config: user-config.i18n.defaultLocale) home-users + ) + ++ (lib.lists.flatten ( + map (user-config: lib.attrValues (lib.filterAttrs (n: v: n != "LANGUAGE") user-config.i18n.extraLocaleSettings)) home-users + )) + )); + }; + }; +} diff --git a/modules/nixos/home-manager-adaptors/openssh.nix b/modules/nixos/home-manager-adaptors/openssh.nix new file mode 100644 index 0000000..ad78906 --- /dev/null +++ b/modules/nixos/home-manager-adaptors/openssh.nix @@ -0,0 +1,13 @@ +{...}: { + flake.nixosModules.home-manager-openssh = { + config, + lib, + ... + }: { + users.users = + lib.attrsets.mapAttrs (name: value: { + openssh.authorizedKeys.keys = value.programs.openssh.authorizedKeys; + }) + config.home-manager.users; + }; +} diff --git a/modules/nixos/home-manager-adaptors/steam.nix b/modules/nixos/home-manager-adaptors/steam.nix new file mode 100644 index 0000000..f387bcf --- /dev/null +++ b/modules/nixos/home-manager-adaptors/steam.nix @@ -0,0 +1,20 @@ +{...}: { + flake.nixosModules.home-manager-steam = { + lib, + config, + ... + }: let + setupSteam = + lib.lists.any + (value: value) + (lib.attrsets.mapAttrsToList (name: value: value.programs.steam.enable) config.home-manager.users); + in { + config = lib.mkIf setupSteam { + programs.steam = { + enable = true; + # TODO: figure out how to not install steam here + # package = lib.mkDefault pkgs.emptyFile; + }; + }; + }; +} diff --git a/modules/nixos/i18n.nix b/modules/nixos/i18n.nix new file mode 100644 index 0000000..a8d67d7 --- /dev/null +++ b/modules/nixos/i18n.nix @@ -0,0 +1,5 @@ +{...}: { + flake.nixosModules.nixos-i18n = {...}: { + i18n.defaultLocale = "en_IE.UTF-8"; + }; +} diff --git a/modules/nixos/nixos-modules.nix b/modules/nixos/nixos-modules.nix new file mode 100644 index 0000000..6a5c403 --- /dev/null +++ b/modules/nixos/nixos-modules.nix @@ -0,0 +1,21 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.nixos-modules-all = { + imports = [ + mod.nixos-system + mod.nixos-hardware + mod.nixos-users + mod.nixos-desktop + mod.nixos-ssh + mod.nixos-i18n + mod.storage + mod.home-manager-adaptors + mod.programs + ]; + + nixpkgs.config.permittedInsecurePackages = [ + "dotnet-sdk-6.0.428" + ]; + }; +} diff --git a/modules/nixos/programs/actual/actual.nix b/modules/nixos/programs/actual/actual.nix new file mode 100644 index 0000000..b5e5d2a --- /dev/null +++ b/modules/nixos/programs/actual/actual.nix @@ -0,0 +1,25 @@ +{...}: { + flake.nixosModules.actual-service = { + lib, + config, + ... + }: let + dataDirectory = "/var/lib/private/actual"; + in { + options.services.actual = { + port = lib.mkOption { + type = lib.types.port; + description = "The port to listen on"; + default = 5006; + }; + }; + config = lib.mkIf config.services.actual.enable { + services.actual = { + settings = { + port = config.services.actual.port; + dataDir = dataDirectory; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/actual/default.nix b/modules/nixos/programs/actual/default.nix new file mode 100644 index 0000000..e2895fa --- /dev/null +++ b/modules/nixos/programs/actual/default.nix @@ -0,0 +1,12 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.actual = { + imports = [ + mod.actual-service + mod.actual-proxy + mod.actual-fail2ban + mod.actual-storage + ]; + }; +} diff --git a/modules/nixos/programs/actual/fail2ban.nix b/modules/nixos/programs/actual/fail2ban.nix new file mode 100644 index 0000000..e4fd01e --- /dev/null +++ b/modules/nixos/programs/actual/fail2ban.nix @@ -0,0 +1,11 @@ +{...}: { + flake.nixosModules.actual-fail2ban = { + lib, + config, + ... + }: { + config = lib.mkIf (config.services.actual.enable && config.services.fail2ban.enable) { + # TODO: configuration for fail2ban for actual + }; + }; +} diff --git a/modules/nixos/programs/actual/proxy.nix b/modules/nixos/programs/actual/proxy.nix new file mode 100644 index 0000000..7b385b0 --- /dev/null +++ b/modules/nixos/programs/actual/proxy.nix @@ -0,0 +1,36 @@ +{...}: { + flake.nixosModules.actual-proxy = { + lib, + config, + ... + }: { + options.services.actual = { + domain = lib.mkOption { + type = lib.types.str; + description = "domain that actual will be hosted at"; + default = "actual.arpa"; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for actual"; + default = []; + }; + reverseProxy.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.actual.enable && config.services.reverseProxy.enable; + }; + }; + + config = lib.mkIf config.services.actual.reverseProxy.enable { + services.reverseProxy.services.actual = { + target = "http://localhost:${toString config.services.actual.settings.port}"; + domain = config.services.actual.domain; + extraDomains = config.services.actual.extraDomains; + + settings = { + forwardHeaders.enable = true; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/actual/storage.nix b/modules/nixos/programs/actual/storage.nix new file mode 100644 index 0000000..272647d --- /dev/null +++ b/modules/nixos/programs/actual/storage.nix @@ -0,0 +1,23 @@ +{...}: { + flake.nixosModules.actual-storage = { + lib, + config, + ... + }: let + dataDirectory = "/var/lib/private/actual"; + in { + options.services.actual.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.actual.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.actual.enable { + storage.datasets.replicate."system/root" = { + directories."${dataDirectory}" = lib.mkIf config.services.actual.impermanence.enable { + owner.name = "actual"; + group.name = "actual"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/bazarr/default.nix b/modules/nixos/programs/bazarr/default.nix new file mode 100644 index 0000000..9e83706 --- /dev/null +++ b/modules/nixos/programs/bazarr/default.nix @@ -0,0 +1,9 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.bazarr = { + imports = [ + mod.bazarr-storage + ]; + }; +} diff --git a/modules/nixos/programs/bazarr/storage.nix b/modules/nixos/programs/bazarr/storage.nix new file mode 100644 index 0000000..8431cd4 --- /dev/null +++ b/modules/nixos/programs/bazarr/storage.nix @@ -0,0 +1,23 @@ +{...}: { + flake.nixosModules.bazarr-storage = { + lib, + config, + ... + }: let + bazarr_data_directory = "/var/lib/bazarr"; + in { + options.services.bazarr.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.bazarr.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.bazarr.enable { + storage.datasets.replicate."system/root" = { + directories."${bazarr_data_directory}" = lib.mkIf config.services.bazarr.impermanence.enable { + owner.name = "bazarr"; + group.name = "bazarr"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/crab-hole/crab-hole.nix b/modules/nixos/programs/crab-hole/crab-hole.nix new file mode 100644 index 0000000..0c25e97 --- /dev/null +++ b/modules/nixos/programs/crab-hole/crab-hole.nix @@ -0,0 +1,195 @@ +{...}: { + flake.nixosModules.crab-hole-service = { + config, + lib, + ... + }: let + cfg = config.services.crab-hole; + in { + options.services.crab-hole = { + port = lib.mkOption { + type = lib.types.port; + default = 8080; + description = "Port for the crab-hole API to listen on."; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to open the firewall for the crab-hole API port."; + }; + + listen = lib.mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = "Address for the crab-hole API to listen on."; + }; + + show_doc = lib.mkEnableOption "OpenAPI documentation (loads content from third party websites)"; + + downstreams = { + host = { + enable = lib.mkEnableOption "host downstream DNS server accessible from network on all interfaces"; + port = lib.mkOption { + type = lib.types.port; + default = 53; + description = "Port for the host downstream DNS server to listen on."; + }; + openFirewall = lib.mkEnableOption "automatic port forwarding for the host downstream"; + disableSystemdResolved = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to automatically disable systemd-resolved when using port 53. Set to false if you want to handle the conflict manually."; + }; + }; + }; + + extraDownstreams = lib.mkOption { + type = lib.types.listOf (lib.types.submodule { + options = { + protocol = lib.mkOption { + type = lib.types.enum ["udp" "tcp" "tls" "https" "quic"]; + description = "Protocol for the downstream server."; + }; + + listen = lib.mkOption { + type = lib.types.str; + description = "Address to listen on for downstream connections."; + }; + + port = lib.mkOption { + type = lib.types.port; + description = "Port to listen on for downstream connections."; + }; + }; + }); + default = []; + description = "List of additional downstream DNS server configurations."; + }; + + upstreams = { + cloudFlare = { + enable = lib.mkEnableOption "Cloudflare DNS over TLS upstream servers (1.1.1.1 and 1.0.0.1)"; + }; + }; + + extraUpstreams = lib.mkOption { + type = lib.types.listOf (lib.types.submodule { + options = { + socket_addr = lib.mkOption { + type = lib.types.str; + description = "Socket address of the upstream DNS server (e.g., \"1.1.1.1:853\" or \"[2606:4700:4700::1111]:853\")."; + }; + + protocol = lib.mkOption { + type = lib.types.enum ["udp" "tcp" "tls" "https" "quic"]; + description = "Protocol to use for upstream DNS queries."; + }; + }; + }); + default = []; + description = "List of additional upstream DNS server configurations."; + }; + + blocklists = { + ad_malware = { + enable = lib.mkEnableOption "Host file for blocking ads and malware"; + url = lib.mkOption { + type = lib.types.str; + default = "http://sbc.io/hosts/hosts"; + description = "URL of the ad and malware blocklist host file"; + }; + }; + }; + + extraBlocklists = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = "Additional blocklist URLs to be added to the configuration"; + }; + }; + + config = lib.mkIf cfg.enable { + # Assertions for proper configuration + assertions = [ + { + assertion = !(cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && config.services.resolved.enable && cfg.downstreams.host.disableSystemdResolved); + message = "crab-hole host downstream cannot use port 53 while systemd-resolved is enabled. Either disable systemd-resolved or use a different port."; + } + { + assertion = !(cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && !cfg.downstreams.host.disableSystemdResolved && config.services.resolved.enable); + message = "crab-hole host downstream is configured to use port 53 but systemd-resolved is still enabled and disableSystemdResolved is false. Set disableSystemdResolved = true or manually disable systemd-resolved."; + } + ]; + + # Automatically disable systemd-resolved if using port 53 + services.resolved.enable = lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && cfg.downstreams.host.disableSystemdResolved) (lib.mkForce false); + + # Configure DNS nameservers when disabling systemd-resolved + networking.nameservers = lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && cfg.downstreams.host.disableSystemdResolved) (lib.mkDefault ["127.0.0.1" "1.1.1.1" "8.8.8.8"]); + + services.crab-hole.settings = lib.mkMerge [ + { + api = { + port = cfg.port; + listen = cfg.listen; + show_doc = cfg.show_doc; + }; + downstream = cfg.extraDownstreams; + upstream.name_servers = cfg.extraUpstreams; + blocklist.lists = cfg.extraBlocklists; + } + (lib.mkIf cfg.blocklists.ad_malware.enable { + blocklist.lists = [cfg.blocklists.ad_malware.url]; + }) + (lib.mkIf cfg.downstreams.host.enable { + downstream = [ + { + protocol = "udp"; + listen = "0.0.0.0"; + port = cfg.downstreams.host.port; + } + ]; + }) + (lib.mkIf cfg.upstreams.cloudFlare.enable { + upstream.name_servers = [ + { + socket_addr = "1.1.1.1:853"; + protocol = "tls"; + tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; + trust_nx_responses = false; + } + { + socket_addr = "1.0.0.1:853"; + protocol = "tls"; + tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; + trust_nx_responses = false; + } + { + socket_addr = "[2606:4700:4700::1111]:853"; + protocol = "tls"; + tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; + trust_nx_responses = false; + } + { + socket_addr = "[2606:4700:4700::1001]:853"; + protocol = "tls"; + tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; + trust_nx_responses = false; + } + ]; + }) + ]; + + # Open firewall if requested + networking.firewall = lib.mkMerge [ + (lib.mkIf cfg.openFirewall { + allowedTCPPorts = [cfg.port]; + }) + (lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.openFirewall) { + allowedUDPPorts = [cfg.downstreams.host.port]; + }) + ]; + }; + }; +} diff --git a/modules/nixos/programs/crab-hole/default.nix b/modules/nixos/programs/crab-hole/default.nix new file mode 100644 index 0000000..44c4cb0 --- /dev/null +++ b/modules/nixos/programs/crab-hole/default.nix @@ -0,0 +1,10 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.crab-hole = { + imports = [ + mod.crab-hole-service + mod.crab-hole-storage + ]; + }; +} diff --git a/modules/nixos/programs/crab-hole/storage.nix b/modules/nixos/programs/crab-hole/storage.nix new file mode 100644 index 0000000..33a3c60 --- /dev/null +++ b/modules/nixos/programs/crab-hole/storage.nix @@ -0,0 +1,23 @@ +{...}: { + flake.nixosModules.crab-hole-storage = { + lib, + config, + ... + }: let + workingDirectory = "/var/lib/private/crab-hole"; + in { + options.services.crab-hole.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.crab-hole.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.crab-hole.enable { + storage.datasets.replicate."system/root" = { + directories."${workingDirectory}" = lib.mkIf config.services.crab-hole.impermanence.enable { + owner.name = "crab-hole"; + group.name = "crab-hole"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/default.nix b/modules/nixos/programs/default.nix new file mode 100644 index 0000000..592911d --- /dev/null +++ b/modules/nixos/programs/default.nix @@ -0,0 +1,32 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.programs = { + imports = [ + mod.steam + mod.sync + mod.tailscale + mod.actual + mod.bazarr + mod.crab-hole + mod.fail2ban + mod.flaresolverr + mod.forgejo + mod.home-assistant + mod.immich + mod.jackett + mod.jellyfin + mod.lidarr + mod.network-storage + mod.panoramax + mod.paperless + mod.postgres + mod.qbittorent + mod.radarr + mod.reverse-proxy + mod.searx + mod.sonarr + mod.wyoming + ]; + }; +} diff --git a/modules/nixos/programs/fail2ban/default.nix b/modules/nixos/programs/fail2ban/default.nix new file mode 100644 index 0000000..1c81d85 --- /dev/null +++ b/modules/nixos/programs/fail2ban/default.nix @@ -0,0 +1,10 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.fail2ban = { + imports = [ + mod.fail2ban-service + mod.fail2ban-storage + ]; + }; +} diff --git a/modules/nixos/programs/fail2ban/fail2ban.nix b/modules/nixos/programs/fail2ban/fail2ban.nix new file mode 100644 index 0000000..3d5d4a5 --- /dev/null +++ b/modules/nixos/programs/fail2ban/fail2ban.nix @@ -0,0 +1,53 @@ +{...}: { + flake.nixosModules.fail2ban-service = { + lib, + pkgs, + config, + ... + }: { + config = lib.mkIf config.services.fail2ban.enable { + environment.etc = { + "fail2ban/filter.d/nginx.local".text = lib.mkIf config.services.nginx.enable ( + pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [Definition] + failregex = "limiting requests, excess:.* by zone.*client: " + '') + ); + }; + + services.fail2ban = { + maxretry = 5; + ignoreIP = [ + # Whitelist local networks + "10.0.0.0/8" + "172.16.0.0/12" + "192.168.0.0/16" + + # tail scale tailnet + "100.64.0.0/10" + "fd7a:115c:a1e0::/48" + ]; + bantime = "24h"; # Ban IPs for one day on the first ban + bantime-increment = { + enable = true; # Enable increment of bantime after each violation + formula = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)"; + maxtime = "168h"; # Do not ban for more than 1 week + overalljails = true; # Calculate the ban time based on all the violations + }; + jails = { + nginx-iptables.settings = lib.mkIf config.services.nginx.enable { + enabled = true; + filter = "nginx"; + action = ''iptables-multiport[name=HTTP, port="http,https"]''; + backend = "auto"; + findtime = 600; + bantime = 600; + maxretry = 5; + }; + # TODO; figure out if there is any fail2ban things we can do on searx + # searx-iptables.settings = lib.mkIf config.services.searx.enable {}; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/fail2ban/storage.nix b/modules/nixos/programs/fail2ban/storage.nix new file mode 100644 index 0000000..d20fad1 --- /dev/null +++ b/modules/nixos/programs/fail2ban/storage.nix @@ -0,0 +1,23 @@ +{...}: { + flake.nixosModules.fail2ban-storage = { + lib, + config, + ... + }: let + dataFolder = "/var/lib/fail2ban"; + in { + options.services.fail2ban.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.fail2ban.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.fail2ban.enable { + storage.datasets.replicate."system/root" = { + directories."${dataFolder}" = lib.mkIf config.services.fail2ban.impermanence.enable { + owner.name = "fail2ban"; + group.name = "fail2ban"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/flaresolverr/default.nix b/modules/nixos/programs/flaresolverr/default.nix new file mode 100644 index 0000000..916d3e6 --- /dev/null +++ b/modules/nixos/programs/flaresolverr/default.nix @@ -0,0 +1,9 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.flaresolverr = { + imports = [ + mod.flaresolverr-storage + ]; + }; +} diff --git a/modules/nixos/programs/flaresolverr/storage.nix b/modules/nixos/programs/flaresolverr/storage.nix new file mode 100644 index 0000000..caed127 --- /dev/null +++ b/modules/nixos/programs/flaresolverr/storage.nix @@ -0,0 +1,21 @@ +{...}: { + flake.nixosModules.flaresolverr-storage = { + lib, + config, + ... + }: { + options.services.flaresolverr.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.flaresolverr.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.flaresolverr.enable { + storage.datasets.replicate."system/root" = { + directories."/var/lib/flaresolverr" = lib.mkIf config.services.flaresolverr.impermanence.enable { + owner.name = "flaresolverr"; + group.name = "flaresolverr"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/forgejo/database.nix b/modules/nixos/programs/forgejo/database.nix new file mode 100644 index 0000000..e4c6697 --- /dev/null +++ b/modules/nixos/programs/forgejo/database.nix @@ -0,0 +1,34 @@ +{...}: { + flake.nixosModules.forgejo-database = { + lib, + config, + ... + }: let + usingPostgres = config.services.forgejo.database.type == "postgres"; + in { + config = lib.mkIf config.services.forgejo.enable { + assertions = [ + { + assertion = !usingPostgres || config.services.postgresql.enable; + message = "PostgreSQL must be enabled when Forgejo database type is postgres"; + } + { + assertion = !(usingPostgres && config.services.forgejo.database.createDatabase) || (builtins.any (db: db == "forgejo") config.services.postgresql.ensureDatabases); + message = "Forgejo built-in database creation failed - expected 'forgejo' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}"; + } + { + assertion = !(usingPostgres && config.services.forgejo.database.createDatabase) || (builtins.any (user: user.name == "forgejo") config.services.postgresql.ensureUsers); + message = "Forgejo built-in user creation failed - expected user 'forgejo' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}"; + } + ]; + + services.forgejo.database.createDatabase = lib.mkDefault usingPostgres; + + systemd.services.forgejo = lib.mkIf usingPostgres { + requires = [ + config.systemd.services.postgresql.name + ]; + }; + }; + }; +} diff --git a/modules/nixos/programs/forgejo/default.nix b/modules/nixos/programs/forgejo/default.nix new file mode 100644 index 0000000..bbccaae --- /dev/null +++ b/modules/nixos/programs/forgejo/default.nix @@ -0,0 +1,13 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.forgejo = { + imports = [ + mod.forgejo-service + mod.forgejo-database + mod.forgejo-proxy + mod.forgejo-fail2ban + mod.forgejo-storage + ]; + }; +} diff --git a/modules/nixos/programs/forgejo/fail2ban.nix b/modules/nixos/programs/forgejo/fail2ban.nix new file mode 100644 index 0000000..e1ab7b7 --- /dev/null +++ b/modules/nixos/programs/forgejo/fail2ban.nix @@ -0,0 +1,43 @@ +{...}: { + flake.nixosModules.forgejo-fail2ban = { + lib, + config, + pkgs, + ... + }: { + options.services.forgejo = { + fail2ban = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.forgejo.enable && config.services.fail2ban.enable; + }; + }; + }; + + config = lib.mkIf config.services.forgejo.fail2ban.enable { + environment.etc = { + "fail2ban/filter.d/forgejo.local".text = lib.mkIf config.services.forgejo.enable ( + pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [Definition] + failregex = ".*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from " + '') + ); + }; + + services.fail2ban = { + jails = { + forgejo-iptables.settings = lib.mkIf config.services.forgejo.enable { + enabled = true; + filter = "forgejo"; + action = ''iptables-multiport[name=HTTP, port="http,https"]''; + logpath = "${config.services.forgejo.settings.log.ROOT_PATH}/*.log"; + backend = "auto"; + findtime = 600; + bantime = 600; + maxretry = 5; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/forgejo/forgejo.nix b/modules/nixos/programs/forgejo/forgejo.nix new file mode 100644 index 0000000..9d8914a --- /dev/null +++ b/modules/nixos/programs/forgejo/forgejo.nix @@ -0,0 +1,47 @@ +{...}: { + flake.nixosModules.forgejo-service = { + lib, + config, + ... + }: let + httpPort = 8081; + sshPort = 22222; + db_user = "forgejo"; + in { + config = lib.mkIf config.services.forgejo.enable { + assertions = [ + { + assertion = config.services.forgejo.settings.server.BUILTIN_SSH_SERVER_USER == config.users.users.git.name; + message = "Forgejo BUILTIN_SSH_SERVER_USER hardcoded value does not match expected git user name"; + } + ]; + + services.forgejo = { + database = { + type = "postgres"; + socket = "/run/postgresql"; + }; + lfs.enable = true; + settings = { + server = { + DOMAIN = config.services.forgejo.reverseProxy.domain; + HTTP_PORT = httpPort; + START_SSH_SERVER = true; + SSH_LISTEN_PORT = sshPort; + SSH_PORT = 22; + BUILTIN_SSH_SERVER_USER = "git"; + ROOT_URL = "https://git.jan-leila.com"; + }; + service = { + DISABLE_REGISTRATION = true; + }; + database = { + DB_TYPE = "postgres"; + NAME = db_user; + USER = db_user; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/forgejo/proxy.nix b/modules/nixos/programs/forgejo/proxy.nix new file mode 100644 index 0000000..af03409 --- /dev/null +++ b/modules/nixos/programs/forgejo/proxy.nix @@ -0,0 +1,44 @@ +{...}: { + flake.nixosModules.forgejo-proxy = { + lib, + config, + ... + }: let + httpPort = 8081; + in { + options.services.forgejo = { + reverseProxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.forgejo.enable && config.services.reverseProxy.enable; + }; + domain = lib.mkOption { + type = lib.types.str; + description = "domain that forgejo will be hosted at"; + default = "git.jan-leila.com"; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for forgejo"; + default = []; + }; + }; + }; + + config = lib.mkIf config.services.forgejo.reverseProxy.enable { + services.reverseProxy.services.forgejo = { + target = "http://localhost:${toString httpPort}"; + domain = config.services.forgejo.reverseProxy.domain; + extraDomains = config.services.forgejo.reverseProxy.extraDomains; + + settings = { + forwardHeaders.enable = true; + }; + }; + + networking.firewall.allowedTCPPorts = [ + config.services.forgejo.settings.server.SSH_LISTEN_PORT + ]; + }; + }; +} diff --git a/modules/nixos/programs/forgejo/storage.nix b/modules/nixos/programs/forgejo/storage.nix new file mode 100644 index 0000000..cff5500 --- /dev/null +++ b/modules/nixos/programs/forgejo/storage.nix @@ -0,0 +1,23 @@ +{...}: { + flake.nixosModules.forgejo-storage = { + lib, + config, + ... + }: let + stateDir = "/var/lib/forgejo"; + in { + options.services.forgejo.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.forgejo.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.forgejo.enable { + storage.datasets.replicate."system/root" = { + directories."${stateDir}" = lib.mkIf config.services.forgejo.impermanence.enable { + owner.name = "forgejo"; + group.name = "forgejo"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/home-assistant/database.nix b/modules/nixos/programs/home-assistant/database.nix new file mode 100644 index 0000000..648df74 --- /dev/null +++ b/modules/nixos/programs/home-assistant/database.nix @@ -0,0 +1,55 @@ +{...}: { + flake.nixosModules.home-assistant-database = { + lib, + config, + ... + }: { + options.services.home-assistant = { + postgres = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Use PostgreSQL instead of SQLite"; + }; + user = lib.mkOption { + type = lib.types.str; + default = "hass"; + description = "Database user name"; + }; + database = lib.mkOption { + type = lib.types.str; + default = "hass"; + description = "Database name"; + }; + }; + }; + + config = lib.mkIf config.services.home-assistant.enable { + assertions = [ + { + assertion = !config.services.home-assistant.postgres.enable || config.services.postgresql.enable; + message = "PostgreSQL must be enabled when using postgres database for Home Assistant"; + } + ]; + + services.postgresql.databases.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable { + enable = true; + user = config.services.home-assistant.postgres.user; + database = config.services.home-assistant.postgres.database; + }; + + services.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable { + extraPackages = python3Packages: + with python3Packages; [ + psycopg2 + ]; + }; + + systemd.services.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable { + requires = [ + config.systemd.services.postgresql.name + ]; + }; + }; + }; +} diff --git a/modules/nixos/programs/home-assistant/default.nix b/modules/nixos/programs/home-assistant/default.nix new file mode 100644 index 0000000..9aca3f9 --- /dev/null +++ b/modules/nixos/programs/home-assistant/default.nix @@ -0,0 +1,14 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.home-assistant = { + imports = [ + mod.home-assistant-service + mod.home-assistant-proxy + mod.home-assistant-database + mod.home-assistant-fail2ban + mod.home-assistant-storage + mod.home-assistant-extensions + ]; + }; +} diff --git a/modules/nixos/programs/home-assistant/extensions/default.nix b/modules/nixos/programs/home-assistant/extensions/default.nix new file mode 100644 index 0000000..43b62b8 --- /dev/null +++ b/modules/nixos/programs/home-assistant/extensions/default.nix @@ -0,0 +1,11 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.home-assistant-extensions = { + imports = [ + mod.home-assistant-sonos + mod.home-assistant-jellyfin + mod.home-assistant-wyoming + ]; + }; +} diff --git a/modules/nixos/programs/home-assistant/extensions/jellyfin.nix b/modules/nixos/programs/home-assistant/extensions/jellyfin.nix new file mode 100644 index 0000000..4c18c3f --- /dev/null +++ b/modules/nixos/programs/home-assistant/extensions/jellyfin.nix @@ -0,0 +1,11 @@ +{...}: { + flake.nixosModules.home-assistant-jellyfin = { + lib, + config, + ... + }: + lib.mkIf (config.services.home-assistant.extensions.jellyfin.enable) { + services.home-assistant.extraComponents = ["jellyfin"]; + # TODO: configure port, address, and login information here + }; +} diff --git a/modules/nixos/programs/home-assistant/extensions/sonos.nix b/modules/nixos/programs/home-assistant/extensions/sonos.nix new file mode 100644 index 0000000..a265a79 --- /dev/null +++ b/modules/nixos/programs/home-assistant/extensions/sonos.nix @@ -0,0 +1,13 @@ +{...}: { + flake.nixosModules.home-assistant-sonos = { + lib, + config, + ... + }: + lib.mkIf (config.services.home-assistant.extensions.sonos.enable) { + services.home-assistant.extraComponents = ["sonos"]; + networking.firewall.allowedTCPPorts = [ + config.services.home-assistant.extensions.sonos.port + ]; + }; +} diff --git a/modules/nixos/programs/home-assistant/extensions/wyoming.nix b/modules/nixos/programs/home-assistant/extensions/wyoming.nix new file mode 100644 index 0000000..2664f75 --- /dev/null +++ b/modules/nixos/programs/home-assistant/extensions/wyoming.nix @@ -0,0 +1,11 @@ +{...}: { + flake.nixosModules.home-assistant-wyoming = { + lib, + config, + ... + }: + lib.mkIf (config.services.home-assistant.extensions.wyoming.enable) { + services.home-assistant.extraComponents = ["wyoming"]; + services.wyoming.enable = true; + }; +} diff --git a/modules/nixos/programs/home-assistant/fail2ban.nix b/modules/nixos/programs/home-assistant/fail2ban.nix new file mode 100644 index 0000000..f44aefe --- /dev/null +++ b/modules/nixos/programs/home-assistant/fail2ban.nix @@ -0,0 +1,51 @@ +{...}: { + flake.nixosModules.home-assistant-fail2ban = { + lib, + pkgs, + config, + ... + }: { + options.services.home-assistant = { + fail2ban = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.fail2ban.enable && config.services.home-assistant.enable; + }; + }; + }; + + config = lib.mkIf config.services.home-assistant.fail2ban.enable { + environment.etc = { + "fail2ban/filter.d/hass.local".text = ( + pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [INCLUDES] + before = common.conf + + [Definition] + failregex = ^%(__prefix_line)s.*Login attempt or request with invalid authentication from .*$ + + ignoreregex = + + [Init] + datepattern = ^%%Y-%%m-%%d %%H:%%M:%%S + '') + ); + }; + + services.fail2ban = { + jails = { + home-assistant-iptables.settings = { + enabled = true; + filter = "hass"; + action = ''iptables-multiport[name=HTTP, port="http,https"]''; + logpath = "${config.services.home-assistant.configDir}/*.log"; + backend = "auto"; + findtime = 600; + bantime = 600; + maxretry = 5; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/home-assistant/home-assistant.nix b/modules/nixos/programs/home-assistant/home-assistant.nix new file mode 100644 index 0000000..5f1f807 --- /dev/null +++ b/modules/nixos/programs/home-assistant/home-assistant.nix @@ -0,0 +1,106 @@ +{...}: { + flake.nixosModules.home-assistant-service = { + lib, + config, + ... + }: { + options.services.home-assistant = { + database = lib.mkOption { + type = lib.types.enum [ + "builtin" + "postgres" + ]; + description = "what database do we want to use"; + default = "builtin"; + }; + + extensions = { + sonos = { + enable = lib.mkEnableOption "enable the sonos plugin"; + port = lib.mkOption { + type = lib.types.int; + default = 1400; + description = "what port to use for sonos discovery"; + }; + }; + jellyfin = { + enable = lib.mkEnableOption "enable the jellyfin plugin"; + }; + wyoming = { + enable = lib.mkEnableOption "enable wyoming"; + }; + }; + }; + + config = lib.mkIf config.services.home-assistant.enable (lib.mkMerge [ + { + services.home-assistant = { + configDir = "/var/lib/hass"; + extraComponents = [ + "default_config" + "esphome" + "met" + "radio_browser" + "isal" + "zha" + "webostv" + "tailscale" + "syncthing" + "analytics_insights" + "unifi" + "openweathermap" + "ollama" + "mobile_app" + "logbook" + "ssdp" + "usb" + "webhook" + "bluetooth" + "dhcp" + "energy" + "history" + "backup" + "assist_pipeline" + "conversation" + "sun" + "zeroconf" + "cpuspeed" + ]; + config = { + http = { + server_port = 8123; + use_x_forwarded_for = true; + trusted_proxies = ["127.0.0.1" "::1"]; + ip_ban_enabled = true; + login_attempts_threshold = 10; + }; + homeassistant = { + external_url = "https://${config.services.home-assistant.domain}"; + # internal_url = "http://192.168.1.2:8123"; + }; + recorder.db_url = "postgresql://@/${config.services.home-assistant.configDir}"; + "automation manual" = []; + "automation ui" = "!include automations.yaml"; + mobile_app = {}; + }; + extraPackages = python3Packages: + with python3Packages; [ + hassil + numpy + gtts + ]; + }; + + # TODO: configure /var/lib/hass/secrets.yaml via sops + + networking.firewall.allowedUDPPorts = [ + 1900 + ]; + + systemd.tmpfiles.rules = [ + "f ${config.services.home-assistant.configDir}/automations.yaml 0755 hass hass" + ]; + } + ]); + }; +} diff --git a/modules/nixos/programs/home-assistant/proxy.nix b/modules/nixos/programs/home-assistant/proxy.nix new file mode 100644 index 0000000..358288f --- /dev/null +++ b/modules/nixos/programs/home-assistant/proxy.nix @@ -0,0 +1,45 @@ +{...}: { + flake.nixosModules.home-assistant-proxy = { + lib, + config, + ... + }: { + options.services.home-assistant = { + domain = lib.mkOption { + type = lib.types.str; + description = "domain that home-assistant will be hosted at"; + default = "home-assistant.arpa"; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for home-assistant"; + default = []; + }; + reverseProxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.reverseProxy.enable && config.services.home-assistant.enable; + }; + }; + }; + + config = lib.mkIf config.services.home-assistant.reverseProxy.enable { + services.reverseProxy.services.home-assistant = { + target = "http://localhost:${toString config.services.home-assistant.config.http.server_port}"; + domain = config.services.home-assistant.domain; + extraDomains = config.services.home-assistant.extraDomains; + + settings = { + proxyWebsockets.enable = true; + forwardHeaders.enable = true; + + # Custom timeout settings + proxyHeaders = { + enable = true; + timeout = 90; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/home-assistant/storage.nix b/modules/nixos/programs/home-assistant/storage.nix new file mode 100644 index 0000000..2cd779f --- /dev/null +++ b/modules/nixos/programs/home-assistant/storage.nix @@ -0,0 +1,23 @@ +{...}: { + flake.nixosModules.home-assistant-storage = { + lib, + config, + ... + }: let + configDir = "/var/lib/hass"; + in { + options.services.home-assistant.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.home-assistant.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.home-assistant.enable { + storage.datasets.replicate."system/root" = { + directories."${configDir}" = lib.mkIf config.services.home-assistant.impermanence.enable { + owner.name = "hass"; + group.name = "hass"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/immich/database.nix b/modules/nixos/programs/immich/database.nix new file mode 100644 index 0000000..60fe07e --- /dev/null +++ b/modules/nixos/programs/immich/database.nix @@ -0,0 +1,32 @@ +{...}: { + flake.nixosModules.immich-database = { + lib, + config, + ... + }: { + config = lib.mkIf config.services.immich.enable { + assertions = [ + { + assertion = !config.services.immich.database.enable || config.services.postgresql.enable; + message = "PostgreSQL must be enabled when using postgres database for Immich"; + } + { + assertion = !(config.services.immich.database.enable && config.services.immich.database.createDB) || (builtins.any (db: db == "immich") config.services.postgresql.ensureDatabases); + message = "Immich built-in database creation failed - expected 'immich' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}"; + } + { + assertion = !(config.services.immich.database.enable && config.services.immich.database.createDB) || (builtins.any (user: user.name == "immich") config.services.postgresql.ensureUsers); + message = "Immich built-in user creation failed - expected user 'immich' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}"; + } + ]; + + # Note: Immich has built-in database creation via services.immich.database.createDB we only add the systemd dependency + + systemd.services.immich-server = lib.mkIf config.services.immich.database.enable { + requires = [ + config.systemd.services.postgresql.name + ]; + }; + }; + }; +} diff --git a/modules/nixos/programs/immich/default.nix b/modules/nixos/programs/immich/default.nix new file mode 100644 index 0000000..d79af33 --- /dev/null +++ b/modules/nixos/programs/immich/default.nix @@ -0,0 +1,12 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.immich = { + imports = [ + mod.immich-proxy + mod.immich-database + mod.immich-fail2ban + mod.immich-storage + ]; + }; +} diff --git a/modules/nixos/programs/immich/fail2ban.nix b/modules/nixos/programs/immich/fail2ban.nix new file mode 100644 index 0000000..c5e6332 --- /dev/null +++ b/modules/nixos/programs/immich/fail2ban.nix @@ -0,0 +1,37 @@ +{...}: { + flake.nixosModules.immich-fail2ban = { + lib, + config, + pkgs, + ... + }: { + options.services.immich = { + fail2ban = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.fail2ban.enable && config.services.immich.enable; + }; + }; + }; + + config = lib.mkIf config.services.immich.fail2ban.enable { + environment.etc = { + "fail2ban/filter.d/immich.local".text = pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [Definition] + failregex = immich-server.*Failed login attempt for user.+from ip address\s? + journalmatch = CONTAINER_TAG=immich-server + ''); + }; + + services.fail2ban = { + jails = { + immich-iptables.settings = { + enabled = true; + filter = "immich"; + backend = "systemd"; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/immich/proxy.nix b/modules/nixos/programs/immich/proxy.nix new file mode 100644 index 0000000..47b908c --- /dev/null +++ b/modules/nixos/programs/immich/proxy.nix @@ -0,0 +1,46 @@ +{...}: { + flake.nixosModules.immich-proxy = { + lib, + config, + ... + }: { + options.services.immich = { + domain = lib.mkOption { + type = lib.types.str; + description = "domain that immich will be hosted at"; + default = "immich.arpa"; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for immich"; + default = []; + }; + reverseProxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.immich.enable && config.services.reverseProxy.enable; + }; + }; + }; + + config = lib.mkIf config.services.immich.reverseProxy.enable { + services.reverseProxy.services.immich = { + target = "http://localhost:${toString config.services.immich.port}"; + domain = config.services.immich.domain; + extraDomains = config.services.immich.extraDomains; + + settings = { + proxyWebsockets.enable = true; + forwardHeaders.enable = true; + maxBodySize = 50000; + + # Custom timeout settings + proxyHeaders = { + enable = true; + timeout = 600; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/immich/storage.nix b/modules/nixos/programs/immich/storage.nix new file mode 100644 index 0000000..93cc2c2 --- /dev/null +++ b/modules/nixos/programs/immich/storage.nix @@ -0,0 +1,23 @@ +{...}: { + flake.nixosModules.immich-storage = { + lib, + config, + ... + }: let + mediaLocation = "/var/lib/immich"; + in { + options.services.immich.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.immich.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.immich.enable { + storage.datasets.replicate."system/root" = { + directories."${mediaLocation}" = lib.mkIf config.services.immich.impermanence.enable { + owner.name = "immich"; + group.name = "immich"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/jackett/default.nix b/modules/nixos/programs/jackett/default.nix new file mode 100644 index 0000000..6e371eb --- /dev/null +++ b/modules/nixos/programs/jackett/default.nix @@ -0,0 +1,21 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.jackett = { + imports = [ + mod.jackett-storage + ]; + + config = { + nixpkgs.overlays = [ + # Disable jackett tests due to date-related test failures + # (ParseDateTimeGoLangTest expects 2024-09-14 but gets 2025-09-14 due to year rollover logic) + (final: prev: { + jackett = prev.jackett.overrideAttrs (oldAttrs: { + doCheck = false; + }); + }) + ]; + }; + }; +} diff --git a/modules/nixos/programs/jackett/storage.nix b/modules/nixos/programs/jackett/storage.nix new file mode 100644 index 0000000..a8977ca --- /dev/null +++ b/modules/nixos/programs/jackett/storage.nix @@ -0,0 +1,23 @@ +{...}: { + flake.nixosModules.jackett-storage = { + lib, + config, + ... + }: let + jackett_data_directory = "/var/lib/jackett/.config/Jackett"; + in { + options.services.jackett.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.jackett.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.jackett.enable { + storage.datasets.replicate."system/root" = { + directories."${jackett_data_directory}" = lib.mkIf config.services.jackett.impermanence.enable { + owner.name = "jackett"; + group.name = "jackett"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/jellyfin/default.nix b/modules/nixos/programs/jellyfin/default.nix new file mode 100644 index 0000000..e016ab6 --- /dev/null +++ b/modules/nixos/programs/jellyfin/default.nix @@ -0,0 +1,12 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.jellyfin = { + imports = [ + mod.jellyfin-service + mod.jellyfin-proxy + mod.jellyfin-fail2ban + mod.jellyfin-storage + ]; + }; +} diff --git a/modules/nixos/programs/jellyfin/fail2ban.nix b/modules/nixos/programs/jellyfin/fail2ban.nix new file mode 100644 index 0000000..7461ba7 --- /dev/null +++ b/modules/nixos/programs/jellyfin/fail2ban.nix @@ -0,0 +1,34 @@ +{...}: { + flake.nixosModules.jellyfin-fail2ban = { + lib, + pkgs, + config, + ... + }: { + config = lib.mkIf (config.services.jellyfin.enable && config.services.fail2ban.enable) { + environment.etc = { + "fail2ban/filter.d/jellyfin.local".text = ( + pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [Definition] + failregex = "^.*Authentication request for .* has been denied \\\\\\(IP: \\\"\\\"\\\\\\)\\\\\\." + '') + ); + }; + + services.fail2ban = { + jails = { + jellyfin-iptables.settings = { + enabled = true; + filter = "jellyfin"; + action = ''iptables-multiport[name=HTTP, port="http,https"]''; + logpath = "${config.services.jellyfin.dataDir}/log/*.log"; + backend = "auto"; + findtime = 600; + bantime = 600; + maxretry = 5; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/jellyfin/jellyfin.nix b/modules/nixos/programs/jellyfin/jellyfin.nix new file mode 100644 index 0000000..28d2f46 --- /dev/null +++ b/modules/nixos/programs/jellyfin/jellyfin.nix @@ -0,0 +1,34 @@ +{...}: { + flake.nixosModules.jellyfin-service = { + lib, + pkgs, + config, + ... + }: let + jellyfinPort = 8096; + dlanPort = 1900; + in { + options.services.jellyfin = { + media_directory = lib.mkOption { + type = lib.types.str; + description = "directory jellyfin media will be hosted at"; + default = "/srv/jellyfin/media"; + }; + }; + + config = lib.mkIf config.services.jellyfin.enable { + environment.systemPackages = [ + pkgs.jellyfin + pkgs.jellyfin-web + pkgs.jellyfin-ffmpeg + ]; + + networking.firewall.allowedTCPPorts = [jellyfinPort dlanPort]; + + systemd.tmpfiles.rules = [ + "d ${config.services.jellyfin.media_directory} 2770 jellyfin jellyfin_media" + "A ${config.services.jellyfin.media_directory} - - - - u:jellyfin:rwX,g:jellyfin_media:rwX,o::-" + ]; + }; + }; +} diff --git a/modules/nixos/programs/jellyfin/proxy.nix b/modules/nixos/programs/jellyfin/proxy.nix new file mode 100644 index 0000000..56a12d1 --- /dev/null +++ b/modules/nixos/programs/jellyfin/proxy.nix @@ -0,0 +1,43 @@ +{...}: { + flake.nixosModules.jellyfin-proxy = { + lib, + config, + ... + }: let + jellyfinPort = 8096; + in { + options.services.jellyfin = { + domain = lib.mkOption { + type = lib.types.str; + description = "domain that jellyfin will be hosted at"; + default = "jellyfin.arpa"; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for jellyfin"; + default = []; + }; + reverseProxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.jellyfin.enable && config.services.reverseProxy.enable; + }; + }; + }; + + config = lib.mkIf config.services.jellyfin.reverseProxy.enable { + services.reverseProxy.services.jellyfin = { + target = "http://localhost:${toString jellyfinPort}"; + domain = config.services.jellyfin.domain; + extraDomains = config.services.jellyfin.extraDomains; + + settings = { + forwardHeaders.enable = true; + maxBodySize = 20; + noSniff.enable = true; + proxyBuffering.enable = false; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/jellyfin/storage.nix b/modules/nixos/programs/jellyfin/storage.nix new file mode 100644 index 0000000..0802530 --- /dev/null +++ b/modules/nixos/programs/jellyfin/storage.nix @@ -0,0 +1,58 @@ +{...}: { + flake.nixosModules.jellyfin-storage = { + lib, + config, + ... + }: let + jellyfin_data_directory = "/var/lib/jellyfin"; + jellyfin_cache_directory = "/var/cache/jellyfin"; + in { + options.services.jellyfin.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.jellyfin.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.jellyfin.enable { + storage.datasets.replicate = { + "system/root" = { + directories = { + "${jellyfin_data_directory}" = lib.mkIf config.services.jellyfin.impermanence.enable { + enable = true; + owner.name = "jellyfin"; + group.name = "jellyfin"; + }; + "${jellyfin_cache_directory}" = lib.mkIf config.services.jellyfin.impermanence.enable { + enable = true; + owner.name = "jellyfin"; + group.name = "jellyfin"; + }; + }; + }; + "system/media" = { + mount = "/persist/replicate/system/media"; + + directories."${config.services.jellyfin.media_directory}" = lib.mkIf config.services.jellyfin.impermanence.enable { + enable = true; + owner.name = "jellyfin"; + group.name = "jellyfin_media"; + owner.permissions = { + read = true; + write = true; + execute = true; + }; + group.permissions = { + read = true; + write = true; + execute = true; + }; + other.permissions = { + read = false; + write = false; + execute = false; + }; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/lidarr/default.nix b/modules/nixos/programs/lidarr/default.nix new file mode 100644 index 0000000..81a9929 --- /dev/null +++ b/modules/nixos/programs/lidarr/default.nix @@ -0,0 +1,9 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.lidarr = { + imports = [ + mod.lidarr-storage + ]; + }; +} diff --git a/modules/nixos/programs/lidarr/storage.nix b/modules/nixos/programs/lidarr/storage.nix new file mode 100644 index 0000000..72104c1 --- /dev/null +++ b/modules/nixos/programs/lidarr/storage.nix @@ -0,0 +1,23 @@ +{...}: { + flake.nixosModules.lidarr-storage = { + lib, + config, + ... + }: let + lidarr_data_directory = "/var/lib/lidarr/.config/Lidarr"; + in { + options.services.lidarr.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.lidarr.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.lidarr.enable { + storage.datasets.replicate."system/root" = { + directories."${lidarr_data_directory}" = lib.mkIf config.services.lidarr.impermanence.enable { + owner.name = "lidarr"; + group.name = "lidarr"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/network_storage/default.nix b/modules/nixos/programs/network_storage/default.nix new file mode 100644 index 0000000..eeaa705 --- /dev/null +++ b/modules/nixos/programs/network_storage/default.nix @@ -0,0 +1,10 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.network-storage = { + imports = [ + mod.network-storage-service + mod.network-storage-nfs + ]; + }; +} diff --git a/modules/nixos/programs/network_storage/network_storage.nix b/modules/nixos/programs/network_storage/network_storage.nix new file mode 100644 index 0000000..7e23c79 --- /dev/null +++ b/modules/nixos/programs/network_storage/network_storage.nix @@ -0,0 +1,88 @@ +{...}: { + flake.nixosModules.network-storage-service = { + config, + lib, + ... + }: let + export_directory = config.host.network_storage.export_directory; + in { + options = { + host.network_storage = { + enable = lib.mkEnableOption "is this machine going to export network storage"; + export_directory = lib.mkOption { + type = lib.types.path; + description = "what are exports going to be stored in"; + default = "/exports"; + }; + directories = lib.mkOption { + type = lib.types.listOf (lib.types.submodule ({config, ...}: { + options = { + folder = lib.mkOption { + type = lib.types.str; + description = "what is the name of this export directory"; + }; + bind = lib.mkOption { + type = lib.types.nullOr lib.types.path; + description = "is this directory bound to anywhere"; + default = null; + }; + user = lib.mkOption { + type = lib.types.str; + description = "what user owns this directory"; + default = "nouser"; + }; + group = lib.mkOption { + type = lib.types.str; + description = "what group owns this directory"; + default = "nogroup"; + }; + _directory = lib.mkOption { + internal = true; + readOnly = true; + type = lib.types.path; + default = "${export_directory}/${config.folder}"; + }; + }; + })); + description = "list of directory names to export"; + }; + }; + }; + + config = lib.mkIf config.host.network_storage.enable (lib.mkMerge [ + { + # create any folders that we need to have for our exports + systemd.tmpfiles.rules = + [ + "d ${config.host.network_storage.export_directory} 2775 nobody nogroup -" + ] + ++ ( + builtins.map ( + directory: "d ${directory._directory} 2770 ${directory.user} ${directory.group}" + ) + config.host.network_storage.directories + ); + + # set up any bind mounts that we need for our exports + fileSystems = builtins.listToAttrs ( + builtins.map (directory: + lib.attrsets.nameValuePair directory._directory { + device = directory.bind; + options = ["bind"]; + }) ( + builtins.filter (directory: directory.bind != null) config.host.network_storage.directories + ) + ); + } + # (lib.mkIf config.host.impermanence.enable { + # environment.persistence."/persist/replicate/system/root" = { + # enable = true; + # hideMounts = true; + # directories = [ + # config.host.network_storage.export_directory + # ]; + # }; + # }) + ]); + }; +} diff --git a/modules/nixos/programs/network_storage/nfs.nix b/modules/nixos/programs/network_storage/nfs.nix new file mode 100644 index 0000000..2a2a63b --- /dev/null +++ b/modules/nixos/programs/network_storage/nfs.nix @@ -0,0 +1,109 @@ +{...}: { + flake.nixosModules.network-storage-nfs = { + config, + lib, + ... + }: { + options = { + host.network_storage.nfs = { + enable = lib.mkEnableOption "is this server going to export network storage as nfs shares"; + port = lib.mkOption { + type = lib.types.int; + default = 2049; + description = "port that nfs will run on"; + }; + directories = lib.mkOption { + type = lib.types.listOf ( + lib.types.enum ( + builtins.map ( + directory: directory.folder + ) + config.host.network_storage.directories + ) + ); + description = "list of exported directories to be exported via nfs"; + }; + }; + }; + config = lib.mkMerge [ + { + assertions = [ + { + assertion = !(config.host.network_storage.nfs.enable && !config.host.network_storage.enable); + message = "nfs cant be enabled with network storage disabled"; + } + ]; + } + ( + lib.mkIf (config.host.network_storage.nfs.enable && config.host.network_storage.enable) { + services.nfs = { + settings = { + nfsd = { + threads = 32; + port = config.host.network_storage.nfs.port; + }; + }; + server = { + enable = true; + + lockdPort = 4001; + mountdPort = 4002; + statdPort = 4000; + + exports = lib.strings.concatLines ( + [ + "${config.host.network_storage.export_directory} 100.64.0.0/10(rw,fsid=0,no_subtree_check)" + ] + ++ ( + lib.lists.imap0 ( + i: directory: let + createOptions = fsid: "(rw,fsid=${toString fsid},nohide,insecure,no_subtree_check)"; + addresses = [ + # loopback + "127.0.0.1" + "::1" + # tailscale + "100.64.0.0/10" + "fd7a:115c:a1e0::/48" + ]; + options = lib.strings.concatStrings ( + lib.strings.intersperse " " ( + lib.lists.imap0 (index: address: "${address}${createOptions (1 + (i * (builtins.length addresses)) + index)}") addresses + ) + ); + in "${directory._directory} ${options}" + ) + ( + builtins.filter ( + directory: lib.lists.any (target: target == directory.folder) config.host.network_storage.nfs.directories + ) + config.host.network_storage.directories + ) + ) + ); + }; + }; + networking.firewall = let + ports = [ + 111 + config.host.network_storage.nfs.port + config.services.nfs.server.lockdPort + config.services.nfs.server.mountdPort + config.services.nfs.server.statdPort + 20048 + ]; + in { + # Allow NFS on Tailscale interface + interfaces.${config.services.tailscale.interfaceName} = { + allowedTCPPorts = ports; + allowedUDPPorts = ports; + }; + # Allow NFS on local network (assuming default interface) + allowedTCPPorts = ports; + allowedUDPPorts = ports; + }; + } + ) + ]; + }; +} diff --git a/modules/nixos/programs/panoramax/database.nix b/modules/nixos/programs/panoramax/database.nix new file mode 100644 index 0000000..5077352 --- /dev/null +++ b/modules/nixos/programs/panoramax/database.nix @@ -0,0 +1,50 @@ +{...}: { + flake.nixosModules.panoramax-database = { + lib, + config, + ... + }: { + options.services.panoramax = { + database = { + postgres = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Use PostgreSQL instead of SQLite"; + }; + user = lib.mkOption { + type = lib.types.str; + default = "panoramax"; + description = "Database user name"; + }; + database = lib.mkOption { + type = lib.types.str; + default = "panoramax"; + description = "Database name"; + }; + }; + }; + }; + + config = lib.mkIf config.services.panoramax.enable { + assertions = [ + { + assertion = !config.services.panoramax.database.postgres.enable || config.services.postgresql.enable; + message = "PostgreSQL must be enabled when using postgres database for Panoramax"; + } + ]; + + services.postgresql.databases.panoramax = lib.mkIf config.services.panoramax.database.postgres.enable { + enable = true; + user = config.services.panoramax.database.postgres.user; + database = config.services.panoramax.database.postgres.database; + }; + + systemd.services.panoramax = lib.mkIf config.services.panoramax.database.postgres.enable { + requires = [ + config.systemd.services.postgresql.name + ]; + }; + }; + }; +} diff --git a/modules/nixos/programs/panoramax/default.nix b/modules/nixos/programs/panoramax/default.nix new file mode 100644 index 0000000..29954f5 --- /dev/null +++ b/modules/nixos/programs/panoramax/default.nix @@ -0,0 +1,13 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.panoramax = { + imports = [ + mod.panoramax-service + mod.panoramax-database + mod.panoramax-proxy + mod.panoramax-fail2ban + mod.panoramax-storage + ]; + }; +} diff --git a/modules/nixos/programs/panoramax/fail2ban.nix b/modules/nixos/programs/panoramax/fail2ban.nix new file mode 100644 index 0000000..97b3a2a --- /dev/null +++ b/modules/nixos/programs/panoramax/fail2ban.nix @@ -0,0 +1,13 @@ +{...}: { + flake.nixosModules.panoramax-fail2ban = { + lib, + config, + ... + }: { + config = lib.mkIf (config.services.panoramax.enable && config.services.fail2ban.enable) { + # TODO: configure options for fail2ban + # This is a placeholder - panoramax fail2ban configuration would need to be defined + # based on the specific log patterns and security requirements + }; + }; +} diff --git a/modules/nixos/programs/panoramax/panoramax.nix b/modules/nixos/programs/panoramax/panoramax.nix new file mode 100644 index 0000000..c9d09b7 --- /dev/null +++ b/modules/nixos/programs/panoramax/panoramax.nix @@ -0,0 +1,361 @@ +{...}: { + flake.nixosModules.panoramax-service = { + config, + lib, + pkgs, + ... + }: { + options.services = { + panoramax = { + enable = lib.mkEnableOption "panoramax"; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.panoramax; + description = "The panoramax package to use"; + }; + + user = lib.mkOption { + type = lib.types.str; + default = "panoramax"; + description = "The user panoramax should run as."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "panoramax"; + description = "The group panoramax should run as."; + }; + + host = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = "Host to bind the panoramax service to"; + }; + + port = lib.mkOption { + type = lib.types.nullOr lib.types.port; + default = 5000; + description = "Port for the panoramax service"; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to open the panoramax port in the firewall"; + }; + + settings = { + urlScheme = lib.mkOption { + type = lib.types.enum ["http" "https"]; + default = "https"; + description = "URL scheme for the application"; + }; + + storage = { + fsUrl = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "/var/lib/panoramax/storage"; + description = "File system URL for storage"; + }; + }; + + infrastructure = { + nbProxies = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = 1; + description = "Number of proxies in front of the application"; + }; + }; + + flask = { + secretKey = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Flask secret key for session security"; + }; + + sessionCookieDomain = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Flask session cookie domain"; + }; + }; + + api = { + pictures = { + licenseSpdxId = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "SPDX license identifier for API pictures"; + }; + + licenseUrl = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "License URL for API pictures"; + }; + }; + }; + + extraEnvironment = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; + description = "Additional environment variables"; + example = { + CUSTOM_SETTING = "value"; + DEBUG = "true"; + }; + }; + }; + + database = { + createDB = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to automatically create the database and user"; + }; + + name = lib.mkOption { + type = lib.types.str; + default = "panoramax"; + description = "The name of the panoramax database"; + }; + + host = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "/run/postgresql"; + description = "Hostname or address of the postgresql server. If an absolute path is given here, it will be interpreted as a unix socket path."; + }; + + port = lib.mkOption { + type = lib.types.nullOr lib.types.port; + default = 5432; + description = "Port of the postgresql server."; + }; + + user = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "panoramax"; + description = "The database user for panoramax."; + }; + + # TODO: password file for external database + }; + + sgblur = { + # TODO: configs to bind to sgblur + }; + }; + sgblur = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to enable sgblur integration for face and license plate blurring"; + }; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.sgblur; + description = "The sgblur package to use"; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 8080; + description = "Port for the sgblur service"; + }; + + host = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = "Host to bind the sgblur service to"; + }; + + url = lib.mkOption { + type = lib.types.str; + default = "http://127.0.0.1:8080"; + description = "URL where sgblur service is accessible"; + }; + }; + }; + + config = lib.mkIf config.services.panoramax.enable (lib.mkMerge [ + { + # Create panoramax user and group + users.users.${config.services.panoramax.user} = { + isSystemUser = true; + group = config.services.panoramax.group; + home = "/var/lib/panoramax"; + createHome = true; + }; + + users.groups.${config.services.panoramax.group} = {}; + + # Ensure storage directory exists with correct permissions + systemd.tmpfiles.rules = [ + "d '${config.services.panoramax.settings.storage.fsUrl}' 0755 ${config.services.panoramax.user} ${config.services.panoramax.group} - -" + ]; + + systemd.services.panoramax-api = { + description = "Panoramax API server (self hosted map street view)"; + after = ["network.target" "postgresql.service"]; + wantedBy = ["multi-user.target"]; + + environment = + { + # Core Flask configuration + FLASK_APP = "geovisio"; + + # Storage configuration + FS_URL = config.services.panoramax.settings.storage.fsUrl; + + # Infrastructure configuration + INFRA_NB_PROXIES = toString config.services.panoramax.settings.infrastructure.nbProxies; + + # Application configuration + PORT = toString config.services.panoramax.port; + + # Python path to include the panoramax package + PYTHONPATH = "${config.services.panoramax.package}/${pkgs.python3.sitePackages}"; + } + // ( + if config.services.panoramax.database.host == "/run/postgresql" + then { + DB_URL = "postgresql://${config.services.panoramax.database.user}@/${config.services.panoramax.database.name}?host=/run/postgresql"; + } + else { + DB_HOST = config.services.panoramax.database.host; + DB_PORT = toString config.services.panoramax.database.port; + DB_USERNAME = config.services.panoramax.database.user; + DB_NAME = config.services.panoramax.database.name; + } + ) + // (lib.optionalAttrs (config.services.panoramax.settings.flask.secretKey != null) { + FLASK_SECRET_KEY = config.services.panoramax.settings.flask.secretKey; + }) + // (lib.optionalAttrs (config.services.panoramax.settings.flask.sessionCookieDomain != null) { + FLASK_SESSION_COOKIE_DOMAIN = config.services.panoramax.settings.flask.sessionCookieDomain; + }) + // (lib.optionalAttrs (config.services.panoramax.settings.api.pictures.licenseSpdxId != null) { + API_PICTURES_LICENSE_SPDX_ID = config.services.panoramax.settings.api.pictures.licenseSpdxId; + }) + // (lib.optionalAttrs (config.services.panoramax.settings.api.pictures.licenseUrl != null) { + API_PICTURES_LICENSE_URL = config.services.panoramax.settings.api.pictures.licenseUrl; + }) + // (lib.optionalAttrs config.services.sgblur.enable { + SGBLUR_API_URL = config.services.sgblur.url; + }) + // config.services.panoramax.settings.extraEnvironment; + + path = with pkgs; [ + (python3.withPackages (ps: with ps; [config.services.panoramax.package waitress])) + ]; + + serviceConfig = { + ExecStart = "${pkgs.python3.withPackages (ps: with ps; [config.services.panoramax.package waitress])}/bin/waitress-serve --port ${toString config.services.panoramax.port} --call geovisio:create_app"; + User = config.services.panoramax.user; + Group = config.services.panoramax.group; + WorkingDirectory = "/var/lib/panoramax"; + Restart = "always"; + RestartSec = 5; + + # Security hardening + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + ReadWritePaths = [ + "/var/lib/panoramax" + config.services.panoramax.settings.storage.fsUrl + ]; + NoNewPrivileges = true; + PrivateDevices = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictSUIDSGID = true; + RestrictRealtime = true; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + SystemCallArchitectures = "native"; + }; + }; + + # Open firewall if requested + networking.firewall.allowedTCPPorts = lib.mkIf config.services.panoramax.openFirewall [ + config.services.panoramax.port + ]; + } + (lib.mkIf config.services.sgblur.enable { + # SGBlur service configuration + systemd.services.sgblur = { + description = "SGBlur face and license plate blurring service"; + after = ["network.target"]; + wantedBy = ["multi-user.target"]; + + path = with pkgs; [ + config.services.sgblur.package + python3 + python3Packages.waitress + ]; + + serviceConfig = { + ExecStart = "${pkgs.python3Packages.waitress}/bin/waitress-serve --host ${config.services.sgblur.host} --port ${toString config.services.sgblur.port} src.detect.detect_api:app"; + WorkingDirectory = "${config.services.sgblur.package}"; + Restart = "always"; + RestartSec = 5; + + # Basic security hardening + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + NoNewPrivileges = true; + PrivateDevices = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictSUIDSGID = true; + RestrictRealtime = true; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + SystemCallArchitectures = "native"; + }; + }; + + networking.firewall.allowedTCPPorts = lib.mkIf config.services.panoramax.openFirewall [ + config.services.sgblur.port + ]; + }) + (lib.mkIf config.services.panoramax.database.createDB { + services.postgresql = { + enable = true; + ensureDatabases = lib.mkIf config.services.panoramax.database.createDB [config.services.panoramax.database.name]; + ensureUsers = lib.mkIf config.services.panoramax.database.createDB [ + { + name = config.services.panoramax.database.user; + ensureDBOwnership = true; + ensureClauses.login = true; + } + ]; + extensions = ps: with ps; [postgis]; + }; + systemd.services.postgresql.serviceConfig.ExecStartPost = let + sqlFile = pkgs.writeText "panoramax-postgis-setup.sql" '' + CREATE EXTENSION IF NOT EXISTS postgis; + + -- TODO: how can we ensure that this runs after the databases have been created + -- ALTER DATABASE ${config.services.panoramax.database.name} SET TIMEZONE TO 'UTC'; + + GRANT SET ON PARAMETER session_replication_role TO ${config.services.panoramax.database.user}; + ''; + in [ + '' + ${lib.getExe' config.services.postgresql.package "psql"} -d "${config.services.panoramax.database.user}" -f "${sqlFile}" + '' + ]; + }) + ]); + }; +} diff --git a/modules/nixos/programs/panoramax/proxy.nix b/modules/nixos/programs/panoramax/proxy.nix new file mode 100644 index 0000000..4112e90 --- /dev/null +++ b/modules/nixos/programs/panoramax/proxy.nix @@ -0,0 +1,41 @@ +{...}: { + flake.nixosModules.panoramax-proxy = { + lib, + config, + ... + }: { + options.services.panoramax = { + domain = lib.mkOption { + type = lib.types.str; + description = "domain that panoramax will be hosted at"; + default = "panoramax.arpa"; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for panoramax"; + default = []; + }; + reverseProxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.panoramax.enable && config.services.reverseProxy.enable; + }; + }; + }; + + config = lib.mkIf config.services.panoramax.reverseProxy.enable { + services.reverseProxy.services.panoramax = { + target = "http://localhost:${toString config.services.panoramax.port}"; + domain = config.services.panoramax.domain; + extraDomains = config.services.panoramax.extraDomains; + + settings = { + proxyWebsockets.enable = true; + forwardHeaders.enable = true; + maxBodySize = 100000; + timeout = 300; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/panoramax/storage.nix b/modules/nixos/programs/panoramax/storage.nix new file mode 100644 index 0000000..cbd35d4 --- /dev/null +++ b/modules/nixos/programs/panoramax/storage.nix @@ -0,0 +1,21 @@ +{...}: { + flake.nixosModules.panoramax-storage = { + lib, + config, + ... + }: { + options.services.panoramax.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.panoramax.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.panoramax.enable { + storage.datasets.replicate."system/root" = { + directories."/var/lib/panoramax" = lib.mkIf config.services.panoramax.impermanence.enable { + owner.name = "panoramax"; + group.name = "panoramax"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/paperless/database.nix b/modules/nixos/programs/paperless/database.nix new file mode 100644 index 0000000..de88dc0 --- /dev/null +++ b/modules/nixos/programs/paperless/database.nix @@ -0,0 +1,32 @@ +{...}: { + flake.nixosModules.paperless-database = { + config, + lib, + ... + }: { + config = lib.mkIf config.services.paperless.enable { + assertions = [ + { + assertion = !config.services.paperless.database.createLocally || config.services.postgresql.enable; + message = "PostgreSQL must be enabled when using local postgres database for Paperless"; + } + { + assertion = !config.services.paperless.database.createLocally || (builtins.any (db: db == "paperless") config.services.postgresql.ensureDatabases); + message = "Paperless built-in database creation failed - expected 'paperless' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}"; + } + { + assertion = !config.services.paperless.database.createLocally || (builtins.any (user: user.name == "paperless") config.services.postgresql.ensureUsers); + message = "Paperless built-in user creation failed - expected user 'paperless' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}"; + } + ]; + + services.paperless.database.createLocally = lib.mkDefault true; + + systemd.services.paperless-scheduler = lib.mkIf config.services.paperless.database.createLocally { + requires = [ + config.systemd.services.postgresql.name + ]; + }; + }; + }; +} diff --git a/modules/nixos/programs/paperless/default.nix b/modules/nixos/programs/paperless/default.nix new file mode 100644 index 0000000..22df555 --- /dev/null +++ b/modules/nixos/programs/paperless/default.nix @@ -0,0 +1,13 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.paperless = { + imports = [ + mod.paperless-service + mod.paperless-database + mod.paperless-proxy + mod.paperless-fail2ban + mod.paperless-storage + ]; + }; +} diff --git a/modules/nixos/programs/paperless/fail2ban.nix b/modules/nixos/programs/paperless/fail2ban.nix new file mode 100644 index 0000000..c5c3294 --- /dev/null +++ b/modules/nixos/programs/paperless/fail2ban.nix @@ -0,0 +1,36 @@ +{...}: { + flake.nixosModules.paperless-fail2ban = { + config, + lib, + pkgs, + ... + }: { + config = lib.mkIf (config.services.paperless.enable && config.services.fail2ban.enable) { + environment.etc = { + "fail2ban/filter.d/paperless.local".text = ( + pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [Definition] + failregex = Login failed for user `.*` from (?:IP|private IP) ``\.$ + ignoreregex = + + '') + ); + }; + + services.fail2ban = { + jails = { + paperless.settings = { + enabled = true; + filter = "paperless"; + action = ''iptables-multiport[name=HTTP, port="http,https"]''; + logpath = "${config.services.paperless.dataDir}/log/*.log"; + backend = "auto"; + findtime = 600; + bantime = 600; + maxretry = 5; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/paperless/paperless.nix b/modules/nixos/programs/paperless/paperless.nix new file mode 100644 index 0000000..de295ee --- /dev/null +++ b/modules/nixos/programs/paperless/paperless.nix @@ -0,0 +1,29 @@ +{...}: { + flake.nixosModules.paperless-service = { + config, + lib, + ... + }: { + options.services.paperless = { + database = { + user = lib.mkOption { + type = lib.types.str; + description = "what is the user and database that we are going to use for paperless"; + default = "paperless"; + }; + }; + }; + + config = lib.mkIf config.services.paperless.enable { + services.paperless = { + configureTika = true; + settings = { + PAPERLESS_DBENGINE = "postgresql"; + PAPERLESS_DBHOST = "/run/postgresql"; + PAPERLESS_DBNAME = config.services.paperless.database.user; + PAPERLESS_DBUSER = config.services.paperless.database.user; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/paperless/proxy.nix b/modules/nixos/programs/paperless/proxy.nix new file mode 100644 index 0000000..95af0a7 --- /dev/null +++ b/modules/nixos/programs/paperless/proxy.nix @@ -0,0 +1,35 @@ +{...}: { + flake.nixosModules.paperless-proxy = { + config, + lib, + ... + }: { + options.services.paperless = { + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for paperless"; + default = []; + }; + reverseProxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.paperless.enable && config.services.reverseProxy.enable; + }; + }; + }; + + config = lib.mkIf config.services.paperless.reverseProxy.enable { + services.reverseProxy.services.paperless = { + target = "http://${config.services.paperless.address}:${toString config.services.paperless.port}"; + domain = config.services.paperless.domain; + extraDomains = config.services.paperless.extraDomains; + + settings = { + proxyWebsockets.enable = true; + forwardHeaders.enable = true; + maxBodySize = 50000; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/paperless/storage.nix b/modules/nixos/programs/paperless/storage.nix new file mode 100644 index 0000000..969a1e1 --- /dev/null +++ b/modules/nixos/programs/paperless/storage.nix @@ -0,0 +1,23 @@ +{...}: { + flake.nixosModules.paperless-storage = { + config, + lib, + ... + }: let + dataDir = "/var/lib/paperless"; + in { + options.services.paperless.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.paperless.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.paperless.enable { + storage.datasets.replicate."system/root" = { + directories."${dataDir}" = lib.mkIf config.services.paperless.impermanence.enable { + owner.name = "paperless"; + group.name = "paperless"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/postgres/default.nix b/modules/nixos/programs/postgres/default.nix new file mode 100644 index 0000000..4a13462 --- /dev/null +++ b/modules/nixos/programs/postgres/default.nix @@ -0,0 +1,10 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.postgres = { + imports = [ + mod.postgres-service + mod.postgres-storage + ]; + }; +} diff --git a/modules/nixos/programs/postgres/postgres.nix b/modules/nixos/programs/postgres/postgres.nix new file mode 100644 index 0000000..fecd7cd --- /dev/null +++ b/modules/nixos/programs/postgres/postgres.nix @@ -0,0 +1,124 @@ +{...}: { + flake.nixosModules.postgres-service = { + config, + lib, + pkgs, + ... + }: let + enabledDatabases = lib.filterAttrs (_: db: db.enable) config.services.postgresql.databases; + extraDatabasesList = config.services.postgresql.extraDatabases; + + serviceDatabaseUsers = lib.mapAttrsToList (_: db: { + name = db.user; + ensureDBOwnership = true; + }) (lib.filterAttrs (_: db: db.ensureUser) enabledDatabases); + + extraDatabaseUsers = + builtins.map (dbName: { + name = dbName; + ensureDBOwnership = true; + }) + extraDatabasesList; + + serviceDatabases = lib.mapAttrsToList (_: db: db.database) enabledDatabases; + extraDatabaseNames = extraDatabasesList; + + serviceUserMappings = lib.mapAttrsToList (_: db: "user_map ${db.user} ${db.user}") enabledDatabases; + extraUserMappings = builtins.map (dbName: "user_map ${dbName} ${dbName}") extraDatabasesList; + + builtinServiceMappings = let + forgejoMapping = lib.optional (config.services.forgejo.enable && config.services.forgejo.database.type == "postgres") "user_map forgejo forgejo"; + immichMapping = lib.optional (config.services.immich.enable && config.services.immich.database.enable) "user_map immich immich"; + paperlessMapping = lib.optional (config.services.paperless.enable && config.services.paperless.database.createLocally) "user_map paperless paperless"; + in + forgejoMapping ++ immichMapping ++ paperlessMapping; + in { + options = { + services.postgresql = { + databases = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { + options = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to create this database and user"; + }; + user = lib.mkOption { + type = lib.types.str; + default = name; + description = "Database user name"; + }; + database = lib.mkOption { + type = lib.types.str; + default = name; + description = "Database name"; + }; + ensureUser = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to ensure the user exists"; + }; + }; + })); + default = {}; + description = "Databases to create for services"; + }; + + extraDatabases = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = "Additional databases to create (user name will match database name)"; + example = ["custom_db" "test_db"]; + }; + + adminUsers = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = "System users who should have PostgreSQL superuser access"; + example = ["leyla" "admin"]; + }; + }; + }; + + config = lib.mkIf config.services.postgresql.enable { + services = { + postgresql = { + package = pkgs.postgresql_16; + + ensureUsers = + [ + {name = "postgres";} + ] + ++ serviceDatabaseUsers ++ extraDatabaseUsers; + + ensureDatabases = serviceDatabases ++ extraDatabaseNames; + + identMap = + '' + # ArbitraryMapName systemUser DBUser + + # Administration Users + superuser_map root postgres + superuser_map postgres postgres + '' + + ( + lib.strings.concatLines (builtins.map (user: "superuser_map ${user} postgres") config.services.postgresql.adminUsers) + ) + + '' + + # Client Users + '' + + ( + lib.strings.concatLines (serviceUserMappings ++ extraUserMappings ++ builtinServiceMappings) + ); + + authentication = pkgs.lib.mkOverride 10 '' + # type database DBuser origin-address auth-method optional_ident_map + local all postgres peer map=superuser_map + local sameuser all peer map=user_map + ''; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/postgres/storage.nix b/modules/nixos/programs/postgres/storage.nix new file mode 100644 index 0000000..056dc45 --- /dev/null +++ b/modules/nixos/programs/postgres/storage.nix @@ -0,0 +1,23 @@ +{...}: { + flake.nixosModules.postgres-storage = { + config, + lib, + ... + }: let + dataDir = "/var/lib/postgresql/16"; + in { + options.services.postgresql.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.postgresql.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.postgresql.enable { + storage.datasets.replicate."system/root" = { + directories."${dataDir}" = lib.mkIf config.services.postgresql.impermanence.enable { + owner.name = "postgres"; + group.name = "postgres"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/qbittorent/default.nix b/modules/nixos/programs/qbittorent/default.nix new file mode 100644 index 0000000..ea0f403 --- /dev/null +++ b/modules/nixos/programs/qbittorent/default.nix @@ -0,0 +1,10 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.qbittorent = { + imports = [ + mod.qbittorent-service + mod.qbittorent-storage + ]; + }; +} diff --git a/modules/nixos/programs/qbittorent/qbittorent.nix b/modules/nixos/programs/qbittorent/qbittorent.nix new file mode 100644 index 0000000..ac558fc --- /dev/null +++ b/modules/nixos/programs/qbittorent/qbittorent.nix @@ -0,0 +1,20 @@ +{...}: { + flake.nixosModules.qbittorent-service = { + lib, + config, + ... + }: { + options.services.qbittorrent = { + mediaDir = lib.mkOption { + type = lib.types.path; + description = lib.mdDoc '' + The directory to create to store qbittorrent media. + ''; + }; + }; + + config = lib.mkIf config.services.qbittorrent.enable { + # Main qbittorrent configuration goes here if needed + }; + }; +} diff --git a/modules/nixos/programs/qbittorent/storage.nix b/modules/nixos/programs/qbittorent/storage.nix new file mode 100644 index 0000000..55b9866 --- /dev/null +++ b/modules/nixos/programs/qbittorent/storage.nix @@ -0,0 +1,48 @@ +{...}: { + flake.nixosModules.qbittorent-storage = { + lib, + config, + ... + }: let + qbittorent_profile_directory = "/var/lib/qBittorrent/"; + in { + options.services.qbittorrent.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.qbittorrent.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.qbittorrent.enable { + storage.datasets.replicate = { + "system/root" = { + directories."${qbittorent_profile_directory}" = lib.mkIf config.services.qbittorrent.impermanence.enable { + owner.name = "qbittorrent"; + group.name = "qbittorrent"; + }; + }; + "system/media" = { + mount = "/persist/replicate/system/media"; + + directories."${config.services.qbittorrent.mediaDir}" = lib.mkIf config.services.qbittorrent.impermanence.enable { + owner.name = "qbittorrent"; + group.name = "qbittorrent"; + owner.permissions = { + read = true; + write = true; + execute = true; + }; + group.permissions = { + read = true; + write = true; + execute = true; + }; + other.permissions = { + read = true; + write = false; + execute = true; + }; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/radarr/default.nix b/modules/nixos/programs/radarr/default.nix new file mode 100644 index 0000000..e3ea4e4 --- /dev/null +++ b/modules/nixos/programs/radarr/default.nix @@ -0,0 +1,9 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.radarr = { + imports = [ + mod.radarr-storage + ]; + }; +} diff --git a/modules/nixos/programs/radarr/storage.nix b/modules/nixos/programs/radarr/storage.nix new file mode 100644 index 0000000..8cb98ae --- /dev/null +++ b/modules/nixos/programs/radarr/storage.nix @@ -0,0 +1,23 @@ +{...}: { + flake.nixosModules.radarr-storage = { + lib, + config, + ... + }: let + radarr_data_directory = "/var/lib/radarr/.config/Radarr"; + in { + options.services.radarr.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.radarr.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.radarr.enable { + storage.datasets.replicate."system/root" = { + directories."${radarr_data_directory}" = lib.mkIf config.services.radarr.impermanence.enable { + owner.name = "radarr"; + group.name = "radarr"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/reverseProxy/default.nix b/modules/nixos/programs/reverseProxy/default.nix new file mode 100644 index 0000000..20af20c --- /dev/null +++ b/modules/nixos/programs/reverseProxy/default.nix @@ -0,0 +1,10 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.reverse-proxy = { + imports = [ + mod.reverse-proxy-service + mod.reverse-proxy-storage + ]; + }; +} diff --git a/modules/nixos/programs/reverseProxy/reverseProxy.nix b/modules/nixos/programs/reverseProxy/reverseProxy.nix new file mode 100644 index 0000000..9401bc6 --- /dev/null +++ b/modules/nixos/programs/reverseProxy/reverseProxy.nix @@ -0,0 +1,178 @@ +{...}: { + flake.nixosModules.reverse-proxy-service = { + lib, + config, + ... + }: { + options.services.reverseProxy = { + enable = lib.mkEnableOption "turn on the reverse proxy"; + openFirewall = lib.mkEnableOption "open the firewall"; + refuseUnmatchedDomains = lib.mkOption { + type = lib.types.bool; + description = "refuse connections for domains that don't match any configured virtual hosts"; + default = true; + }; + ports = { + http = lib.mkOption { + type = lib.types.port; + description = "HTTP port for the reverse proxy"; + default = 80; + }; + https = lib.mkOption { + type = lib.types.port; + description = "HTTPS port for the reverse proxy"; + default = 443; + }; + }; + acme = { + enable = lib.mkOption { + type = lib.types.bool; + description = "enable ACME certificate management"; + default = true; + }; + email = lib.mkOption { + type = lib.types.str; + description = "email address for ACME certificate registration"; + }; + }; + services = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { + options = { + target = lib.mkOption { + type = lib.types.str; + description = "what url will all traffic to this application be forwarded to"; + }; + domain = lib.mkOption { + type = lib.types.str; + description = "what is the default subdomain to be used for this application to be used for"; + default = name; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for this domain"; + default = []; + }; + settings = { + certificateRenewal.enable = lib.mkOption { + type = lib.types.bool; + description = "auto renew certificates"; + default = true; + }; + forceSSL.enable = lib.mkOption { + type = lib.types.bool; + description = "auto renew certificates"; + default = true; + }; + proxyHeaders = { + enable = lib.mkEnableOption "should we proxy headers"; + timeout = lib.mkOption { + type = lib.types.int; + default = 60; + }; + }; + proxyWebsockets.enable = lib.mkEnableOption "should the default config proxy websockets"; + forwardHeaders.enable = lib.mkEnableOption "should the default config contain forward headers"; + noSniff.enable = lib.mkEnableOption "should the no sniff flags be set"; + proxyBuffering.enable = lib.mkOption { + type = lib.types.bool; + description = "should proxy buffering be enabled"; + default = true; + }; + maxBodySize = lib.mkOption { + type = lib.types.nullOr lib.types.int; + description = ""; + default = null; + }; + }; + }; + })); + }; + }; + + config = let + httpPort = config.services.reverseProxy.ports.http; + httpsPort = config.services.reverseProxy.ports.https; + in + lib.mkIf config.services.reverseProxy.enable { + security.acme = lib.mkIf config.services.reverseProxy.acme.enable { + acceptTerms = true; + defaults.email = config.services.reverseProxy.acme.email; + }; + + services.nginx = { + enable = true; + virtualHosts = lib.mkMerge ( + (lib.optionals config.services.reverseProxy.refuseUnmatchedDomains [ + { + "_" = { + default = true; + serverName = "_"; + locations."/" = { + extraConfig = '' + return 444; + ''; + }; + }; + } + ]) + ++ lib.lists.flatten ( + lib.attrsets.mapAttrsToList ( + name: service: let + hostConfig = { + forceSSL = service.settings.forceSSL.enable; + enableACME = service.settings.certificateRenewal.enable; + locations = { + "/" = { + proxyPass = service.target; + proxyWebsockets = service.settings.proxyWebsockets.enable; + recommendedProxySettings = service.settings.forwardHeaders.enable; + extraConfig = let + # Client upload size configuration + maxBodySizeConfig = + lib.optionalString (service.settings.maxBodySize != null) + "client_max_body_size ${toString service.settings.maxBodySize}M;"; + + # Security header configuration + noSniffConfig = + lib.optionalString service.settings.noSniff.enable + "add_header X-Content-Type-Options nosniff;"; + + # Proxy buffering configuration + proxyBufferingConfig = + lib.optionalString (!service.settings.proxyBuffering.enable) + "proxy_buffering off;"; + + # Proxy timeout configuration + proxyTimeoutConfig = + lib.optionalString service.settings.proxyHeaders.enable + '' + proxy_read_timeout ${toString service.settings.proxyHeaders.timeout}s; + proxy_connect_timeout ${toString service.settings.proxyHeaders.timeout}s; + proxy_send_timeout ${toString service.settings.proxyHeaders.timeout}s; + ''; + in + maxBodySizeConfig + noSniffConfig + proxyBufferingConfig + proxyTimeoutConfig; + }; + }; + }; + in ( + [ + { + ${service.domain} = hostConfig; + } + ] + ++ builtins.map (domain: {${domain} = hostConfig;}) + service.extraDomains + ) + ) + config.services.reverseProxy.services + ) + ); + }; + networking.firewall.allowedTCPPorts = lib.mkIf config.services.reverseProxy.openFirewall [ + httpPort + httpsPort + ]; + }; + }; +} diff --git a/modules/nixos/programs/reverseProxy/storage.nix b/modules/nixos/programs/reverseProxy/storage.nix new file mode 100644 index 0000000..868534b --- /dev/null +++ b/modules/nixos/programs/reverseProxy/storage.nix @@ -0,0 +1,23 @@ +{...}: { + flake.nixosModules.reverse-proxy-storage = { + lib, + config, + ... + }: let + dataDir = "/var/lib/acme"; + in { + options.services.reverseProxy.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.reverseProxy.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.reverseProxy.enable { + storage.datasets.replicate."system/root" = { + directories."${dataDir}" = lib.mkIf config.services.reverseProxy.impermanence.enable { + owner.name = "acme"; + group.name = "acme"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/searx/default.nix b/modules/nixos/programs/searx/default.nix new file mode 100644 index 0000000..d6ed868 --- /dev/null +++ b/modules/nixos/programs/searx/default.nix @@ -0,0 +1,10 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.searx = { + imports = [ + mod.searx-service + mod.searx-proxy + ]; + }; +} diff --git a/modules/nixos/programs/searx/proxy.nix b/modules/nixos/programs/searx/proxy.nix new file mode 100644 index 0000000..e0ca084 --- /dev/null +++ b/modules/nixos/programs/searx/proxy.nix @@ -0,0 +1,33 @@ +{...}: { + flake.nixosModules.searx-proxy = { + config, + lib, + ... + }: { + options.services.searx = { + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for searx"; + default = []; + }; + reverseProxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.searx.enable && config.services.reverseProxy.enable; + }; + }; + }; + + config = lib.mkIf config.services.searx.reverseProxy.enable { + services.reverseProxy.services.searx = { + target = "http://localhost:${toString config.services.searx.settings.server.port}"; + domain = config.services.searx.domain; + extraDomains = config.services.searx.extraDomains; + + settings = { + forwardHeaders.enable = true; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/searx/searx.nix b/modules/nixos/programs/searx/searx.nix new file mode 100644 index 0000000..3ad9710 --- /dev/null +++ b/modules/nixos/programs/searx/searx.nix @@ -0,0 +1,61 @@ +{...}: { + flake.nixosModules.searx-service = { + config, + lib, + inputs, + ... + }: { + config = lib.mkIf config.services.searx.enable { + sops.secrets = { + "services/searx" = { + sopsFile = "${inputs.secrets}/defiant-services.yaml"; + }; + }; + + services.searx = { + environmentFile = config.sops.secrets."services/searx".path; + + # Rate limiting + limiterSettings = { + real_ip = { + x_for = 1; + ipv4_prefix = 32; + ipv6_prefix = 56; + }; + + botdetection = { + ip_limit = { + filter_link_local = true; + link_token = true; + }; + }; + }; + + settings = { + server = { + port = 8083; + secret_key = "@SEARXNG_SECRET@"; + }; + + # Search engine settings + search = { + safe_search = 2; + autocomplete_min = 2; + autocomplete = "duckduckgo"; + }; + + # Enabled plugins + enabled_plugins = [ + "Basic Calculator" + "Hash plugin" + "Tor check plugin" + "Open Access DOI rewrite" + "Hostnames plugin" + "Unit converter plugin" + "Tracker URL remover" + ]; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/sonarr/default.nix b/modules/nixos/programs/sonarr/default.nix new file mode 100644 index 0000000..681cd4b --- /dev/null +++ b/modules/nixos/programs/sonarr/default.nix @@ -0,0 +1,9 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.sonarr = { + imports = [ + mod.sonarr-storage + ]; + }; +} diff --git a/modules/nixos/programs/sonarr/storage.nix b/modules/nixos/programs/sonarr/storage.nix new file mode 100644 index 0000000..01aa891 --- /dev/null +++ b/modules/nixos/programs/sonarr/storage.nix @@ -0,0 +1,23 @@ +{...}: { + flake.nixosModules.sonarr-storage = { + lib, + config, + ... + }: let + sonarr_data_directory = "/var/lib/sonarr/.config/NzbDrone"; + in { + options.services.sonarr.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.sonarr.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.sonarr.enable { + storage.datasets.replicate."system/root" = { + directories."${sonarr_data_directory}" = lib.mkIf config.services.sonarr.impermanence.enable { + owner.name = "sonarr"; + group.name = "sonarr"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/steam.nix b/modules/nixos/programs/steam.nix new file mode 100644 index 0000000..5a20742 --- /dev/null +++ b/modules/nixos/programs/steam.nix @@ -0,0 +1,11 @@ +{...}: { + flake.nixosModules.steam = {...}: { + programs = { + steam = { + remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play + dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server + localNetworkGameTransfers.openFirewall = true; # Open ports in the firewall for Steam Local Network Game Transfers + }; + }; + }; +} diff --git a/modules/nixos/programs/sync/default.nix b/modules/nixos/programs/sync/default.nix new file mode 100644 index 0000000..133aeaa --- /dev/null +++ b/modules/nixos/programs/sync/default.nix @@ -0,0 +1,10 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.sync = { + imports = [ + mod.sync-service + mod.sync-storage + ]; + }; +} diff --git a/modules/nixos/programs/sync/storage.nix b/modules/nixos/programs/sync/storage.nix new file mode 100644 index 0000000..a3b71d7 --- /dev/null +++ b/modules/nixos/programs/sync/storage.nix @@ -0,0 +1,34 @@ +{...}: { + flake.nixosModules.sync-storage = { + config, + lib, + ... + }: let + mountDir = "/mnt/sync"; + configDir = "/etc/syncthing"; + in { + options = { + services.syncthing.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.syncthing.enable && config.storage.impermanence.enable; + }; + }; + + config = lib.mkIf config.services.syncthing.enable { + storage.datasets.replicate."system/root" = { + directories = { + "${mountDir}" = lib.mkIf config.services.syncthing.impermanence.enable { + enable = true; + owner.name = "syncthing"; + group.name = "syncthing"; + }; + "${configDir}" = lib.mkIf config.services.syncthing.impermanence.enable { + enable = true; + owner.name = "syncthing"; + group.name = "syncthing"; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/sync/sync.nix b/modules/nixos/programs/sync/sync.nix new file mode 100644 index 0000000..935f401 --- /dev/null +++ b/modules/nixos/programs/sync/sync.nix @@ -0,0 +1,38 @@ +{...}: { + flake.nixosModules.sync-service = { + config, + lib, + syncthingConfiguration, + ... + }: let + mountDir = "/mnt/sync"; + configDir = "/etc/syncthing"; + in { + config = lib.mkMerge [ + { + systemd = lib.mkIf config.services.syncthing.enable { + tmpfiles.rules = [ + "A ${mountDir} - - - - u:syncthing:rwX,g:syncthing:rwX,o::-" + "d ${mountDir} 2755 syncthing syncthing -" + "d ${config.services.syncthing.dataDir} 775 syncthing syncthing -" + "d ${config.services.syncthing.configDir} 755 syncthing syncthing -" + ]; + }; + } + (lib.mkIf config.services.syncthing.enable (lib.mkMerge [ + { + services.syncthing = { + user = "syncthing"; + group = "syncthing"; + dataDir = "${mountDir}/default"; + configDir = configDir; + overrideDevices = true; + overrideFolders = true; + configuration = syncthingConfiguration; + deviceName = config.networking.hostName; + }; + } + ])) + ]; + }; +} diff --git a/modules/nixos/programs/tailscale/default.nix b/modules/nixos/programs/tailscale/default.nix new file mode 100644 index 0000000..0768379 --- /dev/null +++ b/modules/nixos/programs/tailscale/default.nix @@ -0,0 +1,10 @@ +{config, ...}: let + mod = config.flake.nixosModules; +in { + flake.nixosModules.tailscale = { + imports = [ + mod.tailscale-service + mod.tailscale-storage + ]; + }; +} diff --git a/modules/nixos/programs/tailscale/storage.nix b/modules/nixos/programs/tailscale/storage.nix new file mode 100644 index 0000000..4fc969a --- /dev/null +++ b/modules/nixos/programs/tailscale/storage.nix @@ -0,0 +1,26 @@ +{...}: { + flake.nixosModules.tailscale-storage = { + config, + lib, + ... + }: let + tailscale_data_directory = "/var/lib/tailscale"; + in { + options = { + services.tailscale.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.tailscale.enable && config.storage.impermanence.enable; + }; + }; + + config = lib.mkIf config.services.tailscale.enable { + storage.datasets.replicate."system/root" = { + directories."${tailscale_data_directory}" = lib.mkIf config.services.tailscale.impermanence.enable { + enable = true; + owner.name = "root"; + group.name = "root"; + }; + }; + }; + }; +} diff --git a/modules/nixos/programs/tailscale/tailscale.nix b/modules/nixos/programs/tailscale/tailscale.nix new file mode 100644 index 0000000..37876ac --- /dev/null +++ b/modules/nixos/programs/tailscale/tailscale.nix @@ -0,0 +1,21 @@ +{...}: { + flake.nixosModules.tailscale-service = { + config, + lib, + ... + }: { + options = { + host.tailscale = { + enable = lib.mkEnableOption "should tailscale be enabled on this computer"; + }; + }; + + config = lib.mkIf config.services.tailscale.enable ( + lib.mkMerge [ + { + # any configs we want shared between all machines + } + ] + ); + }; +} diff --git a/modules/nixos/programs/wyoming.nix b/modules/nixos/programs/wyoming.nix new file mode 100644 index 0000000..8f0af5d --- /dev/null +++ b/modules/nixos/programs/wyoming.nix @@ -0,0 +1,65 @@ +{...}: { + flake.nixosModules.wyoming = { + lib, + config, + ... + }: { + options.services.wyoming.enable = lib.mkEnableOption "should wyoming be enabled on this device"; + config = lib.mkIf config.services.wyoming.enable (lib.mkMerge [ + { + services.wyoming = { + # Text to speech + piper = { + servers = { + "en" = { + enable = true; + # see https://github.com/rhasspy/rhasspy3/blob/master/programs/tts/piper/script/download.py + voice = "en-us-amy-low"; + uri = "tcp://0.0.0.0:10200"; + speaker = 0; + }; + }; + }; + + # Speech to text + faster-whisper = { + servers = { + "en" = { + enable = true; + # see https://github.com/rhasspy/rhasspy3/blob/master/programs/asr/faster-whisper/script/download.py + model = "tiny-int8"; + language = "en"; + uri = "tcp://0.0.0.0:10300"; + device = "cpu"; + }; + }; + }; + + openwakeword = { + enable = true; + uri = "tcp://0.0.0.0:10400"; + # preloadModels = [ + # "ok_nabu" + # ]; + # TODO: custom models + }; + }; + + # needs access to /proc/cpuinfo + systemd.services."wyoming-faster-whisper-en".serviceConfig.ProcSubset = lib.mkForce "all"; + } + (lib.mkIf config.host.impermanence.enable { + environment.persistence."/persist/replicate/system/root" = { + enable = true; + hideMounts = true; + directories = [ + { + directory = "/var/lib/private/wyoming"; + mode = "0700"; + } + ]; + }; + }) + ]); + }; +} diff --git a/modules/nixos/ssh.nix b/modules/nixos/ssh.nix new file mode 100644 index 0000000..b4f94bc --- /dev/null +++ b/modules/nixos/ssh.nix @@ -0,0 +1,46 @@ +{...}: { + flake.nixosModules.nixos-ssh = { + lib, + config, + ... + }: { + options = { + services.openssh.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.openssh.enable && config.storage.impermanence.enable; + }; + }; + + config = { + services = { + openssh = { + enable = true; + ports = [22]; + settings = { + PasswordAuthentication = false; + UseDns = true; + X11Forwarding = false; + }; + }; + }; + + storage.datasets.replicate."system/root" = { + files = lib.mkIf config.services.openssh.impermanence.enable (builtins.listToAttrs ( + lib.lists.flatten ( + builtins.map (hostKey: [ + { + name = hostKey.path; + value = {enable = true;}; + } + { + name = "${hostKey.path}.pub"; + value = {enable = true;}; + } + ]) + config.services.openssh.hostKeys + ) + )); + }; + }; + }; +} diff --git a/modules/nixos/storage/dataset.nix b/modules/nixos/storage/dataset.nix new file mode 100644 index 0000000..4ae1179 --- /dev/null +++ b/modules/nixos/storage/dataset.nix @@ -0,0 +1,90 @@ +{...}: let + submodule = {lib, ...}: {name, ...}: { + options = { + type = lib.mkOption { + type = lib.types.enum ["zfs_fs" "zfs_volume"]; + default = "zfs_fs"; + description = "Type of ZFS dataset (filesystem or volume)"; + }; + + acltype = lib.mkOption { + type = lib.types.nullOr (lib.types.enum ["off" "nfsv4" "posixacl"]); + default = null; + description = "Access control list type"; + }; + + relatime = lib.mkOption { + type = lib.types.nullOr (lib.types.enum ["on" "off"]); + default = null; + description = "Controls when access time is updated"; + }; + + atime = lib.mkOption { + type = lib.types.nullOr (lib.types.enum ["on" "off"]); + default = null; + description = "Controls whether access time is updated"; + }; + + xattr = lib.mkOption { + type = lib.types.nullOr (lib.types.enum ["on" "off" "sa" "dir"]); + default = null; + description = "Extended attribute storage method"; + }; + + compression = lib.mkOption { + type = lib.types.nullOr (lib.types.enum ["on" "off" "lz4" "gzip" "zstd" "lzjb" "zle"]); + default = null; + description = "Compression algorithm to use"; + }; + + sync = lib.mkOption { + type = lib.types.nullOr (lib.types.enum ["standard" "always" "disabled"]); + default = null; + description = "Synchronous write behavior"; + }; + + mount = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = "Controls the mount point used for this file system"; + default = null; + }; + + encryption = { + enable = lib.mkEnableOption "should encryption be enabled"; + type = lib.mkOption { + type = lib.types.enum ["aes-128-ccm" "aes-192-ccm" "aes-256-ccm" "aes-128-gcm" "aes-192-gcm" "aes-256-gcm"]; + description = "What encryption type to use"; + }; + keyformat = lib.mkOption { + type = lib.types.enum ["raw" "hex" "passphrase"]; + description = "Format of the encryption key"; + }; + keylocation = lib.mkOption { + type = lib.types.str; + description = "Location of the encryption key"; + }; + }; + + snapshot = { + # This option should set this option flag + autoSnapshot = lib.mkEnableOption "Enable automatic snapshots for this dataset"; + # Creates a blank snapshot in the post create hook for rollback purposes + blankSnapshot = lib.mkEnableOption "Should a blank snapshot be auto created in the post create hook"; + }; + + recordSize = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Suggested block size for files in the file system"; + }; + + postCreateHook = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Script to run after dataset creation"; + }; + }; + }; +in { + flake.commonModules.storage-dataset-submodule = submodule; +} diff --git a/legacy-modules/nixos-modules/storage/default.nix b/modules/nixos/storage/default.nix similarity index 67% rename from legacy-modules/nixos-modules/storage/default.nix rename to modules/nixos/storage/default.nix index ebf990a..f227f70 100644 --- a/legacy-modules/nixos-modules/storage/default.nix +++ b/modules/nixos/storage/default.nix @@ -1,13 +1,17 @@ -{...}: { +{config, ...}: let + mod = config.flake.nixosModules; +in { # TODO: we should have an impermanence module for home manager that proxies its values namespaced to the user down here that matches the same interface # TODO: we should have a way of enabling impermanence for a systemd config # these should have an option to put their folder into their own dataset (this needs to support private vs non private) # options for features that can be added to the dataset - imports = [ - ./impermanence.nix - ./zfs.nix - ./storage.nix - ]; + flake.nixosModules.storage = {...}: { + imports = [ + mod.storage-impermanence + mod.storage-zfs + mod.storage-config + ]; + }; } diff --git a/modules/nixos/storage/impermanence-dataset.nix b/modules/nixos/storage/impermanence-dataset.nix new file mode 100644 index 0000000..380e3f4 --- /dev/null +++ b/modules/nixos/storage/impermanence-dataset.nix @@ -0,0 +1,60 @@ +{config, ...}: let + datasetSubmodule = config.flake.commonModules.storage-dataset-submodule; + submodule = args @ {lib, ...}: {name, ...}: let + pathPermissions = { + read = lib.mkEnableOption "should the path have read permissions"; + write = lib.mkEnableOption "should the path have read permissions"; + execute = lib.mkEnableOption "should the path have read permissions"; + }; + pathTypeSubmodule = {name, ...}: { + options = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + }; + owner = { + name = lib.mkOption { + type = lib.types.str; + default = "root"; + }; + permissions = pathPermissions; + }; + group = { + name = lib.mkOption { + type = lib.types.str; + default = "root"; + }; + permissions = pathPermissions; + }; + other = { + permissions = pathPermissions; + }; + }; + }; + in { + imports = [ + (datasetSubmodule args) + ]; + + options = { + files = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule pathTypeSubmodule); + default = {}; + }; + directories = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule pathTypeSubmodule); + default = {}; + }; + impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = true; + }; + }; + + config = { + mount = lib.mkDefault "/${name}"; + }; + }; +in { + flake.commonModules.storage-impermanence-dataset-submodule = submodule; +} diff --git a/modules/nixos/storage/impermanence.nix b/modules/nixos/storage/impermanence.nix new file mode 100644 index 0000000..1ce8e5d --- /dev/null +++ b/modules/nixos/storage/impermanence.nix @@ -0,0 +1,147 @@ +{config, ...}: let + datasetSubmodule = config.flake.commonModules.storage-dataset-submodule; + impermanenceDatasetSubmodule = config.flake.commonModules.storage-impermanence-dataset-submodule; +in { + flake.nixosModules.storage-impermanence = args @ { + lib, + config, + ... + }: let + datasetSub = datasetSubmodule args; + impermanenceDatasetSub = impermanenceDatasetSubmodule args; + + permissionsToMode = permissions: let + permSetToDigit = permSet: + ( + if permSet.read + then 4 + else 0 + ) + + ( + if permSet.write + then 2 + else 0 + ) + + ( + if permSet.execute + then 1 + else 0 + ); + + ownerDigit = permSetToDigit permissions.owner.permissions; + groupDigit = permSetToDigit permissions.group.permissions; + otherDigit = permSetToDigit permissions.other.permissions; + in + toString ownerDigit + toString groupDigit + toString otherDigit; + + # Get the option names from both submodules to automatically determine which are impermanence-specific + regularDatasetEval = lib.evalModules { + modules = [datasetSub]; + specialArgs = args; + }; + impermanenceDatasetEval = lib.evalModules { + modules = [impermanenceDatasetSub]; + specialArgs = args; + }; + + regularDatasetOptions = builtins.attrNames regularDatasetEval.options; + impermanenceDatasetOptions = builtins.attrNames impermanenceDatasetEval.options; + + # Find options that are only in impermanence datasets (not in regular ZFS datasets) + impermanenceOnlyOptions = lib.lists.subtractLists regularDatasetOptions impermanenceDatasetOptions; + in { + options.storage = { + impermanence = { + enable = lib.mkEnableOption "should impermanence be enabled for this system"; + + datasets = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSub); + default = {}; + }; + }; + }; + + config = lib.mkIf config.storage.impermanence.enable (lib.mkMerge [ + { + assertions = [ + { + assertion = config.storage.zfs.enable; + message = "storage.impermanence can not be used without storage.zfs."; + } + ]; + + system.activationScripts = { + # fixes issues with /var/lib/private not having the correct permissions https://github.com/nix-community/impermanence/issues/254 + "createPersistentStorageDirs".deps = ["var-lib-private-permissions" "users" "groups"]; + + "var-lib-private-permissions" = lib.mkIf config.storage.generateBase { + deps = ["specialfs"]; + text = '' + mkdir -p /persist/replicate/system/root/var/lib/private + chmod 0700 /persist/replicate/system/root/var/lib/private + ''; + }; + }; + + programs.fuse.userAllowOther = true; + + # Suppress sudo lecture on every boot since impermanence wipes the lecture status file + security.sudo.extraConfig = "Defaults lecture=never"; + + fileSystems = + lib.mapAttrs' ( + datasetName: dataset: + lib.nameValuePair "/${datasetName}" { + device = "rpool/${datasetName}"; + fsType = "zfs"; + neededForBoot = true; + } + ) + (lib.filterAttrs ( + datasetName: dataset: dataset.impermanence.enable + ) + config.storage.impermanence.datasets); + + environment.persistence = + lib.mapAttrs (datasetName: dataset: { + enable = true; + hideMounts = true; + persistentStoragePath = "/${datasetName}"; + directories = lib.mapAttrsToList (path: dirConfig: { + directory = path; + user = dirConfig.owner.name; + group = dirConfig.group.name; + mode = permissionsToMode dirConfig; + }) (lib.filterAttrs (_: dirConfig: dirConfig.enable) dataset.directories); + files = lib.mapAttrsToList (path: fileConfig: { + file = path; + parentDirectory = { + user = fileConfig.owner.name; + group = fileConfig.group.name; + mode = permissionsToMode fileConfig; + }; + }) (lib.filterAttrs (_: fileConfig: fileConfig.enable) dataset.files); + }) + (lib.filterAttrs ( + datasetName: dataset: let + enabledDirectories = lib.filterAttrs (_: dirConfig: dirConfig.enable) dataset.directories; + enabledFiles = lib.filterAttrs (_: fileConfig: fileConfig.enable) dataset.files; + in + (enabledDirectories != {}) || (enabledFiles != {}) + ) + (lib.filterAttrs ( + datasetName: dataset: dataset.impermanence.enable + ) + config.storage.impermanence.datasets)); + } + (lib.mkIf config.storage.zfs.enable { + storage.zfs.datasets = + lib.mapAttrs ( + datasetName: dataset: + builtins.removeAttrs dataset impermanenceOnlyOptions + ) + config.storage.impermanence.datasets; + }) + ]); + }; +} diff --git a/modules/nixos/storage/storage.nix b/modules/nixos/storage/storage.nix new file mode 100644 index 0000000..5cc3201 --- /dev/null +++ b/modules/nixos/storage/storage.nix @@ -0,0 +1,221 @@ +{config, ...}: let + datasetSubmodule = config.flake.commonModules.storage-dataset-submodule; + impermanenceDatasetSubmodule = config.flake.commonModules.storage-impermanence-dataset-submodule; +in { + flake.nixosModules.storage-config = args @ { + lib, + config, + ... + }: let + datasetSub = datasetSubmodule args; + impermanenceDatasetSub = impermanenceDatasetSubmodule args; + + # Get the option names from both submodules to automatically determine which are impermanence-specific + regularDatasetEval = lib.evalModules { + modules = [datasetSub]; + specialArgs = args; + }; + impermanenceDatasetEval = lib.evalModules { + modules = [impermanenceDatasetSub]; + specialArgs = args; + }; + + regularDatasetOptions = builtins.attrNames regularDatasetEval.options; + impermanenceDatasetOptions = builtins.attrNames impermanenceDatasetEval.options; + + # Find options that are only in impermanence datasets (not in regular ZFS datasets) + impermanenceOnlyOptions = lib.lists.subtractLists regularDatasetOptions impermanenceDatasetOptions; + in { + options.storage = { + generateBase = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + When enabled, enables automatic generation of base datasets (ephemeral, local, replicate roots). + This allows manual definition of datasets matching an existing system layout for migration purposes. + ''; + }; + datasets = { + ephemeral = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule datasetSub); + default = {}; + }; + local = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSub); + default = {}; + }; + replicate = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSub); + default = {}; + }; + }; + }; + + config = lib.mkMerge [ + (lib.mkIf (config.storage.zfs.enable && config.storage.generateBase) { + # Create ZFS datasets based on storage.datasets configuration + storage.datasets = { + local = { + "nix" = { + impermanence.enable = false; + type = "zfs_fs"; + mount = "/nix"; + snapshot = { + autoSnapshot = false; + }; + atime = "off"; + relatime = "off"; + }; + }; + }; + }) + (lib.mkIf (config.storage.zfs.enable && config.storage.impermanence.enable && config.storage.generateBase) { + storage.datasets = { + ephemeral = { + "" = { + type = "zfs_fs"; + mount = null; + }; + "system/root" = { + type = "zfs_fs"; + mount = "/"; + snapshot = { + blankSnapshot = true; + }; + }; + }; + # TODO: can we auto set the mount points on these to just be `"/persist/local/${name}"` + local = { + "" = { + mount = "/persist/local"; + }; + }; + # TODO: can we auto set the mount points on these to just be `"/persist/replicate/${name}"` + replicate = { + "" = { + mount = "/persist/replicate"; + }; + "system/root" = { + mount = "/persist/replicate/system/root"; + snapshot = { + autoSnapshot = true; + }; + directories = { + "/var/lib/nixos".enable = true; + "/var/lib/systemd/coredump".enable = true; + }; + files = { + "/etc/machine-id".enable = true; + }; + }; + "home" = { + mount = "/persist/replicate/home"; + snapshot = { + autoSnapshot = true; + }; + }; + "system/var/log" = { + type = "zfs_fs"; + directories = { + "/var/log".enable = true; + }; + }; + }; + }; + + storage.zfs.datasets = lib.mkMerge [ + (lib.mapAttrs' (name: dataset: { + name = + if name == "" + then "ephemeral" + else "ephemeral/${name}"; + value = dataset; + }) + config.storage.datasets.ephemeral) + ]; + + boot.initrd.postResumeCommands = lib.mkAfter '' + zfs rollback -r rpool/ephemeral/system/root@blank + ''; + + storage.impermanence.datasets = lib.mkMerge [ + (lib.mapAttrs' (name: dataset: { + name = + if name == "" + then "persist/local" + else "persist/local/${name}"; + value = dataset; + }) + config.storage.datasets.local) + (lib.mapAttrs' (name: dataset: { + name = + if name == "" + then "persist/replicate" + else "persist/replicate/${name}"; + value = dataset; + }) + config.storage.datasets.replicate) + ]; + }) + (lib.mkIf (config.storage.zfs.enable && !config.storage.impermanence.enable && config.storage.generateBase) { + storage.datasets = { + # Base organizational datasets (only needed when impermanence is disabled) + local = { + "" = { + type = "zfs_fs"; + mount = null; + }; + "root" = { + type = "zfs_fs"; + mount = "/"; + compression = "lz4"; + acltype = "posixacl"; + relatime = "on"; + xattr = "sa"; + snapshot = { + autoSnapshot = true; + blankSnapshot = true; + }; + }; + }; + replicate = { + "" = { + type = "zfs_fs"; + mount = null; + }; + "system/var/log" = { + type = "zfs_fs"; + mount = "/var/log"; + }; + }; + }; + + storage.zfs.datasets = lib.mkMerge [ + (lib.mapAttrs' (name: dataset: { + name = + if name == "" + then "persist/local" + else "persist/local/${name}"; + value = builtins.removeAttrs dataset impermanenceOnlyOptions; + }) + config.storage.datasets.local) + (lib.mapAttrs' (name: dataset: { + name = + if name == "" + then "persist/replicate" + else "persist/replicate/${name}"; + value = builtins.removeAttrs dataset impermanenceOnlyOptions; + }) + config.storage.datasets.replicate) + ]; + }) + ]; + + # TODO: set up datasets for systemd services that want a dataset created + # TODO: home-manager.users..storage.impermanence.enable + # is false then persist the entire directory of the user + # if true persist home-manager.users..storage.impermanence.datasets + # TODO: systemd.services..storage.datasets persists + # TODO: configure other needed storage modes here + }; +} diff --git a/modules/nixos/storage/zfs.nix b/modules/nixos/storage/zfs.nix new file mode 100644 index 0000000..2817265 --- /dev/null +++ b/modules/nixos/storage/zfs.nix @@ -0,0 +1,351 @@ +{config, ...}: let + datasetSubmodule = config.flake.commonModules.storage-dataset-submodule; +in { + flake.nixosModules.storage-zfs = args @ { + lib, + pkgs, + config, + ... + }: let + datasetSub = datasetSubmodule args; + + # Hash function for disk names (max 27 chars to fit GPT limitations) + hashDisk = drive: (builtins.substring 0 27 (builtins.hashString "sha256" drive)); + + # Map "stripe" to "" for disko compatibility (disko uses "" for stripe mode) + diskoPoolMode = + if config.storage.zfs.pool.mode == "stripe" + then "" + else config.storage.zfs.pool.mode; + + # Helper to flatten vdevs into list of devices with names + allVdevDevices = lib.lists.flatten (builtins.map ( + vdev: + builtins.map ( + device: + lib.attrsets.nameValuePair (hashDisk device.device) device + ) + vdev + ) + config.storage.zfs.pool.vdevs); + + # Cache devices with names + allCacheDevices = builtins.map ( + device: + lib.attrsets.nameValuePair (hashDisk device.device) device + ) (config.storage.zfs.pool.cache); + + # All devices (vdevs + cache) + allDevices = allVdevDevices ++ allCacheDevices; + + # Boot devices - filter devices that have boot = true + bootDevices = builtins.filter (device: device.value.boot) allDevices; + + # Helper function to convert dataset options to ZFS properties + datasetToZfsOptions = dataset: let + baseOptions = + (lib.attrsets.optionalAttrs (dataset.acltype != null) {acltype = dataset.acltype;}) + // (lib.attrsets.optionalAttrs (dataset.relatime != null) {relatime = dataset.relatime;}) + // (lib.attrsets.optionalAttrs (dataset.atime != null) {atime = dataset.atime;}) + // (lib.attrsets.optionalAttrs (dataset.xattr != null) {xattr = dataset.xattr;}) + // (lib.attrsets.optionalAttrs (dataset.compression != null) {compression = dataset.compression;}) + // (lib.attrsets.optionalAttrs (dataset.sync != null) {sync = dataset.sync;}) + // (lib.attrsets.optionalAttrs (dataset.recordSize != null) {recordSize = dataset.recordSize;}); + + encryptionOptions = lib.attrsets.optionalAttrs (dataset.encryption.enable) ( + (lib.attrsets.optionalAttrs (dataset.encryption ? type) {encryption = dataset.encryption.type;}) + // (lib.attrsets.optionalAttrs (dataset.encryption ? keyformat) {keyformat = dataset.encryption.keyformat;}) + // (lib.attrsets.optionalAttrs (dataset.encryption ? keylocation) {keylocation = dataset.encryption.keylocation;}) + ); + + mountOptions = lib.attrsets.optionalAttrs (dataset ? mount && dataset.mount ? enable) ( + if builtins.isBool dataset.mount.enable + then { + canmount = + if dataset.mount.enable + then "on" + else "off"; + } + else {canmount = dataset.mount.enable;} + ); + + snapshotOptions = lib.attrsets.optionalAttrs (dataset ? snapshot && dataset.snapshot ? autoSnapshot) { + "com.sun:auto-snapshot" = + if dataset.snapshot.autoSnapshot + then "true" + else "false"; + }; + in + baseOptions // encryptionOptions // mountOptions // snapshotOptions; + + # Helper to generate post create hooks + generatePostCreateHook = name: dataset: + dataset.postCreateHook + + (lib.optionalString dataset.snapshot.blankSnapshot '' + zfs snapshot rpool/${name}@blank + ''); + + # Convert datasets to disko format + convertedDatasets = builtins.listToAttrs ( + (lib.attrsets.mapAttrsToList ( + name: dataset: + lib.attrsets.nameValuePair name { + type = dataset.type; + options = datasetToZfsOptions dataset; + mountpoint = dataset.mount or null; + postCreateHook = generatePostCreateHook name dataset; + } + ) + config.storage.zfs.datasets) + ++ (lib.optional (config.storage.zfs.rootDataset != null) ( + lib.attrsets.nameValuePair "" { + type = config.storage.zfs.rootDataset.type; + options = datasetToZfsOptions config.storage.zfs.rootDataset; + mountpoint = config.storage.zfs.rootDataset.mount or null; + postCreateHook = generatePostCreateHook "" config.storage.zfs.rootDataset; + } + )) + ); + in { + options.storage = { + zfs = { + enable = lib.mkEnableOption "Should zfs be enabled on this system."; + + notifications = { + enable = lib.mkEnableOption "are notifications enabled"; + host = lib.mkOption { + type = lib.types.str; + description = "what is the host that we are going to send the email to"; + }; + port = lib.mkOption { + type = lib.types.port; + description = "what port is the host using to receive mail on"; + }; + to = lib.mkOption { + type = lib.types.str; + description = "what account is the email going to be sent to"; + }; + user = lib.mkOption { + type = lib.types.str; + description = "what user is the email going to be set from"; + }; + tokenFile = lib.mkOption { + type = lib.types.str; + description = "file containing the password to be used by msmtp for notifications"; + }; + }; + + pool = let + deviceType = + lib.types.coercedTo lib.types.str (device: { + device = device; + boot = false; + }) (lib.types.submodule { + options = { + device = lib.mkOption { + type = lib.types.str; + }; + boot = lib.mkEnableOption "should this device be a boot device"; + }; + }); + in { + encryption = { + enable = lib.mkEnableOption "Should encryption be enabled on this pool."; + keyformat = lib.mkOption { + type = lib.types.enum ["raw" "hex" "passphrase"]; + default = "hex"; + description = "Format of the encryption key"; + }; + keylocation = lib.mkOption { + type = lib.types.str; + default = "prompt"; + description = "Location of the encryption key"; + }; + }; + mode = lib.mkOption { + type = lib.types.enum ["stripe" "mirror" "raidz1" "raidz2" "raidz3"]; + default = "raidz2"; + description = "ZFS redundancy mode for the pool"; + }; + bootPartitionSize = lib.mkOption { + type = lib.types.str; + default = "2G"; + description = "Size of the boot partition on boot drives"; + }; + vdevs = lib.mkOption { + type = lib.types.listOf (lib.types.listOf deviceType); + default = []; + description = "List of vdevs, where each vdev is a list of devices"; + }; + cache = lib.mkOption { + type = lib.types.listOf deviceType; + default = []; + }; + }; + + rootDataset = lib.mkOption { + type = lib.types.nullOr (lib.types.submodule datasetSub); + description = "Root ZFS dataset to create"; + default = null; + }; + + datasets = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule datasetSub); + description = "Additional ZFS datasets to create"; + default = {}; + }; + }; + }; + + config = lib.mkIf config.storage.zfs.enable (lib.mkMerge [ + { + # Assertion that we have at least one boot device + assertions = [ + { + assertion = (builtins.length bootDevices) > 0; + message = "ZFS configuration requires at least one boot device. Set boot = true for at least one device in your vdevs or cache."; + } + ]; + + # # Warning about disk/dataset mismatches - these would be runtime checks + # warnings = let + # configuredDisks = builtins.map (device: device.device) (builtins.map (dev: dev.value) allDevices); + # diskWarnings = + # lib.optional (config.storage.zfs.enable) + # "ZFS: Please ensure the following disks are available on your system: ${builtins.concatStringsSep ", " configuredDisks}"; + + # configuredDatasets = builtins.attrNames config.storage.zfs.datasets; + # datasetWarnings = + # lib.optional (config.storage.zfs.enable && (builtins.length configuredDatasets) > 0) + # "ZFS: Configured datasets: ${builtins.concatStringsSep ", " configuredDatasets}. Ensure these match your intended ZFS layout."; + # in + # diskWarnings ++ datasetWarnings; + + services.zfs = { + autoScrub.enable = true; + autoSnapshot.enable = true; + }; + + # # Configure disko for ZFS setup + disko.devices = { + disk = builtins.listToAttrs ( + builtins.map ( + drive: + lib.attrsets.nameValuePair (drive.name) { + type = "disk"; + device = "/dev/disk/by-id/${drive.value.device}"; + content = { + type = "gpt"; + partitions = { + ESP = lib.mkIf drive.value.boot { + size = config.storage.zfs.pool.bootPartitionSize; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = ["umask=0077"]; + }; + }; + zfs = { + size = "100%"; + content = { + type = "zfs"; + pool = "rpool"; + }; + }; + }; + }; + } + ) + allDevices + ); + + zpool = { + rpool = { + type = "zpool"; + mode = { + topology = { + type = "topology"; + vdev = + builtins.map (vdev: { + mode = diskoPoolMode; + members = builtins.map (device: hashDisk device.device) vdev; + }) + config.storage.zfs.pool.vdevs; + cache = builtins.map (device: hashDisk device.device) config.storage.zfs.pool.cache; + }; + }; + + options = { + ashift = "12"; + autotrim = "on"; + }; + + rootFsOptions = + { + canmount = "off"; + mountpoint = "none"; + xattr = "sa"; + acltype = "posixacl"; + relatime = "on"; + compression = "lz4"; + "com.sun:auto-snapshot" = "false"; + } + // (lib.attrsets.optionalAttrs config.storage.zfs.pool.encryption.enable { + encryption = "on"; + keyformat = config.storage.zfs.pool.encryption.keyformat; + keylocation = config.storage.zfs.pool.encryption.keylocation; + }); + + datasets = convertedDatasets; + }; + }; + }; + } + (lib.mkIf config.storage.zfs.notifications.enable { + programs.msmtp = { + enable = true; + setSendmail = true; + defaults = { + aliases = "/etc/aliases"; + port = config.storage.zfs.notifications.port; + tls_trust_file = "/etc/ssl/certs/ca-certificates.crt"; + tls = "on"; + auth = "login"; + tls_starttls = "off"; + }; + accounts = { + zfs_notifications = { + auth = true; + tls = true; + host = config.storage.zfs.notifications.host; + passwordeval = "cat ${config.storage.zfs.notifications.tokenFile}"; + user = config.storage.zfs.notifications.user; + from = config.storage.zfs.notifications.user; + }; + }; + }; + + services.zfs = { + zed = { + enableMail = true; + + settings = { + ZED_DEBUG_LOG = "/tmp/zed.debug.log"; + ZED_EMAIL_ADDR = [config.storage.zfs.notifications.to]; + ZED_EMAIL_PROG = "${pkgs.msmtp}/bin/msmtp"; + ZED_EMAIL_OPTS = "-a zfs_notifications @ADDRESS@"; + + ZED_NOTIFY_INTERVAL_SECS = 3600; + ZED_NOTIFY_VERBOSE = true; + + ZED_USE_ENCLOSURE_LEDS = true; + ZED_SCRUB_AFTER_RESILVER = true; + }; + }; + }; + }) + ]); + }; +} diff --git a/modules/nixos/system.nix b/modules/nixos/system.nix new file mode 100644 index 0000000..9d863bb --- /dev/null +++ b/modules/nixos/system.nix @@ -0,0 +1,15 @@ +{...}: { + flake.nixosModules.nixos-system = {...}: { + nix = { + gc = { + automatic = true; + dates = "weekly"; + options = "--delete-older-than 7d"; + }; + optimise = { + automatic = true; + dates = ["weekly"]; + }; + }; + }; +} diff --git a/modules/nixos/users.nix b/modules/nixos/users.nix new file mode 100644 index 0000000..785d3c8 --- /dev/null +++ b/modules/nixos/users.nix @@ -0,0 +1,434 @@ +{...}: { + flake.nixosModules.nixos-users = { + lib, + config, + inputs, + ... + }: let + SOPS_AGE_KEY_DIRECTORY = import ../../const/sops_age_key_directory.nix; + + host = config.host; + + principleUsers = host.principleUsers; + terminalUsers = host.terminalUsers; + normalUsers = host.normalUsers; + + uids = { + leyla = 1000; + eve = 1002; + # ester = 1003; + # ivy = 1004; + jellyfin = 2000; + forgejo = 2002; + hass = 2004; + syncthing = 2007; + # ollama = 2008; + git = 2009; + immich = 2010; + qbittorrent = 2011; + paperless = 2012; + actual = 2013; + radarr = 2014; + sonarr = 2015; + bazarr = 2016; + lidarr = 2017; + crab-hole = 2018; + }; + + gids = { + leyla = 1000; + eve = 1002; + # ester = 1003 + # ivy = 1004; + users = 100; + jellyfin_media = 2001; + jellyfin = 2000; + forgejo = 2002; + hass = 2004; + syncthing = 2007; + # ollama = 2008; + git = 2009; + immich = 2010; + qbittorrent = 2011; + paperless = 2012; + actual = 2013; + radarr = 2014; + sonarr = 2015; + bazarr = 2016; + lidarr = 2017; + crab-hole = 2018; + }; + + users = config.users.users; + leyla = users.leyla.name; + eve = users.eve.name; + in { + config = lib.mkMerge [ + { + # principle users are by definition trusted + nix.settings.trusted-users = builtins.map (user: user.name) principleUsers; + + # we should only be able to ssh into principle users of a computer who are also set up for terminal access + services.openssh.settings.AllowUsers = builtins.map (user: user.name) (lib.lists.intersectLists terminalUsers principleUsers); + + # we need to set up env variables to nix can find keys to decrypt passwords on rebuild + environment = { + sessionVariables = { + SOPS_AGE_KEY_DIRECTORY = SOPS_AGE_KEY_DIRECTORY; + SOPS_AGE_KEY_FILE = "${SOPS_AGE_KEY_DIRECTORY}/key.txt"; + }; + }; + + # set up user passwords + sops = { + defaultSopsFormat = "yaml"; + gnupg.sshKeyPaths = []; + + age = { + keyFile = "/var/lib/sops-nix/key.txt"; + sshKeyPaths = []; + # generateKey = true; + }; + + secrets = { + "passwords/leyla" = { + neededForUsers = true; + sopsFile = "${inputs.secrets}/user-passwords.yaml"; + }; + "passwords/eve" = { + neededForUsers = true; + sopsFile = "${inputs.secrets}/user-passwords.yaml"; + }; + }; + }; + + users = { + mutableUsers = false; + users = { + leyla = { + uid = lib.mkForce uids.leyla; + name = lib.mkForce host.users.leyla.name; + description = "Leyla"; + extraGroups = + (lib.lists.optionals host.users.leyla.isNormalUser ["networkmanager"]) + ++ (lib.lists.optionals host.users.leyla.isPrincipleUser ["wheel" "dialout" "docker"]) + ++ (lib.lists.optionals host.users.leyla.isDesktopUser ["adbusers"]); + hashedPasswordFile = config.sops.secrets."passwords/leyla".path; + isNormalUser = host.users.leyla.isNormalUser; + isSystemUser = !host.users.leyla.isNormalUser; + group = config.users.users.leyla.name; + }; + + eve = { + uid = lib.mkForce uids.eve; + name = lib.mkForce host.users.eve.name; + description = "Eve"; + extraGroups = + lib.optionals host.users.eve.isNormalUser ["networkmanager"] + ++ (lib.lists.optionals host.users.eve.isPrincipleUser ["wheel"]); + hashedPasswordFile = config.sops.secrets."passwords/eve".path; + isNormalUser = host.users.eve.isNormalUser; + isSystemUser = !host.users.eve.isNormalUser; + group = config.users.users.eve.name; + }; + + jellyfin = { + uid = lib.mkForce uids.jellyfin; + isSystemUser = true; + group = config.users.users.jellyfin.name; + }; + + forgejo = { + uid = lib.mkForce uids.forgejo; + isSystemUser = true; + group = config.users.users.forgejo.name; + }; + + hass = { + uid = lib.mkForce uids.hass; + isSystemUser = true; + group = config.users.users.hass.name; + }; + + syncthing = { + uid = lib.mkForce uids.syncthing; + isSystemUser = true; + group = config.users.users.syncthing.name; + }; + + # ollama = { + # uid = lib.mkForce uids.ollama; + # isSystemUser = true; + # group = config.users.users.ollama.name; + # }; + + git = { + uid = lib.mkForce uids.git; + isSystemUser = !config.services.forgejo.enable; + isNormalUser = config.services.forgejo.enable; + group = config.users.users.git.name; + }; + + immich = { + uid = lib.mkForce uids.immich; + isSystemUser = true; + group = config.users.users.immich.name; + }; + + qbittorrent = { + uid = lib.mkForce uids.qbittorrent; + isSystemUser = true; + group = config.users.users.qbittorrent.name; + }; + + paperless = { + uid = lib.mkForce uids.paperless; + isSystemUser = true; + group = config.users.users.paperless.name; + }; + + actual = { + uid = lib.mkForce uids.actual; + isSystemUser = true; + group = config.users.users.actual.name; + }; + + radarr = { + uid = lib.mkForce uids.radarr; + isSystemUser = true; + group = config.users.users.radarr.name; + }; + + sonarr = { + uid = lib.mkForce uids.sonarr; + isSystemUser = true; + group = config.users.users.sonarr.name; + }; + + bazarr = { + uid = lib.mkForce uids.bazarr; + isSystemUser = true; + group = config.users.users.bazarr.name; + }; + + lidarr = { + uid = lib.mkForce uids.lidarr; + isSystemUser = true; + group = config.users.users.lidarr.name; + }; + + crab-hole = { + uid = lib.mkForce uids.crab-hole; + isSystemUser = true; + group = config.users.users.crab-hole.name; + }; + }; + + groups = { + leyla = { + gid = lib.mkForce gids.leyla; + members = [ + leyla + ]; + }; + + eve = { + gid = lib.mkForce gids.eve; + members = [ + eve + ]; + }; + + users = { + gid = lib.mkForce gids.users; + members = [ + leyla + eve + ]; + }; + + jellyfin_media = { + gid = lib.mkForce gids.jellyfin_media; + members = [ + users.jellyfin.name + users.radarr.name + users.sonarr.name + users.bazarr.name + users.lidarr.name + leyla + eve + ]; + }; + + jellyfin = { + gid = lib.mkForce gids.jellyfin; + members = [ + users.jellyfin.name + # leyla + ]; + }; + + forgejo = { + gid = lib.mkForce gids.forgejo; + members = [ + users.forgejo.name + # leyla + ]; + }; + + hass = { + gid = lib.mkForce gids.hass; + members = [ + users.hass.name + # leyla + ]; + }; + + syncthing = { + gid = lib.mkForce gids.syncthing; + members = [ + users.syncthing.name + leyla + eve + ]; + }; + + # ollama = { + # gid = lib.mkForce gids.ollama; + # members = [ + # users.ollama.name + # ]; + # }; + + git = { + gid = lib.mkForce gids.git; + members = [ + users.git.name + ]; + }; + + immich = { + gid = lib.mkForce gids.immich; + members = [ + users.immich.name + # leyla + ]; + }; + + qbittorrent = { + gid = lib.mkForce gids.qbittorrent; + members = [ + users.qbittorrent.name + leyla + ]; + }; + + paperless = { + gid = lib.mkForce gids.paperless; + members = [ + users.paperless.name + ]; + }; + + actual = { + gid = lib.mkForce gids.actual; + members = [ + users.actual.name + ]; + }; + + radarr = { + gid = lib.mkForce gids.radarr; + members = [ + users.radarr.name + ]; + }; + + sonarr = { + gid = lib.mkForce gids.sonarr; + members = [ + users.sonarr.name + ]; + }; + + bazarr = { + gid = lib.mkForce gids.bazarr; + members = [ + users.bazarr.name + ]; + }; + + lidarr = { + gid = lib.mkForce gids.lidarr; + members = [ + users.lidarr.name + ]; + }; + + crab-hole = { + gid = lib.mkForce gids.crab-hole; + members = [ + users.crab-hole.name + ]; + }; + }; + }; + } + (lib.mkIf config.storage.zfs.enable (lib.mkMerge [ + { + # sops age key needs to be available to pre persist for user generation + storage.datasets.local."system/sops" = { + type = "zfs_fs"; + mount = SOPS_AGE_KEY_DIRECTORY; + atime = "off"; + relatime = "off"; + impermanence.enable = false; + }; + } + (lib.mkIf (!config.storage.impermanence.enable) { + storage.datasets.replicate = lib.mkMerge ( + builtins.map (user: { + "home/${user.name}" = { + type = "zfs_fs"; + mount = "/home/${user.name}"; + snapshot.autoSnapshot = true; + }; + }) + normalUsers + ); + }) + (lib.mkIf config.storage.impermanence.enable { + storage.datasets.ephemeral = lib.mkMerge ( + builtins.map (user: { + "home/${user.name}" = { + type = "zfs_fs"; + mount = "/home/${user.name}"; + snapshot.blankSnapshot = true; + }; + }) + normalUsers + ); + + # Post resume commands to rollback user home datasets to blank snapshots + # Only add these when generateBase is true -- when false, the legacy + # storage config is responsible for providing rollback commands with + # the correct (old) dataset paths. + boot.initrd.postResumeCommands = lib.mkIf config.storage.generateBase (lib.mkAfter ( + lib.strings.concatLines (builtins.map (user: "zfs rollback -r rpool/ephemeral/home/${user.name}@blank") + normalUsers) + )); + + # TODO: I don't think we need this anymore but I have not tested it + # Create persist home directories with proper permissions + # systemd = { + # tmpfiles.rules = + # builtins.map ( + # user: "d /persist/replicate/home/${user.name} 700 ${user.name} ${user.name} -" + # ) + # normalUsers; + # }; + }) + ])) + ]; + }; +} diff --git a/modules/parts.nix b/modules/parts.nix index 6067651..16228c4 100644 --- a/modules/parts.nix +++ b/modules/parts.nix @@ -51,7 +51,7 @@ in { home-manager.nixosModules.home-manager disko.nixosModules.disko # lix-module.nixosModules.default - ../legacy-modules/nixos-modules + config.flake.nixosModules.nixos-modules-all ({ lib, config,