diff --git a/configurations/home-manager/leyla/default.nix b/configurations/home-manager/leyla/default.nix index 8a37754..20b04c7 100644 --- a/configurations/home-manager/leyla/default.nix +++ b/configurations/home-manager/leyla/default.nix @@ -12,7 +12,7 @@ ]; config = { - impermanence.enable = osConfig.host.impermanence.enable; + impermanence.enable = osConfig.storage.impermanence.enable; # Home Manager needs a bit of information about you and the paths it should # manage. diff --git a/configurations/home-manager/leyla/impermanence.nix b/configurations/home-manager/leyla/impermanence.nix index ce81c81..4a58cbb 100644 --- a/configurations/home-manager/leyla/impermanence.nix +++ b/configurations/home-manager/leyla/impermanence.nix @@ -4,7 +4,7 @@ ... }: { config = lib.mkIf (config.impermanence.enable) { - home.persistence."/persist/home/leyla" = { + home.persistence."/persist/replicate/home" = { directories = [ "desktop" "downloads" @@ -14,7 +14,6 @@ ".bash_history" # keep shell history around "${config.xdg.dataHome}/recently-used.xbel" # gnome recently viewed files ]; - allowOther = true; }; }; } diff --git a/configurations/nixos/defiant/configuration.nix b/configurations/nixos/defiant/configuration.nix index e2f9401..182b8c0 100644 --- a/configurations/nixos/defiant/configuration.nix +++ b/configurations/nixos/defiant/configuration.nix @@ -33,44 +33,6 @@ isPrincipleUser = true; }; }; - impermanence.enable = true; - storage = { - enable = true; - encryption = true; - notifications = { - enable = true; - host = "smtp.protonmail.ch"; - port = 587; - to = "leyla@jan-leila.com"; - user = "noreply@jan-leila.com"; - tokenFile = config.sops.secrets."services/zfs_smtp_token".path; - }; - pool = { - # We are having to boot off of the nvm cache drive because I cant figure out how to boot via the HBA - bootDrives = ["nvme-Samsung_SSD_990_PRO_4TB_S7KGNU0X907881F"]; - vdevs = [ - [ - "ata-ST18000NE000-3G6101_ZVTCXVEB" - "ata-ST18000NE000-3G6101_ZVTCXWSC" - "ata-ST18000NE000-3G6101_ZVTD10EH" - "ata-ST18000NT001-3NF101_ZVTE0S3Q" - "ata-ST18000NT001-3NF101_ZVTEF27J" - "ata-ST18000NE000-3G6101_ZVTJ7359" - ] - [ - "ata-ST4000NE001-2MA101_WS2275P3" - "ata-ST4000NE001-2MA101_WS227B9F" - "ata-ST4000NE001-2MA101_WS227CEW" - "ata-ST4000NE001-2MA101_WS227CYN" - "ata-ST4000NE001-2MA101_WS23TBWV" - "ata-ST4000NE001-2MA101_WS23TC5F" - ] - ]; - cache = [ - "nvme-Samsung_SSD_990_PRO_4TB_S7KGNU0X907881F" - ]; - }; - }; network_storage = { enable = true; directories = [ @@ -104,6 +66,53 @@ }; }; + storage = { + zfs = { + enable = true; + notifications = { + enable = true; + host = "smtp.protonmail.ch"; + port = 587; + to = "leyla@jan-leila.com"; + user = "noreply@jan-leila.com"; + tokenFile = config.sops.secrets."services/zfs_smtp_token".path; + }; + pool = { + encryption = { + enable = true; + }; + vdevs = [ + [ + "ata-ST18000NE000-3G6101_ZVTCXVEB" + "ata-ST18000NE000-3G6101_ZVTCXWSC" + "ata-ST18000NE000-3G6101_ZVTD10EH" + "ata-ST18000NT001-3NF101_ZVTE0S3Q" + "ata-ST18000NT001-3NF101_ZVTEF27J" + "ata-ST18000NE000-3G6101_ZVTJ7359" + ] + [ + "ata-ST4000NE001-2MA101_WS2275P3" + "ata-ST4000NE001-2MA101_WS227B9F" + "ata-ST4000NE001-2MA101_WS227CEW" + "ata-ST4000NE001-2MA101_WS227CYN" + "ata-ST4000NE001-2MA101_WS23TBWV" + "ata-ST4000NE001-2MA101_WS23TC5F" + ] + ]; + # We are having to boot off of the nvm cache drive because I cant figure out how to boot via the HBA + cache = [ + { + device = "nvme-Samsung_SSD_990_PRO_4TB_S7KGNU0X907881F"; + boot = true; + } + ]; + }; + }; + impermanence = { + enable = true; + }; + }; + systemd.network = { enable = true; diff --git a/configurations/nixos/emergent/configuration.nix b/configurations/nixos/emergent/configuration.nix index 6121069..781d66b 100644 --- a/configurations/nixos/emergent/configuration.nix +++ b/configurations/nixos/emergent/configuration.nix @@ -59,12 +59,22 @@ hardware = { piperMouse.enable = true; }; + }; - storage = { + storage = { + zfs = { enable = true; pool = { - mode = ""; - drives = ["wwn-0x5000039fd0cf05eb"]; + mode = "stripe"; + vdevs = [ + [ + { + device = "wwn-0x5000039fd0cf05eb"; + boot = true; + } + ] + ]; + cache = []; }; }; }; diff --git a/flake.lock b/flake.lock index a3c552f..2573f79 100644 --- a/flake.lock +++ b/flake.lock @@ -147,16 +147,25 @@ } }, "impermanence": { + "inputs": { + "home-manager": [ + "home-manager" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, "locked": { - "lastModified": 1737831083, - "narHash": "sha256-LJggUHbpyeDvNagTUrdhe/pRVp4pnS6wVKALS782gRI=", - "owner": "nix-community", + "lastModified": 1762761176, + "narHash": "sha256-i3gM8fUozQrgZIbwVNlTuhLqPSl56zxAYpsQpQ9Lhro=", + "owner": "jan-leila", "repo": "impermanence", - "rev": "4b3e914cdf97a5b536a889e939fb2fd2b043a170", + "rev": "ffbe1ca47cf4b3008c3aa5c49cdae294d8c8058a", "type": "github" }, "original": { - "owner": "nix-community", + "owner": "jan-leila", + "ref": "home-manager-v2", "repo": "impermanence", "type": "github" } diff --git a/flake.nix b/flake.nix index e935688..2f598db 100644 --- a/flake.nix +++ b/flake.nix @@ -36,7 +36,9 @@ # delete your darlings impermanence = { - url = "github:nix-community/impermanence"; + url = "github:jan-leila/impermanence/home-manager-v2"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.home-manager.follows = "home-manager"; }; nix-darwin = { diff --git a/modules/home-manager-modules/impermanence.nix b/modules/home-manager-modules/impermanence.nix index 6c75edd..f5e9869 100644 --- a/modules/home-manager-modules/impermanence.nix +++ b/modules/home-manager-modules/impermanence.nix @@ -18,17 +18,16 @@ in { (lib.mkIf config.impermanence.enable { assertions = [ { - assertion = osConfig.host.impermanence.enable; + assertion = osConfig.storage.impermanence.enable; message = "impermanence can not be enabled for a user when it is not enabled for the system"; } ]; }) # If impermanence is not enabled for this user but system impermanence is enabled, # persist the entire home directory as fallback - (lib.mkIf (osConfig.host.impermanence.enable && !cfg.enable && cfg.fallbackPersistence.enable) { - home.persistence."/persist/home/${config.home.username}" = { + (lib.mkIf (osConfig.storage.impermanence.enable && !cfg.enable && cfg.fallbackPersistence.enable) { + home.persistence."/persist/replicate/home" = { directories = ["."]; - allowOther = true; }; }) ]; diff --git a/modules/home-manager-modules/openssh.nix b/modules/home-manager-modules/openssh.nix index afc98dd..3e723c9 100644 --- a/modules/home-manager-modules/openssh.nix +++ b/modules/home-manager-modules/openssh.nix @@ -96,7 +96,7 @@ } ) (lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { files = lib.lists.flatten ( builtins.map (hostKey: [".ssh/${hostKey.path}" ".ssh/${hostKey.path}.pub"]) config.programs.openssh.hostKeys ); diff --git a/modules/home-manager-modules/programs/anki.nix b/modules/home-manager-modules/programs/anki.nix index c2f93ea..c54feac 100644 --- a/modules/home-manager-modules/programs/anki.nix +++ b/modules/home-manager-modules/programs/anki.nix @@ -1,15 +1,13 @@ { lib, config, - osConfig, ... }: { - config = lib.mkIf (config.programs.anki.enable && osConfig.host.impermanence.enable) { - home.persistence."/persist${config.home.homeDirectory}" = { + config = lib.mkIf (config.programs.anki.enable && config.impermanence.enable) { + home.persistence."/persist/replicate/home" = { directories = [ - "${config.xdg.dataHome}/Anki2/" + ".local/share/Anki2" ]; - allowOther = true; }; }; } diff --git a/modules/home-manager-modules/programs/bitwarden.nix b/modules/home-manager-modules/programs/bitwarden.nix index e305b6c..ade24b6 100644 --- a/modules/home-manager-modules/programs/bitwarden.nix +++ b/modules/home-manager-modules/programs/bitwarden.nix @@ -16,11 +16,10 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/Bitwarden" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/bruno.nix b/modules/home-manager-modules/programs/bruno.nix index 8ad5e63..ced1998 100644 --- a/modules/home-manager-modules/programs/bruno.nix +++ b/modules/home-manager-modules/programs/bruno.nix @@ -16,11 +16,10 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/bruno/" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/calibre.nix b/modules/home-manager-modules/programs/calibre.nix index dbe6e2b..74a0cf4 100644 --- a/modules/home-manager-modules/programs/calibre.nix +++ b/modules/home-manager-modules/programs/calibre.nix @@ -16,11 +16,10 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/calibre" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/davinci-resolve.nix b/modules/home-manager-modules/programs/davinci-resolve.nix index 6c4526f..c17c8b0 100644 --- a/modules/home-manager-modules/programs/davinci-resolve.nix +++ b/modules/home-manager-modules/programs/davinci-resolve.nix @@ -16,12 +16,11 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.dataHome}/DaVinciResolve" "${config.xdg.configHome}/blackmagic" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/dbeaver.nix b/modules/home-manager-modules/programs/dbeaver.nix index 8b6c41a..f509646 100644 --- a/modules/home-manager-modules/programs/dbeaver.nix +++ b/modules/home-manager-modules/programs/dbeaver.nix @@ -16,11 +16,10 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.dataHome}/DBeaverData/" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/discord.nix b/modules/home-manager-modules/programs/discord.nix index 71b09b2..0b0588e 100644 --- a/modules/home-manager-modules/programs/discord.nix +++ b/modules/home-manager-modules/programs/discord.nix @@ -6,11 +6,10 @@ config = lib.mkIf config.programs.discord.enable (lib.mkMerge [ ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/discord/" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/firefox.nix b/modules/home-manager-modules/programs/firefox.nix index 8841887..e100200 100644 --- a/modules/home-manager-modules/programs/firefox.nix +++ b/modules/home-manager-modules/programs/firefox.nix @@ -22,11 +22,10 @@ # Extension configuration ".mozilla/firefox/${profile}/extension-settings.json" ]; - allowOther = true; }; in { config = lib.mkIf (config.programs.firefox.enable && config.impermanence.enable) { - home.persistence."/persist${config.home.homeDirectory}" = lib.mkMerge ( + home.persistence."/persist/replicate/home" = lib.mkMerge ( ( lib.attrsets.mapAttrsToList (profile: _: buildProfilePersistence profile) diff --git a/modules/home-manager-modules/programs/freecad.nix b/modules/home-manager-modules/programs/freecad.nix index 89668de..19e08fa 100644 --- a/modules/home-manager-modules/programs/freecad.nix +++ b/modules/home-manager-modules/programs/freecad.nix @@ -16,11 +16,10 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/FreeCAD" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/gimp.nix b/modules/home-manager-modules/programs/gimp.nix index 925a2d9..fbe4471 100644 --- a/modules/home-manager-modules/programs/gimp.nix +++ b/modules/home-manager-modules/programs/gimp.nix @@ -16,11 +16,10 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/GIMP" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/idea.nix b/modules/home-manager-modules/programs/idea.nix index e59e7b2..ec9d7d6 100644 --- a/modules/home-manager-modules/programs/idea.nix +++ b/modules/home-manager-modules/programs/idea.nix @@ -16,7 +16,7 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ # configuration "${config.xdg.configHome}/JetBrains/" diff --git a/modules/home-manager-modules/programs/inkscape.nix b/modules/home-manager-modules/programs/inkscape.nix index a26ddec..67e5f80 100644 --- a/modules/home-manager-modules/programs/inkscape.nix +++ b/modules/home-manager-modules/programs/inkscape.nix @@ -16,11 +16,10 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/inkscape" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/kdenlive.nix b/modules/home-manager-modules/programs/kdenlive.nix index 05327d1..2bec5b3 100644 --- a/modules/home-manager-modules/programs/kdenlive.nix +++ b/modules/home-manager-modules/programs/kdenlive.nix @@ -23,12 +23,11 @@ in { } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/kdenliverc" "${config.xdg.dataHome}/kdenlive" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/krita.nix b/modules/home-manager-modules/programs/krita.nix index 3ba5560..88d1de9 100644 --- a/modules/home-manager-modules/programs/krita.nix +++ b/modules/home-manager-modules/programs/krita.nix @@ -16,12 +16,11 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/kritarc" "${config.xdg.dataHome}/krita" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/libreoffice.nix b/modules/home-manager-modules/programs/libreoffice.nix index 93163e7..9c3537f 100644 --- a/modules/home-manager-modules/programs/libreoffice.nix +++ b/modules/home-manager-modules/programs/libreoffice.nix @@ -16,11 +16,10 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/libreoffice" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/makemkv.nix b/modules/home-manager-modules/programs/makemkv.nix index e92c3d3..9fcde8b 100644 --- a/modules/home-manager-modules/programs/makemkv.nix +++ b/modules/home-manager-modules/programs/makemkv.nix @@ -30,7 +30,7 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ ".MakeMKV" ]; diff --git a/modules/home-manager-modules/programs/mapillary-uploader.nix b/modules/home-manager-modules/programs/mapillary-uploader.nix index df1f093..09894c9 100644 --- a/modules/home-manager-modules/programs/mapillary-uploader.nix +++ b/modules/home-manager-modules/programs/mapillary-uploader.nix @@ -17,12 +17,11 @@ in { } ( mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/mapillary-uploader" "${config.xdg.dataHome}/mapillary-uploader" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/obs.nix b/modules/home-manager-modules/programs/obs.nix index bfdba90..3a099f7 100644 --- a/modules/home-manager-modules/programs/obs.nix +++ b/modules/home-manager-modules/programs/obs.nix @@ -6,11 +6,10 @@ config = lib.mkIf config.programs.obs-studio.enable (lib.mkMerge [ ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/obs-studio" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/obsidian.nix b/modules/home-manager-modules/programs/obsidian.nix index 824563d..e07beab 100644 --- a/modules/home-manager-modules/programs/obsidian.nix +++ b/modules/home-manager-modules/programs/obsidian.nix @@ -6,7 +6,7 @@ config = lib.mkIf config.programs.obsidian.enable (lib.mkMerge [ ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/obsidian" ]; diff --git a/modules/home-manager-modules/programs/olympus.nix b/modules/home-manager-modules/programs/olympus.nix index 0e38eec..3223d62 100644 --- a/modules/home-manager-modules/programs/olympus.nix +++ b/modules/home-manager-modules/programs/olympus.nix @@ -23,12 +23,11 @@ in { } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/olympus" "${config.xdg.dataHome}/olympus" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/openrgb.nix b/modules/home-manager-modules/programs/openrgb.nix index c9d5e14..64d6229 100644 --- a/modules/home-manager-modules/programs/openrgb.nix +++ b/modules/home-manager-modules/programs/openrgb.nix @@ -16,11 +16,10 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/OpenRGB" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/picard.nix b/modules/home-manager-modules/programs/picard.nix index bc37b86..5d197f8 100644 --- a/modules/home-manager-modules/programs/picard.nix +++ b/modules/home-manager-modules/programs/picard.nix @@ -16,11 +16,10 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/MusicBrainz" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/prostudiomasters.nix b/modules/home-manager-modules/programs/prostudiomasters.nix index 5345169..5256f26 100644 --- a/modules/home-manager-modules/programs/prostudiomasters.nix +++ b/modules/home-manager-modules/programs/prostudiomasters.nix @@ -16,7 +16,7 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/ProStudioMasters" ]; diff --git a/modules/home-manager-modules/programs/protonvpn.nix b/modules/home-manager-modules/programs/protonvpn.nix index 513a610..57e50ab 100644 --- a/modules/home-manager-modules/programs/protonvpn.nix +++ b/modules/home-manager-modules/programs/protonvpn.nix @@ -16,7 +16,7 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/protonvpn" "${config.xdg.configHome}/Proton" diff --git a/modules/home-manager-modules/programs/qbittorrent.nix b/modules/home-manager-modules/programs/qbittorrent.nix index 61d13c0..ee098e0 100644 --- a/modules/home-manager-modules/programs/qbittorrent.nix +++ b/modules/home-manager-modules/programs/qbittorrent.nix @@ -16,7 +16,7 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/qBittorrent" ]; diff --git a/modules/home-manager-modules/programs/qflipper.nix b/modules/home-manager-modules/programs/qflipper.nix index 8b42766..0c7d242 100644 --- a/modules/home-manager-modules/programs/qflipper.nix +++ b/modules/home-manager-modules/programs/qflipper.nix @@ -16,11 +16,10 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/qFlipper" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/signal.nix b/modules/home-manager-modules/programs/signal.nix index 7db23a7..962a139 100644 --- a/modules/home-manager-modules/programs/signal.nix +++ b/modules/home-manager-modules/programs/signal.nix @@ -16,7 +16,7 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/Signal" ]; diff --git a/modules/home-manager-modules/programs/steam.nix b/modules/home-manager-modules/programs/steam.nix index fd98cb6..3dd6504 100644 --- a/modules/home-manager-modules/programs/steam.nix +++ b/modules/home-manager-modules/programs/steam.nix @@ -18,14 +18,13 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ { directory = "${config.xdg.dataHome}/Steam"; method = "symlink"; } ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/tor-browser.nix b/modules/home-manager-modules/programs/tor-browser.nix index c3b085d..92484ae 100644 --- a/modules/home-manager-modules/programs/tor-browser.nix +++ b/modules/home-manager-modules/programs/tor-browser.nix @@ -16,11 +16,10 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.dataHome}/torbrowser" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/ungoogled-chromium.nix b/modules/home-manager-modules/programs/ungoogled-chromium.nix index ef6a881..e76eeeb 100644 --- a/modules/home-manager-modules/programs/ungoogled-chromium.nix +++ b/modules/home-manager-modules/programs/ungoogled-chromium.nix @@ -16,11 +16,10 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/chromium" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/via.nix b/modules/home-manager-modules/programs/via.nix index 0aa58e4..3a638aa 100644 --- a/modules/home-manager-modules/programs/via.nix +++ b/modules/home-manager-modules/programs/via.nix @@ -16,12 +16,11 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ "${config.xdg.configHome}/via" "${config.xdg.dataHome}/via" ]; - allowOther = true; }; } ) diff --git a/modules/home-manager-modules/programs/vmware-workstation.nix b/modules/home-manager-modules/programs/vmware-workstation.nix index 8e9d406..277e4bd 100644 --- a/modules/home-manager-modules/programs/vmware-workstation.nix +++ b/modules/home-manager-modules/programs/vmware-workstation.nix @@ -17,7 +17,7 @@ } ( lib.mkIf config.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { + home.persistence."/persist/replicate/home" = { directories = [ { directory = ".vmware"; @@ -28,7 +28,6 @@ method = "symlink"; } ]; - allowOther = true; }; } ) diff --git a/modules/nixos-modules/default.nix b/modules/nixos-modules/default.nix index 2ba1a58..34e041e 100644 --- a/modules/nixos-modules/default.nix +++ b/modules/nixos-modules/default.nix @@ -8,14 +8,13 @@ ./desktop.nix ./ssh.nix ./i18n.nix - ./sync.nix - ./impermanence.nix - ./disko.nix - ./ollama.nix + ./sync + ./ollama ./ai.nix - ./tailscale.nix + ./tailscale ./steam.nix ./server + ./storage ]; nixpkgs.config.permittedInsecurePackages = [ diff --git a/modules/nixos-modules/disko.nix b/modules/nixos-modules/disko.nix deleted file mode 100644 index a962689..0000000 --- a/modules/nixos-modules/disko.nix +++ /dev/null @@ -1,267 +0,0 @@ -{ - lib, - pkgs, - config, - inputs, - ... -}: let - # there currently is a bug with disko that causes long disk names to be generated improperly this hash function should alleviate it when used for disk names instead of what we are defaulting to - # max gpt length is 36 and disk adds formats it like disk-xxxx-zfs which means we need to be 9 characters under that - hashDisk = drive: (builtins.substring 0 27 (builtins.hashString "sha256" drive)); - - vdevs = - builtins.map ( - disks: - builtins.map (disk: lib.attrsets.nameValuePair (hashDisk disk) disk) disks - ) - config.host.storage.pool.vdevs; - cache = - builtins.map ( - disk: lib.attrsets.nameValuePair (hashDisk disk) disk - ) - config.host.storage.pool.cache; - - datasets = config.host.storage.pool.datasets // config.host.storage.pool.extraDatasets; -in { - options.host.storage = { - enable = lib.mkEnableOption "are we going create zfs disks with disko on this device"; - encryption = lib.mkEnableOption "is the vdev going to be encrypted"; - 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 = { - mode = lib.mkOption { - type = lib.types.str; - default = "raidz2"; - description = "what level of redundancy should this pool have"; - }; - # list of drives in pool that will have a boot partition put onto them - bootDrives = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "list of disks that are going to have a boot partition installed on them"; - default = lib.lists.flatten config.host.storage.pool.vdevs; - }; - # shorthand for vdevs if you only have 1 vdev - drives = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "list of drives that are going to be in the vdev"; - default = []; - }; - # list of all drives in each vdev - vdevs = lib.mkOption { - type = lib.types.listOf (lib.types.listOf lib.types.str); - description = "list of disks that are going to be in"; - default = [config.host.storage.pool.drives]; - }; - # list of cache drives for pool - cache = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "list of drives that are going to be used as cache"; - default = []; - }; - # Default datasets that are needed to make a functioning system - datasets = lib.mkOption { - type = lib.types.attrsOf (inputs.disko.lib.subType { - types = {inherit (inputs.disko.lib.types) zfs_fs zfs_volume;}; - }); - default = { - "local" = { - type = "zfs_fs"; - options.canmount = "off"; - }; - # nix directory needs to be available pre persist and doesn't need to be snapshotted or backed up - "local/system/nix" = { - type = "zfs_fs"; - mountpoint = "/nix"; - options = { - atime = "off"; - relatime = "off"; - canmount = "on"; - }; - }; - # dataset for root that gets rolled back on every boot - "local/system/root" = { - type = "zfs_fs"; - mountpoint = "/"; - options = { - canmount = "on"; - }; - postCreateHook = '' - zfs snapshot rpool/local/system/root@blank - ''; - }; - }; - }; - extraDatasets = lib.mkOption { - type = lib.types.attrsOf (inputs.disko.lib.subType { - types = {inherit (inputs.disko.lib.types) zfs_fs zfs_volume;}; - }); - description = "List of datasets to define"; - default = {}; - }; - }; - }; - - config = lib.mkIf config.host.storage.enable { - programs.msmtp = lib.mkIf config.host.storage.notifications.enable { - enable = true; - setSendmail = true; - defaults = { - aliases = "/etc/aliases"; - port = config.host.storage.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.host.storage.notifications.host; - passwordeval = "cat ${config.host.storage.notifications.tokenFile}"; - user = config.host.storage.notifications.user; - from = config.host.storage.notifications.user; - }; - }; - }; - - services.zfs = { - autoScrub.enable = true; - autoSnapshot.enable = true; - - zed = lib.mkIf config.host.storage.notifications.enable { - enableMail = true; - - settings = { - ZED_DEBUG_LOG = "/tmp/zed.debug.log"; - ZED_EMAIL_ADDR = [config.host.storage.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; - }; - }; - }; - - disko.devices = { - disk = ( - builtins.listToAttrs ( - builtins.map - (drive: - lib.attrsets.nameValuePair (drive.name) { - type = "disk"; - device = "/dev/disk/by-id/${drive.value}"; - content = { - type = "gpt"; - partitions = { - ESP = lib.mkIf (builtins.elem drive.value config.host.storage.pool.bootDrives) { - # The 2GB here for the boot partition might be a bit overkill we probably only need like 1/4th of that but storage is cheap - size = "2G"; - type = "EF00"; - content = { - type = "filesystem"; - format = "vfat"; - mountpoint = "/boot"; - mountOptions = ["umask=0077"]; - }; - }; - zfs = { - size = "100%"; - content = { - type = "zfs"; - pool = "rpool"; - }; - }; - }; - }; - }) - ( - (lib.lists.flatten vdevs) ++ cache - ) - ) - ); - zpool = { - rpool = { - type = "zpool"; - mode = { - topology = { - type = "topology"; - vdev = ( - builtins.map (disks: { - mode = config.host.storage.pool.mode; - members = - builtins.map (disk: disk.name) disks; - }) - vdevs - ); - cache = builtins.map (disk: disk.name) 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.host.storage.encryption { - encryption = "on"; - keyformat = "hex"; - keylocation = "prompt"; - } - ); - - datasets = lib.mkMerge [ - ( - lib.attrsets.mapAttrs (name: value: { - type = value.type; - options = value.options; - mountpoint = value.mountpoint; - postCreateHook = value.postCreateHook; - }) - datasets - ) - ]; - }; - }; - }; - }; -} diff --git a/modules/nixos-modules/impermanence.nix b/modules/nixos-modules/impermanence.nix deleted file mode 100644 index 60011cb..0000000 --- a/modules/nixos-modules/impermanence.nix +++ /dev/null @@ -1,134 +0,0 @@ -{ - config, - lib, - ... -}: { - # options.storage = { - # zfs = { - # # TODO: enable option - # # when this option is enabled we need to configure and enable disko things - - # # TODO: we need some way of managing notifications - - # # TODO: we need options to configure zfs pools - # # we should have warnings when the configured pool is missing drives - - # # TODO: dataset option that is a submodule that adds datasets to the system - # # warnings for when a dataset was created in the past on a system but it is now missing some of the options defined for it - - # # TODO: pools and datasets need to be passed to disko - # }; - - # impermanence = { - # # TODO: enable option - - # # TODO: datasets option that is a submodule that will be used to define what datasets to add to the storage system - # # We should by default create the `local`, `local/system/nix`, `local/system/root`, `persist` `persist/system/root`, and `persist/system/var/log` datasets - # # Then we should make a dataset for user folders local and persist - # # We should also create datasets for systemd modules that have have impermanence enabled for them - # # we need to figure out what options a dataset can have in zfs - # }; - - # # 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 - # }; - - options.host.impermanence.enable = lib.mkEnableOption "are we going to use impermanence on this device"; - - config = lib.mkMerge [ - { - assertions = [ - { - assertion = !(config.host.impermanence.enable && !config.host.storage.enable); - message = '' - Disko storage must be enabled to use impermanence. - ''; - } - ]; - } - ( - lib.mkIf config.host.impermanence.enable { - assertions = [ - { - assertion = config.host.impermanence.enable && config.host.storage.enable; - message = "Impermanence can not be used without managed host storage."; - } - ]; - - # fixes issues with /var/lib/private not having the correct permissions https://github.com/nix-community/impermanence/issues/254 - system.activationScripts."createPersistentStorageDirs".deps = ["var-lib-private-permissions" "users" "groups"]; - system.activationScripts = { - "var-lib-private-permissions" = { - deps = ["specialfs"]; - text = '' - mkdir -p /persist/system/root/var/lib/private - chmod 0700 /persist/system/root/var/lib/private - ''; - }; - }; - - programs.fuse.userAllowOther = true; - - boot.initrd.postResumeCommands = lib.mkAfter '' - zfs rollback -r rpool/local/system/root@blank - ''; - - fileSystems = { - "/".neededForBoot = true; - "/persist/system/root".neededForBoot = true; - "/persist/system/var/log".neededForBoot = true; - }; - - host.storage.pool.extraDatasets = { - # persist datasets are datasets that contain information that we would like to keep around - "persist" = { - type = "zfs_fs"; - options.canmount = "off"; - options = { - "com.sun:auto-snapshot" = "true"; - }; - }; - # this is where root data actually lives - "persist/system/root" = { - type = "zfs_fs"; - mountpoint = "/persist/system/root"; - }; - "persist/system/var/log" = { - type = "zfs_fs"; - mountpoint = "/persist/system/var/log"; - # logs should be append only so we shouldn't need to snapshot them - options = { - "com.sun:auto-snapshot" = "false"; - }; - }; - }; - - environment.persistence."/persist/system/var/log" = { - enable = true; - hideMounts = true; - directories = [ - "/var/log" - ]; - }; - - environment.persistence."/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - "/var/lib/nixos" - "/var/lib/systemd/coredump" - ]; - files = [ - "/etc/machine-id" - ]; - }; - - # TODO: this should live in leylas home manager configuration - security.sudo.extraConfig = "Defaults lecture=never"; - } - ) - ]; -} diff --git a/modules/nixos-modules/ollama/default.nix b/modules/nixos-modules/ollama/default.nix new file mode 100644 index 0000000..896526a --- /dev/null +++ b/modules/nixos-modules/ollama/default.nix @@ -0,0 +1,6 @@ +{...}: { + imports = [ + ./ollama.nix + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/ollama.nix b/modules/nixos-modules/ollama/ollama.nix similarity index 63% rename from modules/nixos-modules/ollama.nix rename to modules/nixos-modules/ollama/ollama.nix index 99819bf..dc7cdd9 100644 --- a/modules/nixos-modules/ollama.nix +++ b/modules/nixos-modules/ollama/ollama.nix @@ -27,20 +27,6 @@ allowedUDPPorts = ports; }; })) - (lib.mkIf config.host.impermanence.enable { - environment.persistence."/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = "/var/lib/private/ollama"; - user = config.services.ollama.user; - group = config.services.ollama.group; - mode = "0700"; - } - ]; - }; - }) ] ); } diff --git a/modules/nixos-modules/ollama/storage.nix b/modules/nixos-modules/ollama/storage.nix new file mode 100644 index 0000000..6ab0fc8 --- /dev/null +++ b/modules/nixos-modules/ollama/storage.nix @@ -0,0 +1,37 @@ +{ + 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/modules/nixos-modules/server/actual/default.nix b/modules/nixos-modules/server/actual/default.nix index b59517b..99778af 100644 --- a/modules/nixos-modules/server/actual/default.nix +++ b/modules/nixos-modules/server/actual/default.nix @@ -3,6 +3,6 @@ ./actual.nix ./proxy.nix ./fail2ban.nix - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/actual/impermanence.nix b/modules/nixos-modules/server/actual/impermanence.nix deleted file mode 100644 index d870789..0000000 --- a/modules/nixos-modules/server/actual/impermanence.nix +++ /dev/null @@ -1,37 +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.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.actual.impermanence.enable { - assertions = [ - { - assertion = config.services.actual.settings.dataDir == dataDirectory; - message = "actual data location does not match persistence\nconfig directory: ${config.services.actual.settings.dataDir}\npersistence directory: ${dataDirectory}"; - } - { - assertion = config.systemd.services.actual.serviceConfig.DynamicUser or false; - message = "actual systemd service must have DynamicUser enabled to use private directory"; - } - ]; - environment.persistence."/persist/system/root" = { - directories = [ - { - directory = dataDirectory; - user = "actual"; - group = "actual"; - } - ]; - }; - }; -} diff --git a/modules/nixos-modules/server/actual/storage.nix b/modules/nixos-modules/server/actual/storage.nix new file mode 100644 index 0000000..d6b904e --- /dev/null +++ b/modules/nixos-modules/server/actual/storage.nix @@ -0,0 +1,22 @@ +{ + 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/modules/nixos-modules/server/bazarr/default.nix b/modules/nixos-modules/server/bazarr/default.nix index 86dbb4b..cb2a5f0 100644 --- a/modules/nixos-modules/server/bazarr/default.nix +++ b/modules/nixos-modules/server/bazarr/default.nix @@ -1,5 +1,5 @@ {...}: { imports = [ - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/bazarr/impermanence.nix b/modules/nixos-modules/server/bazarr/impermanence.nix deleted file mode 100644 index 70a45d1..0000000 --- a/modules/nixos-modules/server/bazarr/impermanence.nix +++ /dev/null @@ -1,33 +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.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.bazarr.impermanence.enable { - assertions = [ - { - assertion = config.services.bazarr.dataDir == bazarr_data_directory; - message = "bazarr data directory does not match persistence"; - } - ]; - - environment.persistence."/persist/system/root" = { - directories = [ - { - directory = bazarr_data_directory; - user = "bazarr"; - group = "bazarr"; - } - ]; - }; - }; -} diff --git a/modules/nixos-modules/server/bazarr/storage.nix b/modules/nixos-modules/server/bazarr/storage.nix new file mode 100644 index 0000000..a243d4c --- /dev/null +++ b/modules/nixos-modules/server/bazarr/storage.nix @@ -0,0 +1,21 @@ +{ + 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-modules/server/crab-hole/default.nix b/modules/nixos-modules/server/crab-hole/default.nix index 158a851..9f990c5 100644 --- a/modules/nixos-modules/server/crab-hole/default.nix +++ b/modules/nixos-modules/server/crab-hole/default.nix @@ -1,6 +1,6 @@ {...}: { imports = [ ./crab-hole.nix - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/crab-hole/impermanence.nix b/modules/nixos-modules/server/crab-hole/impermanence.nix deleted file mode 100644 index 51efc0c..0000000 --- a/modules/nixos-modules/server/crab-hole/impermanence.nix +++ /dev/null @@ -1,33 +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.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.crab-hole.impermanence.enable { - assertions = [ - { - assertion = - config.systemd.services.crab-hole.serviceConfig.WorkingDirectory == (builtins.replaceStrings ["/private"] [""] workingDirectory); - message = "crab-hole working directory does not match persistence"; - } - ]; - environment.persistence."/persist/system/root" = { - directories = [ - { - directory = workingDirectory; - user = "crab-hole"; - group = "crab-hole"; - } - ]; - }; - }; -} diff --git a/modules/nixos-modules/server/crab-hole/storage.nix b/modules/nixos-modules/server/crab-hole/storage.nix new file mode 100644 index 0000000..827fb25 --- /dev/null +++ b/modules/nixos-modules/server/crab-hole/storage.nix @@ -0,0 +1,21 @@ +{ + 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-modules/server/fail2ban/default.nix b/modules/nixos-modules/server/fail2ban/default.nix index 30fca99..84a46d4 100644 --- a/modules/nixos-modules/server/fail2ban/default.nix +++ b/modules/nixos-modules/server/fail2ban/default.nix @@ -1,6 +1,6 @@ {...}: { imports = [ ./fail2ban.nix - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/fail2ban/impermanence.nix b/modules/nixos-modules/server/fail2ban/impermanence.nix deleted file mode 100644 index 6e214b3..0000000 --- a/modules/nixos-modules/server/fail2ban/impermanence.nix +++ /dev/null @@ -1,34 +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.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.fail2ban.impermanence.enable { - assertions = [ - { - assertion = config.services.fail2ban.daemonSettings.Definition.dbfile == "${dataFolder}/${dataFile}"; - message = "fail2ban data file does not match persistence"; - } - ]; - - environment.persistence."/persist/system/root" = { - directories = [ - { - directory = dataFolder; - user = "fail2ban"; - group = "fail2ban"; - } - ]; - }; - }; -} diff --git a/modules/nixos-modules/server/fail2ban/storage.nix b/modules/nixos-modules/server/fail2ban/storage.nix new file mode 100644 index 0000000..1ef02c7 --- /dev/null +++ b/modules/nixos-modules/server/fail2ban/storage.nix @@ -0,0 +1,22 @@ +{ + 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/modules/nixos-modules/server/flaresolverr/default.nix b/modules/nixos-modules/server/flaresolverr/default.nix index 86dbb4b..cb2a5f0 100644 --- a/modules/nixos-modules/server/flaresolverr/default.nix +++ b/modules/nixos-modules/server/flaresolverr/default.nix @@ -1,5 +1,5 @@ {...}: { imports = [ - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/flaresolverr/impermanence.nix b/modules/nixos-modules/server/flaresolverr/impermanence.nix deleted file mode 100644 index 4544e75..0000000 --- a/modules/nixos-modules/server/flaresolverr/impermanence.nix +++ /dev/null @@ -1,26 +0,0 @@ -{ - lib, - config, - ... -}: { - options.services.flaresolverr = { - impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.flaresolverr.enable && config.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.flaresolverr.impermanence.enable { - # FlareSolverr typically doesn't need persistent storage as it's a proxy service - # but we'll add basic structure in case it's needed for logs or configuration - environment.persistence."/persist/system/root" = { - directories = [ - { - directory = "/var/lib/flaresolverr"; - user = "flaresolverr"; - group = "flaresolverr"; - } - ]; - }; - }; -} diff --git a/modules/nixos-modules/server/flaresolverr/storage.nix b/modules/nixos-modules/server/flaresolverr/storage.nix new file mode 100644 index 0000000..919318c --- /dev/null +++ b/modules/nixos-modules/server/flaresolverr/storage.nix @@ -0,0 +1,19 @@ +{ + 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-modules/server/forgejo/default.nix b/modules/nixos-modules/server/forgejo/default.nix index 4333f69..c990e57 100644 --- a/modules/nixos-modules/server/forgejo/default.nix +++ b/modules/nixos-modules/server/forgejo/default.nix @@ -4,6 +4,6 @@ ./proxy.nix ./database.nix ./fail2ban.nix - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/forgejo/impermanence.nix b/modules/nixos-modules/server/forgejo/impermanence.nix deleted file mode 100644 index 6fe3de8..0000000 --- a/modules/nixos-modules/server/forgejo/impermanence.nix +++ /dev/null @@ -1,35 +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.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.forgejo.impermanence.enable { - assertions = [ - { - assertion = config.services.forgejo.stateDir == stateDir; - message = "forgejo state directory does not match persistence"; - } - ]; - - environment.persistence."/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = stateDir; - user = "forgejo"; - group = "forgejo"; - } - ]; - }; - }; -} diff --git a/modules/nixos-modules/server/forgejo/storage.nix b/modules/nixos-modules/server/forgejo/storage.nix new file mode 100644 index 0000000..da30ed9 --- /dev/null +++ b/modules/nixos-modules/server/forgejo/storage.nix @@ -0,0 +1,21 @@ +{ + 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-modules/server/home-assistant/default.nix b/modules/nixos-modules/server/home-assistant/default.nix index b6f9356..d213964 100644 --- a/modules/nixos-modules/server/home-assistant/default.nix +++ b/modules/nixos-modules/server/home-assistant/default.nix @@ -4,7 +4,7 @@ ./proxy.nix ./database.nix ./fail2ban.nix - ./impermanence.nix + ./storage.nix ./extensions ]; } diff --git a/modules/nixos-modules/server/home-assistant/impermanence.nix b/modules/nixos-modules/server/home-assistant/impermanence.nix deleted file mode 100644 index 8c056a1..0000000 --- a/modules/nixos-modules/server/home-assistant/impermanence.nix +++ /dev/null @@ -1,26 +0,0 @@ -{ - lib, - config, - ... -}: let - configDir = "/var/lib/hass"; -in - lib.mkIf (config.host.impermanence.enable && config.services.home-assistant.enable) { - assertions = [ - { - assertion = config.services.home-assistant.configDir == configDir; - message = "home assistant config directory does not match persistence"; - } - ]; - environment.persistence."/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = configDir; - user = "hass"; - group = "hass"; - } - ]; - }; - } diff --git a/modules/nixos-modules/server/home-assistant/storage.nix b/modules/nixos-modules/server/home-assistant/storage.nix new file mode 100644 index 0000000..60e5085 --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/storage.nix @@ -0,0 +1,21 @@ +{ + 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-modules/server/immich/default.nix b/modules/nixos-modules/server/immich/default.nix index 4d93c0b..75ae2fd 100644 --- a/modules/nixos-modules/server/immich/default.nix +++ b/modules/nixos-modules/server/immich/default.nix @@ -3,7 +3,7 @@ ./proxy.nix ./database.nix ./fail2ban.nix - ./impermanence.nix + ./storage.nix ]; # NOTE: This shouldn't be needed now that we are out of testing diff --git a/modules/nixos-modules/server/immich/impermanence.nix b/modules/nixos-modules/server/immich/impermanence.nix deleted file mode 100644 index 56e51d0..0000000 --- a/modules/nixos-modules/server/immich/impermanence.nix +++ /dev/null @@ -1,32 +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.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.immich.impermanence.enable { - assertions = [ - { - assertion = config.services.immich.mediaLocation == mediaLocation; - message = "immich media location does not match persistence"; - } - ]; - environment.persistence."/persist/system/root" = { - directories = [ - { - directory = mediaLocation; - user = "immich"; - group = "immich"; - } - ]; - }; - }; -} diff --git a/modules/nixos-modules/server/immich/storage.nix b/modules/nixos-modules/server/immich/storage.nix new file mode 100644 index 0000000..de24329 --- /dev/null +++ b/modules/nixos-modules/server/immich/storage.nix @@ -0,0 +1,21 @@ +{ + 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-modules/server/jackett/default.nix b/modules/nixos-modules/server/jackett/default.nix index 86dbb4b..cb2a5f0 100644 --- a/modules/nixos-modules/server/jackett/default.nix +++ b/modules/nixos-modules/server/jackett/default.nix @@ -1,5 +1,5 @@ {...}: { imports = [ - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/jackett/impermanence.nix b/modules/nixos-modules/server/jackett/impermanence.nix deleted file mode 100644 index 24fc5e6..0000000 --- a/modules/nixos-modules/server/jackett/impermanence.nix +++ /dev/null @@ -1,33 +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.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.jackett.impermanence.enable { - assertions = [ - { - assertion = config.services.jackett.dataDir == jackett_data_directory; - message = "jackett data directory does not match persistence"; - } - ]; - - environment.persistence."/persist/system/root" = { - directories = [ - { - directory = jackett_data_directory; - user = "jackett"; - group = "jackett"; - } - ]; - }; - }; -} diff --git a/modules/nixos-modules/server/jackett/storage.nix b/modules/nixos-modules/server/jackett/storage.nix new file mode 100644 index 0000000..5f202e6 --- /dev/null +++ b/modules/nixos-modules/server/jackett/storage.nix @@ -0,0 +1,21 @@ +{ + 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-modules/server/jellyfin/default.nix b/modules/nixos-modules/server/jellyfin/default.nix index 2dbdcfd..4770ae1 100644 --- a/modules/nixos-modules/server/jellyfin/default.nix +++ b/modules/nixos-modules/server/jellyfin/default.nix @@ -3,6 +3,6 @@ ./jellyfin.nix ./proxy.nix ./fail2ban.nix - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/jellyfin/impermanence.nix b/modules/nixos-modules/server/jellyfin/impermanence.nix deleted file mode 100644 index cbcb54f..0000000 --- a/modules/nixos-modules/server/jellyfin/impermanence.nix +++ /dev/null @@ -1,73 +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.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.jellyfin.impermanence.enable { - fileSystems."/persist/system/jellyfin".neededForBoot = true; - - host.storage.pool.extraDatasets = { - # sops age key needs to be available to pre persist for user generation - "persist/system/jellyfin" = { - type = "zfs_fs"; - mountpoint = "/persist/system/jellyfin"; - options = { - atime = "off"; - relatime = "off"; - canmount = "on"; - }; - }; - }; - - assertions = [ - { - assertion = config.services.jellyfin.dataDir == jellyfin_data_directory; - message = "jellyfin data directory does not match persistence"; - } - { - assertion = config.services.jellyfin.cacheDir == jellyfin_cache_directory; - message = "jellyfin cache directory does not match persistence"; - } - ]; - - environment.persistence = { - "/persist/system/root" = { - directories = [ - { - directory = jellyfin_data_directory; - user = "jellyfin"; - group = "jellyfin"; - } - { - directory = jellyfin_cache_directory; - user = "jellyfin"; - group = "jellyfin"; - } - ]; - }; - - "/persist/system/jellyfin" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = config.services.jellyfin.media_directory; - user = "jellyfin"; - group = "jellyfin_media"; - mode = "1770"; - } - ]; - }; - }; - }; -} diff --git a/modules/nixos-modules/server/jellyfin/storage.nix b/modules/nixos-modules/server/jellyfin/storage.nix new file mode 100644 index 0000000..5cff3e8 --- /dev/null +++ b/modules/nixos-modules/server/jellyfin/storage.nix @@ -0,0 +1,56 @@ +{ + 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-modules/server/lidarr/default.nix b/modules/nixos-modules/server/lidarr/default.nix index 86dbb4b..cb2a5f0 100644 --- a/modules/nixos-modules/server/lidarr/default.nix +++ b/modules/nixos-modules/server/lidarr/default.nix @@ -1,5 +1,5 @@ {...}: { imports = [ - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/lidarr/impermanence.nix b/modules/nixos-modules/server/lidarr/impermanence.nix deleted file mode 100644 index 5d3aa3f..0000000 --- a/modules/nixos-modules/server/lidarr/impermanence.nix +++ /dev/null @@ -1,33 +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.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.lidarr.impermanence.enable { - assertions = [ - { - assertion = config.services.lidarr.dataDir == lidarr_data_directory; - message = "lidarr data directory does not match persistence"; - } - ]; - - environment.persistence."/persist/system/root" = { - directories = [ - { - directory = lidarr_data_directory; - user = "lidarr"; - group = "lidarr"; - } - ]; - }; - }; -} diff --git a/modules/nixos-modules/server/lidarr/storage.nix b/modules/nixos-modules/server/lidarr/storage.nix new file mode 100644 index 0000000..c4c020e --- /dev/null +++ b/modules/nixos-modules/server/lidarr/storage.nix @@ -0,0 +1,21 @@ +{ + 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-modules/server/network_storage/network_storage.nix b/modules/nixos-modules/server/network_storage/network_storage.nix index ebc3bee..b9d0446 100644 --- a/modules/nixos-modules/server/network_storage/network_storage.nix +++ b/modules/nixos-modules/server/network_storage/network_storage.nix @@ -74,7 +74,7 @@ in { ); } # (lib.mkIf config.host.impermanence.enable { - # environment.persistence."/persist/system/root" = { + # environment.persistence."/persist/replicate/system/root" = { # enable = true; # hideMounts = true; # directories = [ diff --git a/modules/nixos-modules/server/panoramax/default.nix b/modules/nixos-modules/server/panoramax/default.nix index 4c6b9ea..f5a514f 100644 --- a/modules/nixos-modules/server/panoramax/default.nix +++ b/modules/nixos-modules/server/panoramax/default.nix @@ -2,7 +2,7 @@ imports = [ ./proxy.nix ./fail2ban.nix - ./impermanence.nix + ./storage.nix ./panoramax.nix ./database.nix ]; diff --git a/modules/nixos-modules/server/panoramax/impermanence.nix b/modules/nixos-modules/server/panoramax/impermanence.nix deleted file mode 100644 index e25ef92..0000000 --- a/modules/nixos-modules/server/panoramax/impermanence.nix +++ /dev/null @@ -1,20 +0,0 @@ -{ - lib, - config, - ... -}: { - options.services.panoramax = { - impermanence.enable = lib.mkOption { - type = lib.types.bool; - default = config.services.panoramax.enable && config.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.panoramax.impermanence.enable { - # TODO: configure impermanence for panoramax data - # This would typically include directories like: - # - /var/lib/panoramax - # - panoramax storage directories - # - any cache or temporary directories that need to persist - }; -} diff --git a/modules/nixos-modules/server/panoramax/storage.nix b/modules/nixos-modules/server/panoramax/storage.nix new file mode 100644 index 0000000..b36e087 --- /dev/null +++ b/modules/nixos-modules/server/panoramax/storage.nix @@ -0,0 +1,19 @@ +{ + 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-modules/server/paperless/default.nix b/modules/nixos-modules/server/paperless/default.nix index 7e5e16b..f7a5aa7 100644 --- a/modules/nixos-modules/server/paperless/default.nix +++ b/modules/nixos-modules/server/paperless/default.nix @@ -4,6 +4,6 @@ ./proxy.nix ./database.nix ./fail2ban.nix - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/paperless/impermanence.nix b/modules/nixos-modules/server/paperless/impermanence.nix deleted file mode 100644 index fc87ea7..0000000 --- a/modules/nixos-modules/server/paperless/impermanence.nix +++ /dev/null @@ -1,32 +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.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.paperless.impermanence.enable { - assertions = [ - { - assertion = config.services.paperless.dataDir == dataDir; - message = "paperless data location does not match persistence"; - } - ]; - environment.persistence."/persist/system/root" = { - directories = [ - { - directory = dataDir; - user = "paperless"; - group = "paperless"; - } - ]; - }; - }; -} diff --git a/modules/nixos-modules/server/paperless/storage.nix b/modules/nixos-modules/server/paperless/storage.nix new file mode 100644 index 0000000..6e17bc2 --- /dev/null +++ b/modules/nixos-modules/server/paperless/storage.nix @@ -0,0 +1,21 @@ +{ + 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-modules/server/postgres/default.nix b/modules/nixos-modules/server/postgres/default.nix index abf4ade..50d90d4 100644 --- a/modules/nixos-modules/server/postgres/default.nix +++ b/modules/nixos-modules/server/postgres/default.nix @@ -1,6 +1,6 @@ {...}: { imports = [ ./postgres.nix - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/postgres/impermanence.nix b/modules/nixos-modules/server/postgres/impermanence.nix deleted file mode 100644 index a67fb1a..0000000 --- a/modules/nixos-modules/server/postgres/impermanence.nix +++ /dev/null @@ -1,27 +0,0 @@ -{ - config, - lib, - ... -}: let - dataDir = "/var/lib/postgresql/16"; -in { - config = lib.mkIf (config.services.postgresql.enable && config.host.impermanence.enable) { - assertions = [ - { - assertion = config.services.postgresql.dataDir == dataDir; - message = "postgres data directory does not match persistence"; - } - ]; - environment.persistence."/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = dataDir; - user = "postgres"; - group = "postgres"; - } - ]; - }; - }; -} diff --git a/modules/nixos-modules/server/postgres/storage.nix b/modules/nixos-modules/server/postgres/storage.nix new file mode 100644 index 0000000..58a84a6 --- /dev/null +++ b/modules/nixos-modules/server/postgres/storage.nix @@ -0,0 +1,21 @@ +{ + 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-modules/server/qbittorent/default.nix b/modules/nixos-modules/server/qbittorent/default.nix index f7511e6..11cc449 100644 --- a/modules/nixos-modules/server/qbittorent/default.nix +++ b/modules/nixos-modules/server/qbittorent/default.nix @@ -1,6 +1,6 @@ {...}: { imports = [ ./qbittorent.nix - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/qbittorent/impermanence.nix b/modules/nixos-modules/server/qbittorent/impermanence.nix deleted file mode 100644 index 1489e7d..0000000 --- a/modules/nixos-modules/server/qbittorent/impermanence.nix +++ /dev/null @@ -1,61 +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.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.qbittorrent.impermanence.enable { - fileSystems."/persist/system/qbittorrent".neededForBoot = true; - - host.storage.pool.extraDatasets = { - # sops age key needs to be available to pre persist for user generation - "persist/system/qbittorrent" = { - type = "zfs_fs"; - mountpoint = "/persist/system/qbittorrent"; - options = { - canmount = "on"; - }; - }; - }; - - assertions = [ - { - assertion = config.services.qbittorrent.profileDir == qbittorent_profile_directory; - message = "qbittorrent data directory does not match persistence"; - } - ]; - - environment.persistence = { - "/persist/system/root" = { - directories = [ - { - directory = qbittorent_profile_directory; - user = "qbittorrent"; - group = "qbittorrent"; - } - ]; - }; - - "/persist/system/qbittorrent" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = config.services.qbittorrent.mediaDir; - user = "qbittorrent"; - group = "qbittorrent"; - mode = "1775"; - } - ]; - }; - }; - }; -} diff --git a/modules/nixos-modules/server/qbittorent/storage.nix b/modules/nixos-modules/server/qbittorent/storage.nix new file mode 100644 index 0000000..da82bcc --- /dev/null +++ b/modules/nixos-modules/server/qbittorent/storage.nix @@ -0,0 +1,46 @@ +{ + 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-modules/server/radarr/default.nix b/modules/nixos-modules/server/radarr/default.nix index 86dbb4b..cb2a5f0 100644 --- a/modules/nixos-modules/server/radarr/default.nix +++ b/modules/nixos-modules/server/radarr/default.nix @@ -1,5 +1,5 @@ {...}: { imports = [ - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/radarr/impermanence.nix b/modules/nixos-modules/server/radarr/impermanence.nix deleted file mode 100644 index c948e3a..0000000 --- a/modules/nixos-modules/server/radarr/impermanence.nix +++ /dev/null @@ -1,33 +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.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.radarr.impermanence.enable { - assertions = [ - { - assertion = config.services.radarr.dataDir == radarr_data_directory; - message = "radarr data directory does not match persistence"; - } - ]; - - environment.persistence."/persist/system/root" = { - directories = [ - { - directory = radarr_data_directory; - user = "radarr"; - group = "radarr"; - } - ]; - }; - }; -} diff --git a/modules/nixos-modules/server/radarr/storage.nix b/modules/nixos-modules/server/radarr/storage.nix new file mode 100644 index 0000000..8f991c0 --- /dev/null +++ b/modules/nixos-modules/server/radarr/storage.nix @@ -0,0 +1,21 @@ +{ + 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-modules/server/reverseProxy/default.nix b/modules/nixos-modules/server/reverseProxy/default.nix index 5d57175..336e28b 100644 --- a/modules/nixos-modules/server/reverseProxy/default.nix +++ b/modules/nixos-modules/server/reverseProxy/default.nix @@ -1,6 +1,6 @@ {...}: { imports = [ ./reverseProxy.nix - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/reverseProxy/impermanence.nix b/modules/nixos-modules/server/reverseProxy/impermanence.nix deleted file mode 100644 index 7af55df..0000000 --- a/modules/nixos-modules/server/reverseProxy/impermanence.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - lib, - config, - ... -}: let - dataDir = "/var/lib/acme"; -in { - config = lib.mkIf (config.host.impermanence.enable && config.services.reverseProxy.enable) { - environment.persistence."/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = dataDir; - user = "acme"; - group = "acme"; - } - ]; - }; - }; -} diff --git a/modules/nixos-modules/server/reverseProxy/storage.nix b/modules/nixos-modules/server/reverseProxy/storage.nix new file mode 100644 index 0000000..62b5451 --- /dev/null +++ b/modules/nixos-modules/server/reverseProxy/storage.nix @@ -0,0 +1,21 @@ +{ + 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-modules/server/sonarr/default.nix b/modules/nixos-modules/server/sonarr/default.nix index 86dbb4b..cb2a5f0 100644 --- a/modules/nixos-modules/server/sonarr/default.nix +++ b/modules/nixos-modules/server/sonarr/default.nix @@ -1,5 +1,5 @@ {...}: { imports = [ - ./impermanence.nix + ./storage.nix ]; } diff --git a/modules/nixos-modules/server/sonarr/impermanence.nix b/modules/nixos-modules/server/sonarr/impermanence.nix deleted file mode 100644 index 5b90ee9..0000000 --- a/modules/nixos-modules/server/sonarr/impermanence.nix +++ /dev/null @@ -1,33 +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.host.impermanence.enable; - }; - }; - - config = lib.mkIf config.services.sonarr.impermanence.enable { - assertions = [ - { - assertion = config.services.sonarr.dataDir == sonarr_data_directory; - message = "sonarr data directory does not match persistence"; - } - ]; - - environment.persistence."/persist/system/root" = { - directories = [ - { - directory = sonarr_data_directory; - user = "sonarr"; - group = "sonarr"; - } - ]; - }; - }; -} diff --git a/modules/nixos-modules/server/sonarr/storage.nix b/modules/nixos-modules/server/sonarr/storage.nix new file mode 100644 index 0000000..8587751 --- /dev/null +++ b/modules/nixos-modules/server/sonarr/storage.nix @@ -0,0 +1,21 @@ +{ + 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-modules/server/wyoming.nix b/modules/nixos-modules/server/wyoming.nix index c9a1474..1df6877 100644 --- a/modules/nixos-modules/server/wyoming.nix +++ b/modules/nixos-modules/server/wyoming.nix @@ -48,7 +48,7 @@ systemd.services."wyoming-faster-whisper-en".serviceConfig.ProcSubset = lib.mkForce "all"; } (lib.mkIf config.host.impermanence.enable { - environment.persistence."/persist/system/root" = { + environment.persistence."/persist/replicate/system/root" = { enable = true; hideMounts = true; directories = [ diff --git a/modules/nixos-modules/ssh.nix b/modules/nixos-modules/ssh.nix index 0a82116..6fe8e5c 100644 --- a/modules/nixos-modules/ssh.nix +++ b/modules/nixos-modules/ssh.nix @@ -3,28 +3,42 @@ config, ... }: { - config = lib.mkMerge [ - { - services = { - openssh = { - enable = true; - ports = [22]; - settings = { - PasswordAuthentication = false; - UseDns = true; - X11Forwarding = false; - }; + 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; }; }; + }; - programs.ssh.kexAlgorithms = config.services.openssh.settings.KexAlgorithms; - } - (lib.mkIf config.host.impermanence.enable { - environment.persistence."/persist/system/root" = { - files = lib.lists.flatten ( - builtins.map (hostKey: [hostKey.path "${hostKey.path}.pub"]) config.services.openssh.hostKeys - ); - }; - }) - ]; + 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-modules/storage/default.nix b/modules/nixos-modules/storage/default.nix new file mode 100644 index 0000000..ebf990a --- /dev/null +++ b/modules/nixos-modules/storage/default.nix @@ -0,0 +1,13 @@ +{...}: { + # 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 + ]; +} diff --git a/modules/nixos-modules/storage/impermanence.nix b/modules/nixos-modules/storage/impermanence.nix new file mode 100644 index 0000000..9af5681 --- /dev/null +++ b/modules/nixos-modules/storage/impermanence.nix @@ -0,0 +1,136 @@ +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."; + } + ]; + + # fixes issues with /var/lib/private not having the correct permissions https://github.com/nix-community/impermanence/issues/254 + system.activationScripts."createPersistentStorageDirs".deps = ["var-lib-private-permissions" "users" "groups"]; + system.activationScripts = { + "var-lib-private-permissions" = { + 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; + + fileSystems = + lib.mapAttrs' ( + datasetName: dataset: + lib.nameValuePair "/${datasetName}" { + 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-modules/storage/storage.nix b/modules/nixos-modules/storage/storage.nix new file mode 100644 index 0000000..5f9f6f1 --- /dev/null +++ b/modules/nixos-modules/storage/storage.nix @@ -0,0 +1,206 @@ +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.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 { + # 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) { + 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) { + 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-modules/storage/submodules/dataset.nix b/modules/nixos-modules/storage/submodules/dataset.nix new file mode 100644 index 0000000..2a45552 --- /dev/null +++ b/modules/nixos-modules/storage/submodules/dataset.nix @@ -0,0 +1,86 @@ +{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/modules/nixos-modules/storage/submodules/impermanenceDataset.nix b/modules/nixos-modules/storage/submodules/impermanenceDataset.nix new file mode 100644 index 0000000..e4d3584 --- /dev/null +++ b/modules/nixos-modules/storage/submodules/impermanenceDataset.nix @@ -0,0 +1,56 @@ +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/modules/nixos-modules/storage/zfs.nix b/modules/nixos-modules/storage/zfs.nix new file mode 100644 index 0000000..1d3c1fb --- /dev/null +++ b/modules/nixos-modules/storage/zfs.nix @@ -0,0 +1,341 @@ +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)); + + # 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 = config.storage.zfs.pool.mode; + 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-modules/sync.nix b/modules/nixos-modules/sync.nix deleted file mode 100644 index 96f54d5..0000000 --- a/modules/nixos-modules/sync.nix +++ /dev/null @@ -1,69 +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; - }; - } - - (lib.mkIf config.host.impermanence.enable { - assertions = - [ - { - assertion = config.services.syncthing.configDir == configDir; - message = "syncthing config dir does not match persistence"; - } - ] - ++ lib.attrsets.mapAttrsToList (_: folder: { - assertion = lib.strings.hasPrefix mountDir folder.path; - message = "syncthing folder ${folder.label} is stored at ${folder.path} which not under the persisted path of ${mountDir}"; - }) - config.services.syncthing.settings.folders; - environment.persistence = { - "/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = mountDir; - user = "syncthing"; - group = "syncthing"; - } - { - directory = configDir; - user = "syncthing"; - group = "syncthing"; - } - ]; - }; - }; - }) - ])) - ]; -} diff --git a/modules/nixos-modules/sync/default.nix b/modules/nixos-modules/sync/default.nix new file mode 100644 index 0000000..5640417 --- /dev/null +++ b/modules/nixos-modules/sync/default.nix @@ -0,0 +1,6 @@ +{...}: { + imports = [ + ./sync.nix + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/sync/storage.nix b/modules/nixos-modules/sync/storage.nix new file mode 100644 index 0000000..61bf855 --- /dev/null +++ b/modules/nixos-modules/sync/storage.nix @@ -0,0 +1,32 @@ +{ + 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-modules/sync/sync.nix b/modules/nixos-modules/sync/sync.nix new file mode 100644 index 0000000..28b6e38 --- /dev/null +++ b/modules/nixos-modules/sync/sync.nix @@ -0,0 +1,36 @@ +{ + 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-modules/tailscale.nix b/modules/nixos-modules/tailscale.nix deleted file mode 100644 index db664e8..0000000 --- a/modules/nixos-modules/tailscale.nix +++ /dev/null @@ -1,34 +0,0 @@ -{ - config, - lib, - ... -}: let - tailscale_data_directory = "/var/lib/tailscale"; -in { - 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 - } - (lib.mkIf config.host.impermanence.enable { - environment.persistence = { - "/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = tailscale_data_directory; - user = "root"; - group = "root"; - } - ]; - }; - }; - }) - ] - ); -} diff --git a/modules/nixos-modules/tailscale/default.nix b/modules/nixos-modules/tailscale/default.nix new file mode 100644 index 0000000..7a283e8 --- /dev/null +++ b/modules/nixos-modules/tailscale/default.nix @@ -0,0 +1,6 @@ +{...}: { + imports = [ + ./tailscale.nix + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/tailscale/storage.nix b/modules/nixos-modules/tailscale/storage.nix new file mode 100644 index 0000000..7ac7e9a --- /dev/null +++ b/modules/nixos-modules/tailscale/storage.nix @@ -0,0 +1,24 @@ +{ + 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-modules/tailscale/tailscale.nix b/modules/nixos-modules/tailscale/tailscale.nix new file mode 100644 index 0000000..06899b1 --- /dev/null +++ b/modules/nixos-modules/tailscale/tailscale.nix @@ -0,0 +1,19 @@ +{ + 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-modules/users.nix b/modules/nixos-modules/users.nix index 987e080..195d8b6 100644 --- a/modules/nixos-modules/users.nix +++ b/modules/nixos-modules/users.nix @@ -399,79 +399,57 @@ in { }; }; } - (lib.mkIf config.host.impermanence.enable { - boot.initrd.postResumeCommands = lib.mkAfter ( - lib.strings.concatLines (builtins.map (user: "zfs rollback -r rpool/local/home/${user.name}@blank") - normalUsers) - ); - - systemd = { - tmpfiles.rules = - builtins.map ( - user: "d /persist/home/${user.name} 700 ${user.name} ${user.name} -" - ) - normalUsers; - }; - - fileSystems = lib.mkMerge [ - { - ${SOPS_AGE_KEY_DIRECTORY}.neededForBoot = true; - } - ( - builtins.listToAttrs ( - builtins.map (user: - lib.attrsets.nameValuePair "/persist/home/${user.name}" { - neededForBoot = true; - }) - normalUsers - ) - ) - ( - builtins.listToAttrs ( - builtins.map (user: - lib.attrsets.nameValuePair "/home/${user.name}" { - neededForBoot = true; - }) - normalUsers - ) - ) - ]; - - host.storage.pool.extraDatasets = lib.mkMerge ( - [ - { - # sops age key needs to be available to pre persist for user generation - "local/system/sops" = { - type = "zfs_fs"; - mountpoint = SOPS_AGE_KEY_DIRECTORY; - options = { - atime = "off"; - relatime = "off"; - canmount = "on"; - }; - }; - } - ] - ++ ( + (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: { - "local/home/${user.name}" = { + "home/${user.name}" = { type = "zfs_fs"; - mountpoint = "/home/${user.name}"; - options = { - canmount = "on"; - }; - postCreateHook = '' - zfs snapshot rpool/local/home/${user.name}@blank - ''; - }; - "persist/home/${user.name}" = { - type = "zfs_fs"; - mountpoint = "/persist/home/${user.name}"; + 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 + boot.initrd.postResumeCommands = 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; + # }; + }) + ])) ]; }