From fab03391fced092fa46b50fb14815f566036703d Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 4 Sep 2025 00:33:53 -0500 Subject: [PATCH 01/62] updated flake.lock --- flake.lock | 30 +++++++++++----------- modules/nixos-modules/server/paperless.nix | 3 +-- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/flake.lock b/flake.lock index 5b4d6a3..19959d9 100644 --- a/flake.lock +++ b/flake.lock @@ -46,11 +46,11 @@ }, "locked": { "dir": "pkgs/firefox-addons", - "lastModified": 1756699417, - "narHash": "sha256-rpRy5ae5ijEGaK+Cr66NqCQJ6ZeUE5Zi8gUWgKhesto=", + "lastModified": 1756958609, + "narHash": "sha256-1nRGsnPZjOubRTsXEsnJqWlLsgo/Xq7tN7PWK57dFDQ=", "owner": "rycee", "repo": "nur-expressions", - "rev": "007b803d1eff595d25e7886e83054dbd038bf029", + "rev": "b2a4e1bc62946403f82594ab9550ac13a1afa4df", "type": "gitlab" }, "original": { @@ -118,11 +118,11 @@ ] }, "locked": { - "lastModified": 1756734952, - "narHash": "sha256-H6jmduj4QIncLPAPODPSG/8ry9lpr1kRq6fYytU52qU=", + "lastModified": 1756954499, + "narHash": "sha256-Pg4xBHzvzNY8l9x/rLWoJMnIR8ebG+xeU+IyqThIkqU=", "owner": "nix-community", "repo": "home-manager", - "rev": "29ab63bbb3d9eee4a491f7ce701b189becd34068", + "rev": "ed1a98c375450dfccf427adacd2bfd1a7b22eb25", "type": "github" }, "original": { @@ -217,11 +217,11 @@ ] }, "locked": { - "lastModified": 1756692643, - "narHash": "sha256-SVos3AYuLvF6bD8Y0b6EiLABoEaiAOa4M/fTCBe0FV8=", + "lastModified": 1756950692, + "narHash": "sha256-3MnwSjiqIK8XtKZ1pkhuiv2wnCzQfulc5Wu0pWFluew=", "owner": "nix-community", "repo": "nix-vscode-extensions", - "rev": "2f1d16db96f1ce8ee3c893ea9dc49c0035846988", + "rev": "5ae2ac105a0d3ed2230a225ef6441928286897da", "type": "github" }, "original": { @@ -232,11 +232,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1756245047, - "narHash": "sha256-9bHzrVbjAudbO8q4vYFBWlEkDam31fsz0J7GB8k4AsI=", + "lastModified": 1756925795, + "narHash": "sha256-kUb5hehaikfUvoJDEc7ngiieX88TwWX/bBRX9Ar6Tac=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "a65b650d6981e23edd1afa1f01eb942f19cdcbb7", + "rev": "ba6fab29768007e9f2657014a6e134637100c57d", "type": "github" }, "original": { @@ -264,11 +264,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1756542300, - "narHash": "sha256-tlOn88coG5fzdyqz6R93SQL5Gpq+m/DsWpekNFhqPQk=", + "lastModified": 1756787288, + "narHash": "sha256-rw/PHa1cqiePdBxhF66V7R+WAP8WekQ0mCDG4CFqT8Y=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d7600c775f877cd87b4f5a831c28aa94137377aa", + "rev": "d0fc30899600b9b3466ddb260fd83deb486c32f1", "type": "github" }, "original": { diff --git a/modules/nixos-modules/server/paperless.nix b/modules/nixos-modules/server/paperless.nix index 0243d53..b97c48d 100644 --- a/modules/nixos-modules/server/paperless.nix +++ b/modules/nixos-modules/server/paperless.nix @@ -51,10 +51,9 @@ in { }; }; services.paperless = { + domain = "${config.services.paperless.subdomain}.${config.host.reverse_proxy.hostname}"; configureTika = true; settings = { - PAPERLESS_URL = "https://${config.services.paperless.subdomain}.${config.host.reverse_proxy.hostname}"; - PAPERLESS_DBENGINE = "postgresql"; PAPERLESS_DBHOST = "/run/postgresql"; PAPERLESS_DBNAME = config.services.paperless.database.user; From c31eb38229e0be1414ecad42a0085debba16c5fd Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 4 Sep 2025 14:33:17 -0500 Subject: [PATCH 02/62] installed direnv extension --- .../leyla/packages/vscode/default.nix | 1 + .../programs/vscode/default.nix | 1 + .../programs/vscode/direnv.nix | 25 +++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 modules/home-manager-modules/programs/vscode/direnv.nix diff --git a/configurations/home-manager/leyla/packages/vscode/default.nix b/configurations/home-manager/leyla/packages/vscode/default.nix index 778439a..f213d3c 100644 --- a/configurations/home-manager/leyla/packages/vscode/default.nix +++ b/configurations/home-manager/leyla/packages/vscode/default.nix @@ -78,6 +78,7 @@ in { # misc extensions evenBetterToml.enable = true; + direnv.enable = config.programs.direnv.enable; }; extensions = let diff --git a/modules/home-manager-modules/programs/vscode/default.nix b/modules/home-manager-modules/programs/vscode/default.nix index 50b323d..48eb1ce 100644 --- a/modules/home-manager-modules/programs/vscode/default.nix +++ b/modules/home-manager-modules/programs/vscode/default.nix @@ -21,5 +21,6 @@ ./claudeDev.nix ./nearley.nix ./vitest.nix + ./direnv.nix ]; } diff --git a/modules/home-manager-modules/programs/vscode/direnv.nix b/modules/home-manager-modules/programs/vscode/direnv.nix new file mode 100644 index 0000000..231ea17 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/direnv.nix @@ -0,0 +1,25 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.vscode-marketplace; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.direnv = { + enable = lib.mkEnableOption "Enable direnv extension"; + extension = lib.mkPackageOption pkgsRepository "direnv" { + default = ["mkhl" "direnv"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.direnv.enable { + extensions = [config.extraExtensions.direnv.extension]; + }; + })); + }; +} From 2aad75a334050e30f9fba9567d231ff6d338bda7 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 4 Sep 2025 15:23:59 -0500 Subject: [PATCH 03/62] moved more packages to modules --- configurations/home-manager/eve/packages.nix | 2 +- .../home-manager/leyla/packages/default.nix | 66 ++++++------------- .../programs/davinci-resolve.nix | 30 +++++++++ .../home-manager-modules/programs/default.nix | 18 +++++ .../home-manager-modules/programs/freecad.nix | 29 ++++++++ .../programs/gdx-liftoff.nix | 17 +++++ .../home-manager-modules/programs/gimp.nix | 29 ++++++++ .../programs/inkscape.nix | 29 ++++++++ .../programs/libreoffice.nix | 29 ++++++++ .../home-manager-modules/programs/mfoc.nix | 17 +++++ .../programs/noisetorch.nix | 17 +++++ .../programs/onionshare.nix | 17 +++++ .../home-manager-modules/programs/openrgb.nix | 17 +++++ .../home-manager-modules/programs/openvpn.nix | 17 +++++ .../programs/pdfarranger.nix | 17 +++++ .../home-manager-modules/programs/picard.nix | 29 ++++++++ .../programs/proxmark3.nix | 17 +++++ .../programs/qflipper.nix | 29 ++++++++ .../programs/tor-browser.nix | 29 ++++++++ .../programs/ungoogled-chromium.nix | 29 ++++++++ modules/home-manager-modules/programs/via.nix | 17 +++++ 21 files changed, 453 insertions(+), 48 deletions(-) create mode 100644 modules/home-manager-modules/programs/davinci-resolve.nix create mode 100644 modules/home-manager-modules/programs/freecad.nix create mode 100644 modules/home-manager-modules/programs/gdx-liftoff.nix create mode 100644 modules/home-manager-modules/programs/gimp.nix create mode 100644 modules/home-manager-modules/programs/inkscape.nix create mode 100644 modules/home-manager-modules/programs/libreoffice.nix create mode 100644 modules/home-manager-modules/programs/mfoc.nix create mode 100644 modules/home-manager-modules/programs/noisetorch.nix create mode 100644 modules/home-manager-modules/programs/onionshare.nix create mode 100644 modules/home-manager-modules/programs/openrgb.nix create mode 100644 modules/home-manager-modules/programs/openvpn.nix create mode 100644 modules/home-manager-modules/programs/pdfarranger.nix create mode 100644 modules/home-manager-modules/programs/picard.nix create mode 100644 modules/home-manager-modules/programs/proxmark3.nix create mode 100644 modules/home-manager-modules/programs/qflipper.nix create mode 100644 modules/home-manager-modules/programs/tor-browser.nix create mode 100644 modules/home-manager-modules/programs/ungoogled-chromium.nix create mode 100644 modules/home-manager-modules/programs/via.nix diff --git a/configurations/home-manager/eve/packages.nix b/configurations/home-manager/eve/packages.nix index f7f0c78..f738fe2 100644 --- a/configurations/home-manager/eve/packages.nix +++ b/configurations/home-manager/eve/packages.nix @@ -17,7 +17,6 @@ in { # See https://search.nixos.org/packages for all options home.packages = lib.lists.optionals userConfig.isDesktopUser ( with pkgs; [ - ungoogled-chromium gnomeExtensions.dash-to-panel ] ); @@ -61,6 +60,7 @@ in { steam.enable = true; piper.enable = hardware.piperMouse.enable; krita.enable = true; + ungoogled-chromium.enable = true; }) ]; }; diff --git a/configurations/home-manager/leyla/packages/default.nix b/configurations/home-manager/leyla/packages/default.nix index 717b153..86bbd96 100644 --- a/configurations/home-manager/leyla/packages/default.nix +++ b/configurations/home-manager/leyla/packages/default.nix @@ -37,6 +37,12 @@ in { dbeaver-bin.enable = true; bruno.enable = true; piper.enable = hardware.piperMouse.enable; + proxmark3.enable = true; + openrgb.enable = hardware.openRGB.enable; + via.enable = hardware.viaKeyboard.enable; + claude-code.enable = osConfig.host.ai.enable; + davinci-resolve.enable = hardware.graphicsAcceleration.enable; + mfoc.enable = true; }) (lib.mkIf (hardware.directAccess.enable && config.user.isDesktopUser) { anki.enable = true; @@ -50,6 +56,19 @@ in { firefox.enable = true; steam.enable = true; krita.enable = true; + ungoogled-chromium.enable = true; + libreoffice.enable = true; + inkscape.enable = true; + gimp.enable = true; + freecad.enable = true; + onionshare.enable = true; + pdfarranger.enable = true; + picard.enable = true; + qflipper.enable = true; + openvpn.enable = true; + noisetorch.enable = true; + tor-browser.enable = true; + gdx-liftoff.enable = true; }) ]; } @@ -66,53 +85,6 @@ in { nixpkgs.config = { allowUnfree = true; }; - - home.packages = ( - (with pkgs; [ - proxmark3 - ]) - ++ ( - lib.lists.optionals hardware.directAccess.enable (with pkgs; [ - #foss platforms - ungoogled-chromium - libreoffice - inkscape - gimp - freecad - # cura - # kicad-small - onionshare - # rhythmbox - - # wireshark - # rpi-imager - # fritzing - mfoc - tor-browser - pdfarranger - picard - - gdx-liftoff - - # proprietary platforms - (lib.mkIf hardware.graphicsAcceleration.enable davinci-resolve) - - # development tools - # androidStudioPackages.canary - qFlipper - - # system tools - openvpn - noisetorch - - # hardware management tools - (lib.mkIf hardware.openRGB.enable openrgb) - (lib.mkIf hardware.viaKeyboard.enable via) - - (lib.mkIf osConfig.host.ai.enable claude-code) - ]) - ) - ); }) ]; } diff --git a/modules/home-manager-modules/programs/davinci-resolve.nix b/modules/home-manager-modules/programs/davinci-resolve.nix new file mode 100644 index 0000000..00ba525 --- /dev/null +++ b/modules/home-manager-modules/programs/davinci-resolve.nix @@ -0,0 +1,30 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.davinci-resolve = { + enable = lib.mkEnableOption "enable davinci-resolve"; + }; + + config = lib.mkIf config.programs.davinci-resolve.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + davinci-resolve + ]; + } + ( + lib.mkIf osConfig.host.impermanence.enable { + home.persistence."/persist${config.home.homeDirectory}" = { + directories = [ + "${config.xdg.dataHome}/DaVinciResolve" + "${config.xdg.configHome}/blackmagic" + ]; + allowOther = true; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/default.nix b/modules/home-manager-modules/programs/default.nix index f2a22ee..d1c13db 100644 --- a/modules/home-manager-modules/programs/default.nix +++ b/modules/home-manager-modules/programs/default.nix @@ -19,5 +19,23 @@ ./dbeaver.nix ./steam.nix ./vscode + ./ungoogled-chromium.nix + ./libreoffice.nix + ./inkscape.nix + ./gimp.nix + ./proxmark3.nix + ./freecad.nix + ./onionshare.nix + ./mfoc.nix + ./pdfarranger.nix + ./picard.nix + ./qflipper.nix + ./openvpn.nix + ./noisetorch.nix + ./openrgb.nix + ./via.nix + ./davinci-resolve.nix + ./gdx-liftoff.nix + ./tor-browser.nix ]; } diff --git a/modules/home-manager-modules/programs/freecad.nix b/modules/home-manager-modules/programs/freecad.nix new file mode 100644 index 0000000..ec17205 --- /dev/null +++ b/modules/home-manager-modules/programs/freecad.nix @@ -0,0 +1,29 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.freecad = { + enable = lib.mkEnableOption "enable freecad"; + }; + + config = lib.mkIf config.programs.freecad.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + freecad + ]; + } + ( + lib.mkIf osConfig.host.impermanence.enable { + home.persistence."/persist${config.home.homeDirectory}" = { + directories = [ + "${config.xdg.configHome}/FreeCAD" + ]; + allowOther = true; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/gdx-liftoff.nix b/modules/home-manager-modules/programs/gdx-liftoff.nix new file mode 100644 index 0000000..b29230d --- /dev/null +++ b/modules/home-manager-modules/programs/gdx-liftoff.nix @@ -0,0 +1,17 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.gdx-liftoff = { + enable = lib.mkEnableOption "enable gdx-liftoff"; + }; + + config = lib.mkIf config.programs.gdx-liftoff.enable { + home.packages = with pkgs; [ + gdx-liftoff + ]; + }; +} diff --git a/modules/home-manager-modules/programs/gimp.nix b/modules/home-manager-modules/programs/gimp.nix new file mode 100644 index 0000000..428068e --- /dev/null +++ b/modules/home-manager-modules/programs/gimp.nix @@ -0,0 +1,29 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.gimp = { + enable = lib.mkEnableOption "enable gimp"; + }; + + config = lib.mkIf config.programs.gimp.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + gimp + ]; + } + ( + lib.mkIf osConfig.host.impermanence.enable { + home.persistence."/persist${config.home.homeDirectory}" = { + directories = [ + "${config.xdg.configHome}/GIMP" + ]; + allowOther = true; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/inkscape.nix b/modules/home-manager-modules/programs/inkscape.nix new file mode 100644 index 0000000..facb08f --- /dev/null +++ b/modules/home-manager-modules/programs/inkscape.nix @@ -0,0 +1,29 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.inkscape = { + enable = lib.mkEnableOption "enable inkscape"; + }; + + config = lib.mkIf config.programs.inkscape.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + inkscape + ]; + } + ( + lib.mkIf osConfig.host.impermanence.enable { + home.persistence."/persist${config.home.homeDirectory}" = { + directories = [ + "${config.xdg.configHome}/inkscape" + ]; + allowOther = true; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/libreoffice.nix b/modules/home-manager-modules/programs/libreoffice.nix new file mode 100644 index 0000000..b61ea58 --- /dev/null +++ b/modules/home-manager-modules/programs/libreoffice.nix @@ -0,0 +1,29 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.libreoffice = { + enable = lib.mkEnableOption "enable libreoffice"; + }; + + config = lib.mkIf config.programs.libreoffice.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + libreoffice + ]; + } + ( + lib.mkIf osConfig.host.impermanence.enable { + home.persistence."/persist${config.home.homeDirectory}" = { + directories = [ + "${config.xdg.configHome}/libreoffice" + ]; + allowOther = true; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/mfoc.nix b/modules/home-manager-modules/programs/mfoc.nix new file mode 100644 index 0000000..7b92007 --- /dev/null +++ b/modules/home-manager-modules/programs/mfoc.nix @@ -0,0 +1,17 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.mfoc = { + enable = lib.mkEnableOption "enable mfoc"; + }; + + config = lib.mkIf config.programs.mfoc.enable { + home.packages = with pkgs; [ + mfoc + ]; + }; +} diff --git a/modules/home-manager-modules/programs/noisetorch.nix b/modules/home-manager-modules/programs/noisetorch.nix new file mode 100644 index 0000000..c53e3a9 --- /dev/null +++ b/modules/home-manager-modules/programs/noisetorch.nix @@ -0,0 +1,17 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.noisetorch = { + enable = lib.mkEnableOption "enable noisetorch"; + }; + + config = lib.mkIf config.programs.noisetorch.enable { + home.packages = with pkgs; [ + noisetorch + ]; + }; +} diff --git a/modules/home-manager-modules/programs/onionshare.nix b/modules/home-manager-modules/programs/onionshare.nix new file mode 100644 index 0000000..ed1903d --- /dev/null +++ b/modules/home-manager-modules/programs/onionshare.nix @@ -0,0 +1,17 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.onionshare = { + enable = lib.mkEnableOption "enable onionshare"; + }; + + config = lib.mkIf config.programs.onionshare.enable { + home.packages = with pkgs; [ + onionshare + ]; + }; +} diff --git a/modules/home-manager-modules/programs/openrgb.nix b/modules/home-manager-modules/programs/openrgb.nix new file mode 100644 index 0000000..0260c91 --- /dev/null +++ b/modules/home-manager-modules/programs/openrgb.nix @@ -0,0 +1,17 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.openrgb = { + enable = lib.mkEnableOption "enable openrgb"; + }; + + config = lib.mkIf config.programs.openrgb.enable { + home.packages = with pkgs; [ + openrgb + ]; + }; +} diff --git a/modules/home-manager-modules/programs/openvpn.nix b/modules/home-manager-modules/programs/openvpn.nix new file mode 100644 index 0000000..814c16d --- /dev/null +++ b/modules/home-manager-modules/programs/openvpn.nix @@ -0,0 +1,17 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.openvpn = { + enable = lib.mkEnableOption "enable openvpn"; + }; + + config = lib.mkIf config.programs.openvpn.enable { + home.packages = with pkgs; [ + openvpn + ]; + }; +} diff --git a/modules/home-manager-modules/programs/pdfarranger.nix b/modules/home-manager-modules/programs/pdfarranger.nix new file mode 100644 index 0000000..d4e33b5 --- /dev/null +++ b/modules/home-manager-modules/programs/pdfarranger.nix @@ -0,0 +1,17 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.pdfarranger = { + enable = lib.mkEnableOption "enable pdfarranger"; + }; + + config = lib.mkIf config.programs.pdfarranger.enable { + home.packages = with pkgs; [ + pdfarranger + ]; + }; +} diff --git a/modules/home-manager-modules/programs/picard.nix b/modules/home-manager-modules/programs/picard.nix new file mode 100644 index 0000000..d2c1fe2 --- /dev/null +++ b/modules/home-manager-modules/programs/picard.nix @@ -0,0 +1,29 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.picard = { + enable = lib.mkEnableOption "enable picard"; + }; + + config = lib.mkIf config.programs.picard.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + picard + ]; + } + ( + lib.mkIf osConfig.host.impermanence.enable { + home.persistence."/persist${config.home.homeDirectory}" = { + directories = [ + "${config.xdg.configHome}/MusicBrainz" + ]; + allowOther = true; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/proxmark3.nix b/modules/home-manager-modules/programs/proxmark3.nix new file mode 100644 index 0000000..ad1e298 --- /dev/null +++ b/modules/home-manager-modules/programs/proxmark3.nix @@ -0,0 +1,17 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.proxmark3 = { + enable = lib.mkEnableOption "enable proxmark3"; + }; + + config = lib.mkIf config.programs.proxmark3.enable { + home.packages = with pkgs; [ + proxmark3 + ]; + }; +} diff --git a/modules/home-manager-modules/programs/qflipper.nix b/modules/home-manager-modules/programs/qflipper.nix new file mode 100644 index 0000000..abc2442 --- /dev/null +++ b/modules/home-manager-modules/programs/qflipper.nix @@ -0,0 +1,29 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.qflipper = { + enable = lib.mkEnableOption "enable qflipper"; + }; + + config = lib.mkIf config.programs.qflipper.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + qFlipper + ]; + } + ( + lib.mkIf osConfig.host.impermanence.enable { + home.persistence."/persist${config.home.homeDirectory}" = { + directories = [ + "${config.xdg.configHome}/qFlipper" + ]; + allowOther = true; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/tor-browser.nix b/modules/home-manager-modules/programs/tor-browser.nix new file mode 100644 index 0000000..2c58578 --- /dev/null +++ b/modules/home-manager-modules/programs/tor-browser.nix @@ -0,0 +1,29 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.tor-browser = { + enable = lib.mkEnableOption "enable tor-browser"; + }; + + config = lib.mkIf config.programs.tor-browser.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + tor-browser + ]; + } + ( + lib.mkIf osConfig.host.impermanence.enable { + home.persistence."/persist${config.home.homeDirectory}" = { + 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 new file mode 100644 index 0000000..5b52cd6 --- /dev/null +++ b/modules/home-manager-modules/programs/ungoogled-chromium.nix @@ -0,0 +1,29 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.ungoogled-chromium = { + enable = lib.mkEnableOption "enable ungoogled-chromium"; + }; + + config = lib.mkIf config.programs.ungoogled-chromium.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + ungoogled-chromium + ]; + } + ( + lib.mkIf osConfig.host.impermanence.enable { + home.persistence."/persist${config.home.homeDirectory}" = { + 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 new file mode 100644 index 0000000..0b79452 --- /dev/null +++ b/modules/home-manager-modules/programs/via.nix @@ -0,0 +1,17 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.via = { + enable = lib.mkEnableOption "enable via"; + }; + + config = lib.mkIf config.programs.via.enable { + home.packages = with pkgs; [ + via + ]; + }; +} From 1831fea96a57f9c538b3f72f13b09adb51a35ea1 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 4 Sep 2025 15:40:22 -0500 Subject: [PATCH 04/62] updated flake lock --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 19959d9..03054c1 100644 --- a/flake.lock +++ b/flake.lock @@ -118,11 +118,11 @@ ] }, "locked": { - "lastModified": 1756954499, - "narHash": "sha256-Pg4xBHzvzNY8l9x/rLWoJMnIR8ebG+xeU+IyqThIkqU=", + "lastModified": 1756991914, + "narHash": "sha256-4ve/3ah5H/SpL2m3qmZ9GU+VinQYp2MN1G7GamimTds=", "owner": "nix-community", "repo": "home-manager", - "rev": "ed1a98c375450dfccf427adacd2bfd1a7b22eb25", + "rev": "b08f8737776f10920c330657bee8b95834b7a70f", "type": "github" }, "original": { @@ -175,11 +175,11 @@ ] }, "locked": { - "lastModified": 1755825449, - "narHash": "sha256-XkiN4NM9Xdy59h69Pc+Vg4PxkSm9EWl6u7k6D5FZ5cM=", + "lastModified": 1757015938, + "narHash": "sha256-1qBXNK/QxEjCqIoA2DxWn5gqM8rVxt+OxKodXu1GLTY=", "owner": "LnL7", "repo": "nix-darwin", - "rev": "8df64f819698c1fee0c2969696f54a843b2231e8", + "rev": "eaacfa1101b84225491d2ceae9549366d74dc214", "type": "github" }, "original": { From 68b791f7c10340d2e97992f9bfe3e12311945ac8 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sat, 6 Sep 2025 23:11:22 -0500 Subject: [PATCH 05/62] feat: installed conventional commits plugin --- .../leyla/packages/vscode/default.nix | 1 + .../programs/vscode/conventionalCommits.nix | 25 +++++++++++++++++++ .../programs/vscode/default.nix | 1 + 3 files changed, 27 insertions(+) create mode 100644 modules/home-manager-modules/programs/vscode/conventionalCommits.nix diff --git a/configurations/home-manager/leyla/packages/vscode/default.nix b/configurations/home-manager/leyla/packages/vscode/default.nix index f213d3c..8ac026e 100644 --- a/configurations/home-manager/leyla/packages/vscode/default.nix +++ b/configurations/home-manager/leyla/packages/vscode/default.nix @@ -79,6 +79,7 @@ in { # misc extensions evenBetterToml.enable = true; direnv.enable = config.programs.direnv.enable; + conventionalCommits.enable = true; }; extensions = let diff --git a/modules/home-manager-modules/programs/vscode/conventionalCommits.nix b/modules/home-manager-modules/programs/vscode/conventionalCommits.nix new file mode 100644 index 0000000..00ca6fa --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/conventionalCommits.nix @@ -0,0 +1,25 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.vscode-marketplace; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.conventionalCommits = { + enable = lib.mkEnableOption "Enable VSCode Conventional Commits extension"; + extension = lib.mkPackageOption pkgsRepository "conventional-commits" { + default = ["vivaxy" "vscode-conventional-commits"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.conventionalCommits.enable { + extensions = [config.extraExtensions.conventionalCommits.extension]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/default.nix b/modules/home-manager-modules/programs/vscode/default.nix index 48eb1ce..85f4a62 100644 --- a/modules/home-manager-modules/programs/vscode/default.nix +++ b/modules/home-manager-modules/programs/vscode/default.nix @@ -22,5 +22,6 @@ ./nearley.nix ./vitest.nix ./direnv.nix + ./conventionalCommits.nix ]; } From 09d258840662c034f187e66bea1536eefb1f644a Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sat, 6 Sep 2025 23:42:13 -0500 Subject: [PATCH 06/62] feat: added config options to hte conventional commit extension to disable emoji and scopes propts --- .../programs/vscode/conventionalCommits.nix | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/modules/home-manager-modules/programs/vscode/conventionalCommits.nix b/modules/home-manager-modules/programs/vscode/conventionalCommits.nix index 00ca6fa..1e3954c 100644 --- a/modules/home-manager-modules/programs/vscode/conventionalCommits.nix +++ b/modules/home-manager-modules/programs/vscode/conventionalCommits.nix @@ -15,10 +15,27 @@ in { extension = lib.mkPackageOption pkgsRepository "conventional-commits" { default = ["vivaxy" "vscode-conventional-commits"]; }; + + emojiFormat = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Enable emoji format for conventional commits"; + }; + + promptScopes = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Enable prompting for scopes in conventional commits"; + }; }; }; config = lib.mkIf config.extraExtensions.conventionalCommits.enable { extensions = [config.extraExtensions.conventionalCommits.extension]; + + userSettings = { + "conventionalCommits.emojiFormat" = config.extraExtensions.conventionalCommits.emojiFormat; + "conventionalCommits.promptScopes" = config.extraExtensions.conventionalCommits.promptScopes; + }; }; })); }; From 58fec3f132f0fdfd4ade138ffc1edf9905753ad4 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sun, 7 Sep 2025 15:58:54 -0500 Subject: [PATCH 07/62] refactor: switched to using mkEnableOption --- .../programs/vscode/conventionalCommits.nix | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/modules/home-manager-modules/programs/vscode/conventionalCommits.nix b/modules/home-manager-modules/programs/vscode/conventionalCommits.nix index 1e3954c..5bc8124 100644 --- a/modules/home-manager-modules/programs/vscode/conventionalCommits.nix +++ b/modules/home-manager-modules/programs/vscode/conventionalCommits.nix @@ -16,24 +16,16 @@ in { default = ["vivaxy" "vscode-conventional-commits"]; }; - emojiFormat = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Enable emoji format for conventional commits"; - }; + gitmoji = lib.mkEnableOption "should emoji be prompted for as a part of the commit message./"; - promptScopes = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Enable prompting for scopes in conventional commits"; - }; + promptScopes = lib.mkEnableOption "prompting for scopes in conventional commits"; }; }; config = lib.mkIf config.extraExtensions.conventionalCommits.enable { extensions = [config.extraExtensions.conventionalCommits.extension]; userSettings = { - "conventionalCommits.emojiFormat" = config.extraExtensions.conventionalCommits.emojiFormat; + "conventionalCommits.gitmoji" = config.extraExtensions.conventionalCommits.gitmoji; "conventionalCommits.promptScopes" = config.extraExtensions.conventionalCommits.promptScopes; }; }; From 487dc215503aab6d309ade68480ac48a47604376 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sun, 7 Sep 2025 20:12:46 -0500 Subject: [PATCH 08/62] refactor: removed eslint mcp server --- .../leyla/packages/vscode/default.nix | 1 - .../programs/vscode/claudeDev.nix | 38 +++---------------- 2 files changed, 6 insertions(+), 33 deletions(-) diff --git a/configurations/home-manager/leyla/packages/vscode/default.nix b/configurations/home-manager/leyla/packages/vscode/default.nix index 8ac026e..6e36908 100644 --- a/configurations/home-manager/leyla/packages/vscode/default.nix +++ b/configurations/home-manager/leyla/packages/vscode/default.nix @@ -72,7 +72,6 @@ in { enable = true; mcp = { nixos.enable = true; - eslint.enable = true; }; }; diff --git a/modules/home-manager-modules/programs/vscode/claudeDev.nix b/modules/home-manager-modules/programs/vscode/claudeDev.nix index 11eb155..21ff6b5 100644 --- a/modules/home-manager-modules/programs/vscode/claudeDev.nix +++ b/modules/home-manager-modules/programs/vscode/claudeDev.nix @@ -10,21 +10,11 @@ mcp-nixos = inputs.mcp-nixos.packages.${pkgs.stdenv.hostPlatform.system}.default; - mcp-eslint = pkgs.writeShellScriptBin "mcp-eslint" '' - ${pkgs.nodejs}/bin/npx --yes @modelcontextprotocol/server-eslint "$@" - ''; - anyProfileHasMcpNixos = lib.any ( profile: profile.extraExtensions.claudeDev.enable && profile.extraExtensions.claudeDev.mcp.nixos.enable ) (lib.attrValues config.programs.vscode.profiles); - - anyProfileHasMcpEslint = lib.any ( - profile: - profile.extraExtensions.claudeDev.enable - && profile.extraExtensions.claudeDev.mcp.eslint.enable - ) (lib.attrValues config.programs.vscode.profiles); in { options.programs.vscode.profiles = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { @@ -39,9 +29,6 @@ in { nixos = { enable = lib.mkEnableOption "enable NixOS MCP server for Claude Dev"; }; - eslint = { - enable = lib.mkEnableOption "enable ESLint MCP server for Claude Dev"; - }; }; }; }; @@ -60,27 +47,14 @@ in { ]; }) - (lib.mkIf anyProfileHasMcpEslint { - home.packages = [ - mcp-eslint - pkgs.eslint - ]; - }) - - (lib.mkIf (anyProfileHasMcpNixos || anyProfileHasMcpEslint) { + (lib.mkIf anyProfileHasMcpNixos { home.file."${config.xdg.configHome}/VSCodium/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json" = { text = builtins.toJSON { - mcpServers = - (lib.optionalAttrs anyProfileHasMcpNixos { - nixos = { - command = "${mcp-nixos}/bin/mcp-nixos"; - }; - }) - // (lib.optionalAttrs anyProfileHasMcpEslint { - eslint = { - command = "${mcp-eslint}/bin/mcp-eslint"; - }; - }); + mcpServers = { + nixos = { + command = "${mcp-nixos}/bin/mcp-nixos"; + }; + }; }; force = true; }; From 2745af9443d5a814866e791e513648d887dc7dbe Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Mon, 8 Sep 2025 16:47:05 -0500 Subject: [PATCH 09/62] feat: updated flake.lock --- flake.lock | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/flake.lock b/flake.lock index 03054c1..6ea6f9b 100644 --- a/flake.lock +++ b/flake.lock @@ -25,11 +25,11 @@ ] }, "locked": { - "lastModified": 1756733629, - "narHash": "sha256-dwWGlDhcO5SMIvMSTB4mjQ5Pvo2vtxvpIknhVnSz2I8=", + "lastModified": 1757255839, + "narHash": "sha256-XH33B1X888Xc/xEXhF1RPq/kzKElM0D5C9N6YdvOvIc=", "owner": "nix-community", "repo": "disko", - "rev": "a5c4f2ab72e3d1ab43e3e65aa421c6f2bd2e12a1", + "rev": "c8a0e78d86b12ea67be6ed0f7cae7f9bfabae75a", "type": "github" }, "original": { @@ -46,11 +46,11 @@ }, "locked": { "dir": "pkgs/firefox-addons", - "lastModified": 1756958609, - "narHash": "sha256-1nRGsnPZjOubRTsXEsnJqWlLsgo/Xq7tN7PWK57dFDQ=", + "lastModified": 1757304222, + "narHash": "sha256-s070stByAXxeCLgftTXxFxZ2ynJhghne4Y6cTuqGAaw=", "owner": "rycee", "repo": "nur-expressions", - "rev": "b2a4e1bc62946403f82594ab9550ac13a1afa4df", + "rev": "fa312c0175ffb82bc67da095439b9cb683ac52bd", "type": "gitlab" }, "original": { @@ -118,11 +118,11 @@ ] }, "locked": { - "lastModified": 1756991914, - "narHash": "sha256-4ve/3ah5H/SpL2m3qmZ9GU+VinQYp2MN1G7GamimTds=", + "lastModified": 1757256385, + "narHash": "sha256-WK7tOhWwr15mipcckhDg2no/eSpM1nIh4C9le8HgHhk=", "owner": "nix-community", "repo": "home-manager", - "rev": "b08f8737776f10920c330657bee8b95834b7a70f", + "rev": "f35703b412c67b48e97beb6e27a6ab96a084cd37", "type": "github" }, "original": { @@ -175,11 +175,11 @@ ] }, "locked": { - "lastModified": 1757015938, - "narHash": "sha256-1qBXNK/QxEjCqIoA2DxWn5gqM8rVxt+OxKodXu1GLTY=", + "lastModified": 1757130842, + "narHash": "sha256-4i7KKuXesSZGUv0cLPLfxbmF1S72Gf/3aSypgvVkwuA=", "owner": "LnL7", "repo": "nix-darwin", - "rev": "eaacfa1101b84225491d2ceae9549366d74dc214", + "rev": "15f067638e2887c58c4b6ba1bdb65a0b61dc58c5", "type": "github" }, "original": { @@ -217,11 +217,11 @@ ] }, "locked": { - "lastModified": 1756950692, - "narHash": "sha256-3MnwSjiqIK8XtKZ1pkhuiv2wnCzQfulc5Wu0pWFluew=", + "lastModified": 1757296711, + "narHash": "sha256-7u9/tXUdmTj8x7ofet8aELLBlCHSoA+QOhYKheRdacM=", "owner": "nix-community", "repo": "nix-vscode-extensions", - "rev": "5ae2ac105a0d3ed2230a225ef6441928286897da", + "rev": "ab9374ac8c162dacffcd4400e668fd7f9b6f173a", "type": "github" }, "original": { @@ -232,11 +232,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1756925795, - "narHash": "sha256-kUb5hehaikfUvoJDEc7ngiieX88TwWX/bBRX9Ar6Tac=", + "lastModified": 1757103352, + "narHash": "sha256-PtT7ix43ss8PONJ1VJw3f6t2yAoGH+q462Sn8lrmWmk=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "ba6fab29768007e9f2657014a6e134637100c57d", + "rev": "11b2a10c7be726321bb854403fdeec391e798bf0", "type": "github" }, "original": { @@ -264,11 +264,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1756787288, - "narHash": "sha256-rw/PHa1cqiePdBxhF66V7R+WAP8WekQ0mCDG4CFqT8Y=", + "lastModified": 1757068644, + "narHash": "sha256-NOrUtIhTkIIumj1E/Rsv1J37Yi3xGStISEo8tZm3KW4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d0fc30899600b9b3466ddb260fd83deb486c32f1", + "rev": "8eb28adfa3dc4de28e792e3bf49fcf9007ca8ac9", "type": "github" }, "original": { From ca9f54d795a84177cf4d98e818891e87c5c44f28 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Fri, 12 Sep 2025 00:41:17 -0500 Subject: [PATCH 10/62] feat: installed mcp servers for vitest and eslint --- .../leyla/packages/vscode/default.nix | 2 + .../programs/vscode/claudeDev.nix | 45 ++++++++++++++++--- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/configurations/home-manager/leyla/packages/vscode/default.nix b/configurations/home-manager/leyla/packages/vscode/default.nix index 6e36908..41ecdcb 100644 --- a/configurations/home-manager/leyla/packages/vscode/default.nix +++ b/configurations/home-manager/leyla/packages/vscode/default.nix @@ -72,6 +72,8 @@ in { enable = true; mcp = { nixos.enable = true; + eslint.enable = true; + vitest.enable = true; }; }; diff --git a/modules/home-manager-modules/programs/vscode/claudeDev.nix b/modules/home-manager-modules/programs/vscode/claudeDev.nix index 21ff6b5..47da0af 100644 --- a/modules/home-manager-modules/programs/vscode/claudeDev.nix +++ b/modules/home-manager-modules/programs/vscode/claudeDev.nix @@ -15,6 +15,20 @@ profile.extraExtensions.claudeDev.enable && profile.extraExtensions.claudeDev.mcp.nixos.enable ) (lib.attrValues config.programs.vscode.profiles); + + anyProfileHasMcpEslint = lib.any ( + profile: + profile.extraExtensions.claudeDev.enable + && profile.extraExtensions.claudeDev.mcp.eslint.enable + ) (lib.attrValues config.programs.vscode.profiles); + + anyProfileHasMcpVitest = lib.any ( + profile: + profile.extraExtensions.claudeDev.enable + && profile.extraExtensions.claudeDev.mcp.vitest.enable + ) (lib.attrValues config.programs.vscode.profiles); + + anyProfileHasMcp = anyProfileHasMcpNixos || anyProfileHasMcpEslint || anyProfileHasMcpVitest; in { options.programs.vscode.profiles = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { @@ -29,6 +43,12 @@ in { nixos = { enable = lib.mkEnableOption "enable NixOS MCP server for Claude Dev"; }; + eslint = { + enable = lib.mkEnableOption "enable ESLint MCP server for Claude Dev"; + }; + vitest = { + enable = lib.mkEnableOption "enable Vitest MCP server for Claude Dev"; + }; }; }; }; @@ -47,14 +67,27 @@ in { ]; }) - (lib.mkIf anyProfileHasMcpNixos { + (lib.mkIf anyProfileHasMcp { home.file."${config.xdg.configHome}/VSCodium/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json" = { text = builtins.toJSON { - mcpServers = { - nixos = { - command = "${mcp-nixos}/bin/mcp-nixos"; - }; - }; + mcpServers = + (lib.optionalAttrs anyProfileHasMcpNixos { + nixos = { + command = "${mcp-nixos}/bin/mcp-nixos"; + }; + }) + // (lib.optionalAttrs anyProfileHasMcpEslint { + eslint = { + command = "${pkgs.nodejs}/bin/npx"; + args = ["-y" "@eslint/mcp@latest"]; + }; + }) + // (lib.optionalAttrs anyProfileHasMcpVitest { + vitest = { + command = "${pkgs.nodejs}/bin/npx"; + args = ["-y" "@djankies/vitest-mcp"]; + }; + }); }; force = true; }; From cf330b1cbb44a07a28bca5d86eebd40176f5ac5a Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Fri, 12 Sep 2025 10:18:06 -0500 Subject: [PATCH 11/62] feat: installed sleep-mcp server --- .../leyla/packages/vscode/default.nix | 1 + .../programs/vscode/claudeDev.nix | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/configurations/home-manager/leyla/packages/vscode/default.nix b/configurations/home-manager/leyla/packages/vscode/default.nix index 41ecdcb..a651265 100644 --- a/configurations/home-manager/leyla/packages/vscode/default.nix +++ b/configurations/home-manager/leyla/packages/vscode/default.nix @@ -74,6 +74,7 @@ in { nixos.enable = true; eslint.enable = true; vitest.enable = true; + sleep.enable = true; }; }; diff --git a/modules/home-manager-modules/programs/vscode/claudeDev.nix b/modules/home-manager-modules/programs/vscode/claudeDev.nix index 47da0af..9c067e8 100644 --- a/modules/home-manager-modules/programs/vscode/claudeDev.nix +++ b/modules/home-manager-modules/programs/vscode/claudeDev.nix @@ -28,7 +28,13 @@ && profile.extraExtensions.claudeDev.mcp.vitest.enable ) (lib.attrValues config.programs.vscode.profiles); - anyProfileHasMcp = anyProfileHasMcpNixos || anyProfileHasMcpEslint || anyProfileHasMcpVitest; + anyProfileHasMcpSleep = lib.any ( + profile: + profile.extraExtensions.claudeDev.enable + && profile.extraExtensions.claudeDev.mcp.sleep.enable + ) (lib.attrValues config.programs.vscode.profiles); + + anyProfileHasMcp = anyProfileHasMcpNixos || anyProfileHasMcpEslint || anyProfileHasMcpVitest || anyProfileHasMcpSleep; in { options.programs.vscode.profiles = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { @@ -49,6 +55,9 @@ in { vitest = { enable = lib.mkEnableOption "enable Vitest MCP server for Claude Dev"; }; + sleep = { + enable = lib.mkEnableOption "enable Sleep MCP server for Claude Dev"; + }; }; }; }; @@ -87,6 +96,12 @@ in { command = "${pkgs.nodejs}/bin/npx"; args = ["-y" "@djankies/vitest-mcp"]; }; + }) + // (lib.optionalAttrs anyProfileHasMcpSleep { + sleep-mcp = { + command = "${pkgs.nodejs}/bin/npx"; + args = ["-y" "sleep-mcp"]; + }; }); }; force = true; From 0f8faadd80cb86c34d23b7e17f327de5f3f7acde Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Fri, 12 Sep 2025 10:47:31 -0500 Subject: [PATCH 12/62] feat: added more config options for mcp servers --- .../leyla/packages/vscode/default.nix | 24 +++- .../programs/vscode/claudeDev.nix | 110 ++++++++++++++++-- 2 files changed, 119 insertions(+), 15 deletions(-) diff --git a/configurations/home-manager/leyla/packages/vscode/default.nix b/configurations/home-manager/leyla/packages/vscode/default.nix index a651265..583f440 100644 --- a/configurations/home-manager/leyla/packages/vscode/default.nix +++ b/configurations/home-manager/leyla/packages/vscode/default.nix @@ -72,9 +72,27 @@ in { enable = true; mcp = { nixos.enable = true; - eslint.enable = true; - vitest.enable = true; - sleep.enable = true; + eslint = { + enable = true; + autoApprove = { + lint-files = true; + }; + }; + vitest = { + enable = true; + autoApprove = { + list_tests = true; + run_tests = true; + analyze_coverage = true; + }; + }; + sleep = { + enable = true; + timeout = 18000; # 5 hours to match claude codes timeout + autoApprove = { + sleep = true; + }; + }; }; }; diff --git a/modules/home-manager-modules/programs/vscode/claudeDev.nix b/modules/home-manager-modules/programs/vscode/claudeDev.nix index 9c067e8..0e34f97 100644 --- a/modules/home-manager-modules/programs/vscode/claudeDev.nix +++ b/modules/home-manager-modules/programs/vscode/claudeDev.nix @@ -35,6 +35,30 @@ ) (lib.attrValues config.programs.vscode.profiles); anyProfileHasMcp = anyProfileHasMcpNixos || anyProfileHasMcpEslint || anyProfileHasMcpVitest || anyProfileHasMcpSleep; + + getMcpTimeout = serverName: + lib.findFirst (timeout: timeout != null) null (map ( + profile: + if profile.extraExtensions.claudeDev.enable && profile.extraExtensions.claudeDev.mcp.${serverName}.enable + then profile.extraExtensions.claudeDev.mcp.${serverName}.timeout + else null + ) (lib.attrValues config.programs.vscode.profiles)); + + getMcpAutoApprove = serverName: + lib.foldl' ( + acc: profile: + if profile.extraExtensions.claudeDev.enable && profile.extraExtensions.claudeDev.mcp.${serverName}.enable + then acc // profile.extraExtensions.claudeDev.mcp.${serverName}.autoApprove + else acc + ) {} (lib.attrValues config.programs.vscode.profiles); + + getMcpPackage = serverName: + lib.findFirst (package: package != null) null (map ( + profile: + if profile.extraExtensions.claudeDev.enable && profile.extraExtensions.claudeDev.mcp.${serverName}.enable + then profile.extraExtensions.claudeDev.mcp.${serverName}.package + else null + ) (lib.attrValues config.programs.vscode.profiles)); in { options.programs.vscode.profiles = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { @@ -51,12 +75,53 @@ in { }; eslint = { enable = lib.mkEnableOption "enable ESLint MCP server for Claude Dev"; + package = lib.mkOption { + type = lib.types.str; + default = "@eslint/mcp@latest"; + description = "NPM package to use for ESLint MCP server"; + }; + timeout = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = null; + description = "Timeout in seconds for ESLint MCP server operations"; + }; + autoApprove = { + lint-files = lib.mkEnableOption "Should the lint-files tool be auto approved for ESLint MCP server"; + }; }; vitest = { enable = lib.mkEnableOption "enable Vitest MCP server for Claude Dev"; + package = lib.mkOption { + type = lib.types.str; + default = "@djankies/vitest-mcp"; + description = "NPM package to use for Vitest MCP server"; + }; + timeout = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = null; + description = "Timeout in seconds for Vitest MCP server operations"; + }; + autoApprove = { + list_tests = lib.mkEnableOption "Should the list_tests tool be auto approved for Vitest MCP server"; + run_tests = lib.mkEnableOption "Should the run_tests tool be auto approved for Vitest MCP server"; + analyze_coverage = lib.mkEnableOption "Should the analyze_coverage tool be auto approved for Vitest MCP server"; + }; }; sleep = { enable = lib.mkEnableOption "enable Sleep MCP server for Claude Dev"; + package = lib.mkOption { + type = lib.types.str; + default = "sleep-mcp"; + description = "NPM package to use for Sleep MCP server"; + }; + timeout = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = null; + description = "Timeout in seconds for Sleep MCP server operations"; + }; + autoApprove = { + sleep = lib.mkEnableOption "Should the sleep tool be auto approved for Sleep MCP server"; + }; }; }; }; @@ -86,22 +151,43 @@ in { }; }) // (lib.optionalAttrs anyProfileHasMcpEslint { - eslint = { - command = "${pkgs.nodejs}/bin/npx"; - args = ["-y" "@eslint/mcp@latest"]; - }; + eslint = + { + command = "${pkgs.nodejs}/bin/npx"; + args = ["-y" (getMcpPackage "eslint")]; + } + // (lib.optionalAttrs ((getMcpTimeout "eslint") != null) { + timeout = getMcpTimeout "eslint"; + }) + // (lib.optionalAttrs ((getMcpAutoApprove "eslint") != {}) { + autoApprove = builtins.attrNames (lib.filterAttrs (_: v: v) (getMcpAutoApprove "eslint")); + }); }) // (lib.optionalAttrs anyProfileHasMcpVitest { - vitest = { - command = "${pkgs.nodejs}/bin/npx"; - args = ["-y" "@djankies/vitest-mcp"]; - }; + vitest = + { + command = "${pkgs.nodejs}/bin/npx"; + args = ["-y" (getMcpPackage "vitest")]; + } + // (lib.optionalAttrs ((getMcpTimeout "vitest") != null) { + timeout = getMcpTimeout "vitest"; + }) + // (lib.optionalAttrs ((getMcpAutoApprove "vitest") != {}) { + autoApprove = builtins.attrNames (lib.filterAttrs (_: v: v) (getMcpAutoApprove "vitest")); + }); }) // (lib.optionalAttrs anyProfileHasMcpSleep { - sleep-mcp = { - command = "${pkgs.nodejs}/bin/npx"; - args = ["-y" "sleep-mcp"]; - }; + sleep-mcp = + { + command = "${pkgs.nodejs}/bin/npx"; + args = ["-y" (getMcpPackage "sleep")]; + } + // (lib.optionalAttrs ((getMcpTimeout "sleep") != null) { + timeout = getMcpTimeout "sleep"; + }) + // (lib.optionalAttrs ((getMcpAutoApprove "sleep") != {}) { + autoApprove = builtins.attrNames (lib.filterAttrs (_: v: v) (getMcpAutoApprove "sleep")); + }); }); }; force = true; From ffcba0d714f2fed4a72aadf68ccd184d5872dcee Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sat, 13 Sep 2025 18:03:04 -0500 Subject: [PATCH 13/62] feat: created mapillary desktop uploader dirivation --- modules/common-modules/pkgs/default.nix | 3 ++ .../pkgs/mapillary-uploader.nix | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 modules/common-modules/pkgs/mapillary-uploader.nix diff --git a/modules/common-modules/pkgs/default.nix b/modules/common-modules/pkgs/default.nix index 16f3a3c..669533b 100644 --- a/modules/common-modules/pkgs/default.nix +++ b/modules/common-modules/pkgs/default.nix @@ -22,5 +22,8 @@ (final: prev: { codium-extensions = pkgs.callPackage ./codium-extensions {}; }) + (final: prev: { + mapillary-uploader = pkgs.callPackage ./mapillary-uploader.nix {}; + }) ]; } diff --git a/modules/common-modules/pkgs/mapillary-uploader.nix b/modules/common-modules/pkgs/mapillary-uploader.nix new file mode 100644 index 0000000..3ab38f8 --- /dev/null +++ b/modules/common-modules/pkgs/mapillary-uploader.nix @@ -0,0 +1,42 @@ +{ + lib, + fetchurl, + appimageTools, +}: let + pname = "mapillary-uploader"; + version = "4.7.2"; # Based on the application output + + src = fetchurl { + url = "https://tools.mapillary.com/uploader/download/linux"; + name = "mapillary-uploader.AppImage"; + sha256 = "sha256-Oyx7AIdA/2mwBaq7UzXOoyq/z2SU2sViMN40sY2RCQw="; + }; + + appimageContents = appimageTools.extractType2 { + inherit pname version src; + }; +in + appimageTools.wrapType2 { + inherit pname version src; + + extraInstallCommands = '' + # Install desktop file + install -Dm644 ${appimageContents}/mapillary-desktop-uploader.desktop $out/share/applications/mapillary-uploader.desktop + + # Install icon + install -Dm644 ${appimageContents}/usr/share/icons/hicolor/0x0/apps/mapillary-desktop-uploader.png $out/share/pixmaps/mapillary-uploader.png + + # Fix desktop file paths + substituteInPlace $out/share/applications/mapillary-uploader.desktop \ + --replace 'Exec=AppRun' 'Exec=${pname}' + ''; + + meta = with lib; { + description = "Mapillary Desktop Uploader - Upload street-level imagery to Mapillary"; + homepage = "https://www.mapillary.com/"; + license = licenses.unfree; # Mapillary's license terms + maintainers = []; + platforms = ["x86_64-linux"]; + sourceProvenance = with sourceTypes; [binaryNativeCode]; + }; + } From 3bf3391eb90e7b2cead773c262ecf0d8c8c8a4c9 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sat, 13 Sep 2025 18:15:43 -0500 Subject: [PATCH 14/62] feat: created mapillary desktop uploader program config and installed for leyla --- .../home-manager/leyla/packages/default.nix | 1 + .../home-manager-modules/programs/default.nix | 1 + .../programs/mapillary-uploader.nix | 17 +++++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 modules/home-manager-modules/programs/mapillary-uploader.nix diff --git a/configurations/home-manager/leyla/packages/default.nix b/configurations/home-manager/leyla/packages/default.nix index 86bbd96..6377ed2 100644 --- a/configurations/home-manager/leyla/packages/default.nix +++ b/configurations/home-manager/leyla/packages/default.nix @@ -58,6 +58,7 @@ in { krita.enable = true; ungoogled-chromium.enable = true; libreoffice.enable = true; + mapillary-uploader.enable = true; inkscape.enable = true; gimp.enable = true; freecad.enable = true; diff --git a/modules/home-manager-modules/programs/default.nix b/modules/home-manager-modules/programs/default.nix index d1c13db..79f3351 100644 --- a/modules/home-manager-modules/programs/default.nix +++ b/modules/home-manager-modules/programs/default.nix @@ -21,6 +21,7 @@ ./vscode ./ungoogled-chromium.nix ./libreoffice.nix + ./mapillary-uploader.nix ./inkscape.nix ./gimp.nix ./proxmark3.nix diff --git a/modules/home-manager-modules/programs/mapillary-uploader.nix b/modules/home-manager-modules/programs/mapillary-uploader.nix new file mode 100644 index 0000000..38c1144 --- /dev/null +++ b/modules/home-manager-modules/programs/mapillary-uploader.nix @@ -0,0 +1,17 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.programs.mapillary-uploader; +in { + options.programs.mapillary-uploader = { + enable = mkEnableOption "Mapillary Desktop Uploader"; + }; + + config = mkIf cfg.enable { + home.packages = [pkgs.mapillary-uploader]; + }; +} From 22b9c5b3f96094ddd6e210070650da4c1d9a497c Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sun, 14 Sep 2025 17:34:19 -0500 Subject: [PATCH 15/62] chore: added items to task list --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 339a8e8..13d1206 100644 --- a/README.md +++ b/README.md @@ -67,4 +67,7 @@ nix multi user, multi system, configuration with `sops` secret management, `home - rotate sops encryption keys periodically (and somehow sync between devices?) - wake on LAN for updates - remote distributed builds - https://nix.dev/tutorials/nixos/distributed-builds-setup.html -- ISO target that contains authorized keys for nixos-anywhere https://github.com/diegofariasm/yggdrasil/blob/4acc43ebc7bcbf2e41376d14268e382007e94d78/hosts/bootstrap/default.nix \ No newline at end of file +- ISO target that contains authorized keys for nixos-anywhere https://github.com/diegofariasm/yggdrasil/blob/4acc43ebc7bcbf2e41376d14268e382007e94d78/hosts/bootstrap/default.nix +- panoramax instance +- mastodon instance +- move searx, jellyfin, paperless, and immich to only be accessible via vpn \ No newline at end of file From 88dcba346f6413963277fdb0f216a4f64811ac08 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sun, 14 Sep 2025 21:42:34 -0500 Subject: [PATCH 16/62] feat: started to create panoramax config --- modules/common-modules/pkgs/default.nix | 3 + modules/common-modules/pkgs/panoramax.nix | 65 ++++++++++++++++++++++ modules/nixos-modules/server/panoramax.nix | 43 ++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 modules/common-modules/pkgs/panoramax.nix create mode 100644 modules/nixos-modules/server/panoramax.nix diff --git a/modules/common-modules/pkgs/default.nix b/modules/common-modules/pkgs/default.nix index 669533b..f1235cc 100644 --- a/modules/common-modules/pkgs/default.nix +++ b/modules/common-modules/pkgs/default.nix @@ -25,5 +25,8 @@ (final: prev: { mapillary-uploader = pkgs.callPackage ./mapillary-uploader.nix {}; }) + (final: prev: { + panoramax = pkgs.python3.pkgs.callPackage ./panoramax.nix {}; + }) ]; } diff --git a/modules/common-modules/pkgs/panoramax.nix b/modules/common-modules/pkgs/panoramax.nix new file mode 100644 index 0000000..e2dad14 --- /dev/null +++ b/modules/common-modules/pkgs/panoramax.nix @@ -0,0 +1,65 @@ +{ + lib, + fetchFromGitLab, + buildPythonPackage, + flit-core, + flask, + pillow, + requests, + python-dotenv, + authlib, + sentry-sdk, + python-dateutil, + croniter, + pydantic, + ... +}: let + pname = "geovisio"; + version = "2.10.0"; + repo = fetchFromGitLab { + owner = "panoramax"; + repo = "server/api"; + rev = version; + hash = "sha256-kCLcrOe7jJdIfmWWOmxQ5dOj8ZG2B7s0qFpHXs02B/E="; + }; +in + buildPythonPackage { + inherit pname version; + + pyproject = true; + + src = repo; + + build-system = [ + flit-core + ]; + + dependencies = [ + flask + pillow + requests + python-dotenv + authlib + sentry-sdk + python-dateutil + croniter + pydantic + ]; + + # Skip tests as they may require network access or specific setup + doCheck = false; + + # Disable runtime dependencies check as many dependencies are not available in nixpkgs + dontCheckRuntimeDeps = true; + + # Disable imports check as many dependencies are not available in nixpkgs + pythonImportsCheck = []; + + meta = with lib; { + description = "Panoramax API client and tools for street-level imagery platform"; + homepage = "https://gitlab.com/panoramax/server/api"; + license = licenses.mit; + maintainers = []; + platforms = platforms.all; + }; + } diff --git a/modules/nixos-modules/server/panoramax.nix b/modules/nixos-modules/server/panoramax.nix new file mode 100644 index 0000000..a16588a --- /dev/null +++ b/modules/nixos-modules/server/panoramax.nix @@ -0,0 +1,43 @@ +{ + config, + lib, + pkgs, + osConfig, + ... +}: let + cfg = config.services.panoramax; +in { + options.services.panoramax = { + enable = lib.mkEnableOption "panoramax"; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.panoramax; + description = "The panoramax package to use"; + }; + + # TODO: create configs + # TODO: auto config db + # config = { + # DB_PORT = lib.mkOption {}; + # DB_HOST = lib.mkOption {}; + # DB_USERNAME = lib.mkOption {}; + # DB_PASSWORD = lib.mkOption {}; + # DB_NAME = lib.mkOption {}; + # FS_URL = lib.mkOption {}; + # }; + }; + + config = lib.mkIf cfg.enable ( + lib.mkMerge [ + { + # TODO: configure options for the package + } + ( + lib.mkIf osConfig.host.impermanence.enable { + # TODO: configure impermanence for panoramax data + } + ) + ] + ); +} From 663bdcc012cbf6410bf6acebaf4566313c4081fe Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sun, 14 Sep 2025 21:48:10 -0500 Subject: [PATCH 17/62] chore: stubed out section for fail2ban for panoramax --- modules/nixos-modules/server/panoramax.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/nixos-modules/server/panoramax.nix b/modules/nixos-modules/server/panoramax.nix index a16588a..f7d305f 100644 --- a/modules/nixos-modules/server/panoramax.nix +++ b/modules/nixos-modules/server/panoramax.nix @@ -33,6 +33,11 @@ in { { # TODO: configure options for the package } + ( + lib.mkIf config.services.fail2ban { + # TODO: configure options for fail2ban + } + ) ( lib.mkIf osConfig.host.impermanence.enable { # TODO: configure impermanence for panoramax data From 52801b4bb7a71f02115c6f3b05899ed3daf150ab Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sun, 14 Sep 2025 22:10:57 -0500 Subject: [PATCH 18/62] refactor: moved reverse proxy into own section in server modules --- modules/nixos-modules/server/actual.nix | 12 +- modules/nixos-modules/server/forgejo.nix | 10 +- .../nixos-modules/server/home-assistant.nix | 37 +++--- modules/nixos-modules/server/immich.nix | 38 +++--- modules/nixos-modules/server/jellyfin.nix | 28 ++--- modules/nixos-modules/server/paperless.nix | 26 +++-- modules/nixos-modules/server/searx.nix | 110 +++++++++--------- 7 files changed, 142 insertions(+), 119 deletions(-) diff --git a/modules/nixos-modules/server/actual.nix b/modules/nixos-modules/server/actual.nix index 7fc0b93..80f4fab 100644 --- a/modules/nixos-modules/server/actual.nix +++ b/modules/nixos-modules/server/actual.nix @@ -18,11 +18,6 @@ in { systemd.tmpfiles.rules = [ "d ${dataDirectory} 2770 actual actual" ]; - host = { - reverse_proxy.subdomains.${config.services.actual.subdomain} = { - target = "http://localhost:${toString config.services.actual.settings.port}"; - }; - }; services.actual = { settings = { @@ -30,6 +25,13 @@ in { }; }; } + (lib.mkIf config.host.reverse_proxy.enable { + host = { + reverse_proxy.subdomains.${config.services.actual.subdomain} = { + target = "http://localhost:${toString config.services.actual.settings.port}"; + }; + }; + }) (lib.mkIf config.services.fail2ban.enable { # TODO: configuration for fail2ban for actual }) diff --git a/modules/nixos-modules/server/forgejo.nix b/modules/nixos-modules/server/forgejo.nix index de06f94..3b19695 100644 --- a/modules/nixos-modules/server/forgejo.nix +++ b/modules/nixos-modules/server/forgejo.nix @@ -26,9 +26,6 @@ in { } ]; host = { - reverse_proxy.subdomains.${config.services.forgejo.subdomain} = { - target = "http://localhost:${toString forgejoPort}"; - }; postgres = { enable = true; extraUsers = { @@ -76,6 +73,13 @@ in { config.services.forgejo.settings.server.SSH_LISTEN_PORT ]; } + (lib.mkIf config.host.reverse_proxy.enable { + host = { + reverse_proxy.subdomains.${config.services.forgejo.subdomain} = { + target = "http://localhost:${toString forgejoPort}"; + }; + }; + }) (lib.mkIf config.services.fail2ban.enable { environment.etc = { "fail2ban/filter.d/forgejo.local".text = lib.mkIf config.services.forgejo.enable ( diff --git a/modules/nixos-modules/server/home-assistant.nix b/modules/nixos-modules/server/home-assistant.nix index 57bedc1..baf6683 100644 --- a/modules/nixos-modules/server/home-assistant.nix +++ b/modules/nixos-modules/server/home-assistant.nix @@ -43,24 +43,6 @@ in { config = lib.mkIf config.services.home-assistant.enable (lib.mkMerge [ { - host = { - reverse_proxy.subdomains.${config.services.home-assistant.subdomain} = { - target = "http://localhost:${toString config.services.home-assistant.config.http.server_port}"; - - websockets.enable = true; - forwardHeaders.enable = true; - - extraConfig = '' - add_header Upgrade $http_upgrade; - add_header Connection \"upgrade\"; - - proxy_buffering off; - - proxy_read_timeout 90; - ''; - }; - }; - services.home-assistant = { configDir = configDir; extraComponents = [ @@ -173,6 +155,25 @@ in { ]; }; }) + (lib.mkIf config.host.reverse_proxy.enable { + host = { + reverse_proxy.subdomains.${config.services.home-assistant.subdomain} = { + target = "http://localhost:${toString config.services.home-assistant.config.http.server_port}"; + + websockets.enable = true; + forwardHeaders.enable = true; + + extraConfig = '' + add_header Upgrade $http_upgrade; + add_header Connection \"upgrade\"; + + proxy_buffering off; + + proxy_read_timeout 90; + ''; + }; + }; + }) (lib.mkIf config.services.fail2ban.enable { environment.etc = { "fail2ban/filter.d/hass.local".text = lib.mkIf config.services.home-assistant.enable ( diff --git a/modules/nixos-modules/server/immich.nix b/modules/nixos-modules/server/immich.nix index e7088a9..fa376e4 100644 --- a/modules/nixos-modules/server/immich.nix +++ b/modules/nixos-modules/server/immich.nix @@ -17,23 +17,6 @@ in { config = lib.mkIf config.services.immich.enable (lib.mkMerge [ { host = { - reverse_proxy.subdomains.${config.services.immich.subdomain} = { - target = "http://localhost:${toString config.services.immich.port}"; - - websockets.enable = true; - forwardHeaders.enable = true; - - extraConfig = '' - # allow large file uploads - client_max_body_size 50000M; - - # set timeout - proxy_read_timeout 600s; - proxy_send_timeout 600s; - send_timeout 600s; - proxy_redirect off; - ''; - }; postgres = { enable = true; extraUsers = { @@ -53,6 +36,27 @@ in { ]; }; } + (lib.mkIf config.host.reverse_proxy.enable { + host = { + reverse_proxy.subdomains.${config.services.immich.subdomain} = { + target = "http://localhost:${toString config.services.immich.port}"; + + websockets.enable = true; + forwardHeaders.enable = true; + + extraConfig = '' + # allow large file uploads + client_max_body_size 50000M; + + # set timeout + proxy_read_timeout 600s; + proxy_send_timeout 600s; + send_timeout 600s; + proxy_redirect off; + ''; + }; + }; + }) (lib.mkIf config.services.fail2ban.enable { environment.etc = { "fail2ban/filter.d/immich.local".text = lib.mkIf config.services.immich.enable ( diff --git a/modules/nixos-modules/server/jellyfin.nix b/modules/nixos-modules/server/jellyfin.nix index 294c8e1..85c870f 100644 --- a/modules/nixos-modules/server/jellyfin.nix +++ b/modules/nixos-modules/server/jellyfin.nix @@ -30,6 +30,20 @@ in { config = lib.mkIf config.services.jellyfin.enable ( lib.mkMerge [ { + environment.systemPackages = [ + pkgs.jellyfin + pkgs.jellyfin-web + pkgs.jellyfin-ffmpeg + ]; + + networking.firewall.allowedTCPPorts = [jellyfinPort dlanPort]; + + systemd.tmpfiles.rules = [ + "d ${config.services.jellyfin.media_directory} 2770 jellyfin jellyfin_media" + "A ${config.services.jellyfin.media_directory} - - - - u:jellyfin:rwX,g:jellyfin_media:rwX,o::-" + ]; + } + (lib.mkIf config.host.reverse_proxy.enable { host.reverse_proxy.subdomains.jellyfin = { target = "http://localhost:${toString jellyfinPort}"; @@ -45,19 +59,7 @@ in { proxy_buffering off; ''; }; - environment.systemPackages = [ - pkgs.jellyfin - pkgs.jellyfin-web - pkgs.jellyfin-ffmpeg - ]; - - networking.firewall.allowedTCPPorts = [jellyfinPort dlanPort]; - - systemd.tmpfiles.rules = [ - "d ${config.services.jellyfin.media_directory} 2770 jellyfin jellyfin_media" - "A ${config.services.jellyfin.media_directory} - - - - u:jellyfin:rwX,g:jellyfin_media:rwX,o::-" - ]; - } + }) (lib.mkIf config.services.fail2ban.enable { environment.etc = { "fail2ban/filter.d/jellyfin.local".text = ( diff --git a/modules/nixos-modules/server/paperless.nix b/modules/nixos-modules/server/paperless.nix index b97c48d..303d742 100644 --- a/modules/nixos-modules/server/paperless.nix +++ b/modules/nixos-modules/server/paperless.nix @@ -24,17 +24,6 @@ in { config = lib.mkIf config.services.paperless.enable (lib.mkMerge [ { host = { - reverse_proxy.subdomains.${config.services.paperless.subdomain} = { - target = "http://${config.services.paperless.address}:${toString config.services.paperless.port}"; - - websockets.enable = true; - forwardHeaders.enable = true; - - extraConfig = '' - # allow large file uploads - client_max_body_size 50000M; - ''; - }; postgres = { enable = true; extraUsers = { @@ -61,6 +50,21 @@ in { }; }; } + (lib.mkIf config.host.reverse_proxy.enable { + host = { + reverse_proxy.subdomains.${config.services.paperless.subdomain} = { + target = "http://${config.services.paperless.address}:${toString config.services.paperless.port}"; + + websockets.enable = true; + forwardHeaders.enable = true; + + extraConfig = '' + # allow large file uploads + client_max_body_size 50000M; + ''; + }; + }; + }) (lib.mkIf config.services.fail2ban.enable { environment.etc = { "fail2ban/filter.d/paperless.local".text = ( diff --git a/modules/nixos-modules/server/searx.nix b/modules/nixos-modules/server/searx.nix index d357308..0e547af 100644 --- a/modules/nixos-modules/server/searx.nix +++ b/modules/nixos-modules/server/searx.nix @@ -12,61 +12,67 @@ }; }; - config = lib.mkIf config.services.searx.enable { - sops.secrets = { - "services/searx" = { - sopsFile = "${inputs.secrets}/defiant-services.yaml"; - }; - }; - host = { - reverse_proxy.subdomains.searx = { - subdomain = config.services.searx.subdomain; - target = "http://localhost:${toString config.services.searx.settings.server.port}"; - }; - }; - services.searx = { - environmentFile = config.sops.secrets."services/searx".path; - - # Rate limiting - limiterSettings = { - real_ip = { - x_for = 1; - ipv4_prefix = 32; - ipv6_prefix = 56; - }; - - botdetection = { - ip_limit = { - filter_link_local = true; - link_token = true; + config = lib.mkIf config.services.searx.enable ( + lib.mkMerge [ + { + sops.secrets = { + "services/searx" = { + sopsFile = "${inputs.secrets}/defiant-services.yaml"; }; }; - }; + services.searx = { + environmentFile = config.sops.secrets."services/searx".path; - settings = { - server = { - port = 8083; - secret_key = "@SEARXNG_SECRET@"; + # Rate limiting + limiterSettings = { + real_ip = { + x_for = 1; + ipv4_prefix = 32; + ipv6_prefix = 56; + }; + + botdetection = { + ip_limit = { + filter_link_local = true; + link_token = true; + }; + }; + }; + + settings = { + server = { + port = 8083; + secret_key = "@SEARXNG_SECRET@"; + }; + + # Search engine settings + search = { + safe_search = 2; + autocomplete_min = 2; + autocomplete = "duckduckgo"; + }; + + # Enabled plugins + enabled_plugins = [ + "Basic Calculator" + "Hash plugin" + "Tor check plugin" + "Open Access DOI rewrite" + "Hostnames plugin" + "Unit converter plugin" + "Tracker URL remover" + ]; + }; }; - - # Search engine settings - search = { - safe_search = 2; - autocomplete_min = 2; - autocomplete = "duckduckgo"; + } + (lib.mkIf config.host.reverse_proxy.enable { + host = { + reverse_proxy.subdomains.searx = { + subdomain = config.services.searx.subdomain; + target = "http://localhost:${toString config.services.searx.settings.server.port}"; + }; }; - - # Enabled plugins - enabled_plugins = [ - "Basic Calculator" - "Hash plugin" - "Tor check plugin" - "Open Access DOI rewrite" - "Hostnames plugin" - "Unit converter plugin" - "Tracker URL remover" - ]; - }; - }; - }; + }) + ] + ); } From 84b204f8b1553aa25a0831ec25f03de62df3d7b1 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Mon, 15 Sep 2025 10:40:34 -0500 Subject: [PATCH 19/62] feat: created env config for panoramax --- modules/nixos-modules/server/panoramax.nix | 191 +++++++++++++++++++-- 1 file changed, 177 insertions(+), 14 deletions(-) diff --git a/modules/nixos-modules/server/panoramax.nix b/modules/nixos-modules/server/panoramax.nix index f7d305f..5196a2d 100644 --- a/modules/nixos-modules/server/panoramax.nix +++ b/modules/nixos-modules/server/panoramax.nix @@ -4,8 +4,28 @@ pkgs, osConfig, ... -}: let - cfg = config.services.panoramax; +}: +with lib; let + envContent = '' + # Panoramax Configuration + FLASK_APP=geovisio + ${optionalString (config.services.panoramax.database.url != null) "DB_URL=${config.services.panoramax.database.url}"} + ${optionalString (config.services.panoramax.database.url == null && config.services.panoramax.database.port != null) "DB_PORT=${toString config.services.panoramax.database.port}"} + ${optionalString (config.services.panoramax.database.url == null && config.services.panoramax.database.host != null) "DB_HOST=${config.services.panoramax.database.host}"} + ${optionalString (config.services.panoramax.database.url == null && config.services.panoramax.database.username != null) "DB_USERNAME=${config.services.panoramax.database.username}"} + ${optionalString (config.services.panoramax.database.url == null && config.services.panoramax.database.password != null) "DB_PASSWORD=${config.services.panoramax.database.password}"} + ${optionalString (config.services.panoramax.database.url == null && config.services.panoramax.database.name != null) "DB_NAME=${config.services.panoramax.database.name}"} + ${optionalString (config.services.panoramax.storage.fsUrl != null) "FS_URL=${config.services.panoramax.storage.fsUrl}"} + ${optionalString (config.services.panoramax.infrastructure.nbProxies != null) "INFRA_NB_PROXIES=${toString config.services.panoramax.infrastructure.nbProxies}"} + ${optionalString (config.services.panoramax.flask.secretKey != null) "FLASK_SECRET_KEY=${config.services.panoramax.flask.secretKey}"} + ${optionalString (config.services.panoramax.flask.sessionCookieDomain != null) "FLASK_SESSION_COOKIE_DOMAIN=${config.services.panoramax.flask.sessionCookieDomain}"} + ${optionalString (config.services.panoramax.api.pictures.licenseSpdxId != null) "API_PICTURES_LICENSE_SPDX_ID=${config.services.panoramax.api.pictures.licenseSpdxId}"} + ${optionalString (config.services.panoramax.api.pictures.licenseUrl != null) "API_PICTURES_LICENSE_URL=${config.services.panoramax.api.pictures.licenseUrl}"} + ${optionalString (config.services.panoramax.port != null) "PORT=${toString config.services.panoramax.port}"} + ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name}=${value}") config.services.panoramax.extraEnvironment)} + ''; + + envFile = pkgs.writeText "panoramax.env" envContent; in { options.services.panoramax = { enable = lib.mkEnableOption "panoramax"; @@ -16,23 +36,166 @@ in { description = "The panoramax package to use"; }; - # TODO: create configs - # TODO: auto config db - # config = { - # DB_PORT = lib.mkOption {}; - # DB_HOST = lib.mkOption {}; - # DB_USERNAME = lib.mkOption {}; - # DB_PASSWORD = lib.mkOption {}; - # DB_NAME = lib.mkOption {}; - # FS_URL = lib.mkOption {}; - # }; + # TODO: sgblur config + port = mkOption { + type = types.nullOr types.port; + default = 5000; + description = "Port for the Panoramax service"; + }; + + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Host to bind the Panoramax service to"; + }; + + urlScheme = mkOption { + type = types.enum ["http" "https"]; + default = "https"; + description = "URL scheme for the application"; + }; + + database = { + url = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Complete database URL connection string (e.g., "postgresql://user:password@host:port/dbname"). + If provided, individual database options (host, port, username, password, name) are ignored. + ''; + }; + + port = mkOption { + type = types.nullOr types.port; + default = 5432; + description = "Database port (ignored if database.url is set)"; + }; + + host = mkOption { + type = types.nullOr types.str; + default = "localhost"; + description = "Database host (ignored if database.url is set)"; + }; + + username = mkOption { + type = types.nullOr types.str; + default = "panoramax"; + description = "Database username (ignored if database.url is set)"; + }; + + password = mkOption { + type = types.nullOr types.str; + default = null; + description = "Database password (ignored if database.url is set)"; + }; + + name = mkOption { + type = types.nullOr types.str; + default = "panoramax"; + description = "Database name (ignored if database.url is set)"; + }; + }; + + storage = { + fsUrl = mkOption { + type = types.nullOr types.str; + default = "/var/lib/panoramax/storage"; + description = "File system URL for storage"; + }; + }; + + infrastructure = { + nbProxies = mkOption { + type = types.nullOr types.int; + default = 1; + description = "Number of proxies in front of the application"; + }; + }; + + flask = { + secretKey = mkOption { + type = types.nullOr types.str; + default = null; + description = "Flask secret key for session security"; + }; + + sessionCookieDomain = mkOption { + type = types.nullOr types.str; + default = null; + description = "Flask session cookie domain"; + }; + }; + + api = { + pictures = { + licenseSpdxId = mkOption { + type = types.nullOr types.str; + default = null; + description = "SPDX license identifier for API pictures"; + }; + + licenseUrl = mkOption { + type = types.nullOr types.str; + default = null; + description = "License URL for API pictures"; + }; + }; + }; + + extraEnvironment = mkOption { + type = types.attrsOf types.str; + default = {}; + description = "Additional environment variables"; + example = { + CUSTOM_SETTING = "value"; + DEBUG = "true"; + }; + }; }; - config = lib.mkIf cfg.enable ( + config = lib.mkIf config.services.panoramax.enable ( lib.mkMerge [ { - # TODO: configure options for the package + environment.systemPackages = with pkgs; [ + config.services.panoramax.package + python3Packages.waitress + ]; + + systemd.services.panoramax = { + description = "Panoramax Service"; + after = ["network.target"]; + wantedBy = ["multi-user.target"]; + serviceConfig = { + ExecStart = "${pkgs.python3Packages.waitress}/bin/waitress-serve --env-file=${envFile} --host=${config.services.panoramax.host} --port=${toString config.services.panoramax.port} --url-scheme=${config.services.panoramax.urlScheme} --call geovisio:create_app"; + Restart = "always"; + User = "panoramax"; + Group = "panoramax"; + WorkingDirectory = "/var/lib/panoramax"; + Environment = "PYTHONPATH=${config.services.panoramax.package}/lib/python3.11/site-packages"; + }; + }; + + users.users.panoramax = { + isSystemUser = true; + group = "panoramax"; + home = "/var/lib/panoramax"; + createHome = true; + }; + + users.groups.panoramax = {}; + + systemd.tmpfiles.rules = [ + "d /var/lib/panoramax 0755 panoramax panoramax -" + "d ${config.services.panoramax.storage.fsUrl} 0755 panoramax panoramax -" + ]; + + # TODO: auto config db } + ( + lib.mkIf config.host.reverse_proxy.enable { + # TODO: configure reverse proxy here + } + ) ( lib.mkIf config.services.fail2ban { # TODO: configure options for fail2ban From 376cb934c322be84390c57f30ddb234f6a45a50f Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Mon, 15 Sep 2025 11:29:43 -0500 Subject: [PATCH 20/62] refactor: added asseration for db config --- modules/nixos-modules/server/panoramax.nix | 68 +++++++++++++++++----- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/modules/nixos-modules/server/panoramax.nix b/modules/nixos-modules/server/panoramax.nix index 5196a2d..0ebd82f 100644 --- a/modules/nixos-modules/server/panoramax.nix +++ b/modules/nixos-modules/server/panoramax.nix @@ -6,23 +6,40 @@ ... }: with lib; let + cfg = config.services.panoramax; + + # Database configuration assertions + dbUrlConfigured = cfg.database.url != null; + individualDbConfigured = all (x: x != null) [ + cfg.database.host + cfg.database.port + cfg.database.username + cfg.database.password + cfg.database.name + ]; + envContent = '' # Panoramax Configuration FLASK_APP=geovisio - ${optionalString (config.services.panoramax.database.url != null) "DB_URL=${config.services.panoramax.database.url}"} - ${optionalString (config.services.panoramax.database.url == null && config.services.panoramax.database.port != null) "DB_PORT=${toString config.services.panoramax.database.port}"} - ${optionalString (config.services.panoramax.database.url == null && config.services.panoramax.database.host != null) "DB_HOST=${config.services.panoramax.database.host}"} - ${optionalString (config.services.panoramax.database.url == null && config.services.panoramax.database.username != null) "DB_USERNAME=${config.services.panoramax.database.username}"} - ${optionalString (config.services.panoramax.database.url == null && config.services.panoramax.database.password != null) "DB_PASSWORD=${config.services.panoramax.database.password}"} - ${optionalString (config.services.panoramax.database.url == null && config.services.panoramax.database.name != null) "DB_NAME=${config.services.panoramax.database.name}"} - ${optionalString (config.services.panoramax.storage.fsUrl != null) "FS_URL=${config.services.panoramax.storage.fsUrl}"} - ${optionalString (config.services.panoramax.infrastructure.nbProxies != null) "INFRA_NB_PROXIES=${toString config.services.panoramax.infrastructure.nbProxies}"} - ${optionalString (config.services.panoramax.flask.secretKey != null) "FLASK_SECRET_KEY=${config.services.panoramax.flask.secretKey}"} - ${optionalString (config.services.panoramax.flask.sessionCookieDomain != null) "FLASK_SESSION_COOKIE_DOMAIN=${config.services.panoramax.flask.sessionCookieDomain}"} - ${optionalString (config.services.panoramax.api.pictures.licenseSpdxId != null) "API_PICTURES_LICENSE_SPDX_ID=${config.services.panoramax.api.pictures.licenseSpdxId}"} - ${optionalString (config.services.panoramax.api.pictures.licenseUrl != null) "API_PICTURES_LICENSE_URL=${config.services.panoramax.api.pictures.licenseUrl}"} - ${optionalString (config.services.panoramax.port != null) "PORT=${toString config.services.panoramax.port}"} - ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name}=${value}") config.services.panoramax.extraEnvironment)} + ${ + if dbUrlConfigured + then "DB_URL=${cfg.database.url}" + else '' + DB_HOST=${cfg.database.host} + DB_PORT=${toString cfg.database.port} + DB_USERNAME=${cfg.database.username} + DB_PASSWORD=${cfg.database.password} + DB_NAME=${cfg.database.name} + '' + } + ${optionalString (cfg.storage.fsUrl != null) "FS_URL=${cfg.storage.fsUrl}"} + ${optionalString (cfg.infrastructure.nbProxies != null) "INFRA_NB_PROXIES=${toString cfg.infrastructure.nbProxies}"} + ${optionalString (cfg.flask.secretKey != null) "FLASK_SECRET_KEY=${cfg.flask.secretKey}"} + ${optionalString (cfg.flask.sessionCookieDomain != null) "FLASK_SESSION_COOKIE_DOMAIN=${cfg.flask.sessionCookieDomain}"} + ${optionalString (cfg.api.pictures.licenseSpdxId != null) "API_PICTURES_LICENSE_SPDX_ID=${cfg.api.pictures.licenseSpdxId}"} + ${optionalString (cfg.api.pictures.licenseUrl != null) "API_PICTURES_LICENSE_URL=${cfg.api.pictures.licenseUrl}"} + ${optionalString (cfg.port != null) "PORT=${toString cfg.port}"} + ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name}=${value}") cfg.extraEnvironment)} ''; envFile = pkgs.writeText "panoramax.env" envContent; @@ -189,6 +206,29 @@ in { "d ${config.services.panoramax.storage.fsUrl} 0755 panoramax panoramax -" ]; + assertions = [ + { + assertion = dbUrlConfigured || individualDbConfigured; + message = '' + Panoramax database configuration requires either: + - A complete database URL (services.panoramax.database.url), OR + - All individual database options (host, port, username, password, name) + + Currently configured: + - database.url: ${ + if dbUrlConfigured + then "✓ configured" + else "✗ not configured" + } + - individual options: ${ + if individualDbConfigured + then "✓ all configured" + else "✗ some missing" + } + ''; + } + ]; + # TODO: auto config db } ( From 1d0f51c70ad69b103c630281a75dc65b2776df26 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Mon, 15 Sep 2025 11:36:12 -0500 Subject: [PATCH 21/62] chore: addede panoramax.nix to server modules --- modules/nixos-modules/server/default.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/nixos-modules/server/default.nix b/modules/nixos-modules/server/default.nix index 4ca50e2..87f3dae 100644 --- a/modules/nixos-modules/server/default.nix +++ b/modules/nixos-modules/server/default.nix @@ -14,5 +14,6 @@ ./qbittorent.nix ./paperless.nix ./actual.nix + ./panoramax.nix ]; } From c0579f55dc56e90af409dfbab22bafba4b3338c9 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Mon, 15 Sep 2025 13:36:07 -0500 Subject: [PATCH 22/62] feat: created sgblur package --- modules/common-modules/pkgs/default.nix | 3 ++ modules/common-modules/pkgs/sgblur.nix | 65 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 modules/common-modules/pkgs/sgblur.nix diff --git a/modules/common-modules/pkgs/default.nix b/modules/common-modules/pkgs/default.nix index f1235cc..28141c8 100644 --- a/modules/common-modules/pkgs/default.nix +++ b/modules/common-modules/pkgs/default.nix @@ -28,5 +28,8 @@ (final: prev: { panoramax = pkgs.python3.pkgs.callPackage ./panoramax.nix {}; }) + (final: prev: { + sgblur = pkgs.python3.pkgs.callPackage ./sgblur.nix {}; + }) ]; } diff --git a/modules/common-modules/pkgs/sgblur.nix b/modules/common-modules/pkgs/sgblur.nix new file mode 100644 index 0000000..d007b4e --- /dev/null +++ b/modules/common-modules/pkgs/sgblur.nix @@ -0,0 +1,65 @@ +{ + lib, + python3Packages, + fetchFromGitHub, + pkg-config, + libjpeg_turbo, + exiftran ? libjpeg_turbo, +}: +python3Packages.buildPythonPackage { + pname = "sgblur"; + version = "1.0.0"; + + pyproject = true; + + src = fetchFromGitHub { + owner = "cquest"; + repo = "sgblur"; + rev = "master"; + hash = "sha256-17wpif2sa021kaa1pbkry4l1967la1qd7knhngvxblrvd7jqqz4y="; + }; + + nativeBuildInputs = [ + pkg-config + ]; + + buildInputs = [ + libjpeg_turbo + exiftran + ]; + + build-system = with python3Packages; [ + setuptools + wheel + ]; + + dependencies = with python3Packages; [ + # Core dependencies from pyproject.toml + ultralytics + # pyturbojpeg # May need special handling + pillow + # uuid # Built into Python + # exifread + python-multipart + fastapi + uvicorn + requests + # piexif + pydantic-settings + pydantic + ]; + + # Skip tests as they may require GPU or specific setup + doCheck = false; + + # The package may have import issues due to system dependencies + pythonImportsCheck = []; + + meta = with lib; { + description = "Panoramax Speedy Gonzales Blurring Algorithm - AI-powered face and license plate blurring API"; + homepage = "https://github.com/cquest/sgblur"; + license = licenses.mit; + maintainers = []; + platforms = platforms.unix; + }; +} From 0f87d78271bd2ca520f70bc20cdda6b27a86537b Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Mon, 15 Sep 2025 14:02:16 -0500 Subject: [PATCH 23/62] feat: updated flake lock --- flake.lock | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/flake.lock b/flake.lock index 6ea6f9b..ca41b02 100644 --- a/flake.lock +++ b/flake.lock @@ -25,11 +25,11 @@ ] }, "locked": { - "lastModified": 1757255839, - "narHash": "sha256-XH33B1X888Xc/xEXhF1RPq/kzKElM0D5C9N6YdvOvIc=", + "lastModified": 1757508292, + "narHash": "sha256-7lVWL5bC6xBIMWWDal41LlGAG+9u2zUorqo3QCUL4p4=", "owner": "nix-community", "repo": "disko", - "rev": "c8a0e78d86b12ea67be6ed0f7cae7f9bfabae75a", + "rev": "146f45bee02b8bd88812cfce6ffc0f933788875a", "type": "github" }, "original": { @@ -46,11 +46,11 @@ }, "locked": { "dir": "pkgs/firefox-addons", - "lastModified": 1757304222, - "narHash": "sha256-s070stByAXxeCLgftTXxFxZ2ynJhghne4Y6cTuqGAaw=", + "lastModified": 1757822625, + "narHash": "sha256-w+V97GrUZK5Lt50DOzhmFGPf3coxfj4TTNHa0rHswuE=", "owner": "rycee", "repo": "nur-expressions", - "rev": "fa312c0175ffb82bc67da095439b9cb683ac52bd", + "rev": "5a1d5f5453eef0ea2510d9860d2f803911df6776", "type": "gitlab" }, "original": { @@ -118,11 +118,11 @@ ] }, "locked": { - "lastModified": 1757256385, - "narHash": "sha256-WK7tOhWwr15mipcckhDg2no/eSpM1nIh4C9le8HgHhk=", + "lastModified": 1757920978, + "narHash": "sha256-Mv16aegXLulgyDunijP6SPFJNm8lSXb2w3Q0X+vZ9TY=", "owner": "nix-community", "repo": "home-manager", - "rev": "f35703b412c67b48e97beb6e27a6ab96a084cd37", + "rev": "11cc5449c50e0e5b785be3dfcb88245232633eb8", "type": "github" }, "original": { @@ -175,11 +175,11 @@ ] }, "locked": { - "lastModified": 1757130842, - "narHash": "sha256-4i7KKuXesSZGUv0cLPLfxbmF1S72Gf/3aSypgvVkwuA=", + "lastModified": 1757430124, + "narHash": "sha256-MhDltfXesGH8VkGv3hmJ1QEKl1ChTIj9wmGAFfWj/Wk=", "owner": "LnL7", "repo": "nix-darwin", - "rev": "15f067638e2887c58c4b6ba1bdb65a0b61dc58c5", + "rev": "830b3f0b50045cf0bcfd4dab65fad05bf882e196", "type": "github" }, "original": { @@ -217,11 +217,11 @@ ] }, "locked": { - "lastModified": 1757296711, - "narHash": "sha256-7u9/tXUdmTj8x7ofet8aELLBlCHSoA+QOhYKheRdacM=", + "lastModified": 1757901553, + "narHash": "sha256-gW45THWkxnzWpPtjuaDeTnpKFB6i5cZmxk4WuGKhCNc=", "owner": "nix-community", "repo": "nix-vscode-extensions", - "rev": "ab9374ac8c162dacffcd4400e668fd7f9b6f173a", + "rev": "846f1334090a2c44d77850c00d0c17a27ad66618", "type": "github" }, "original": { @@ -232,11 +232,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1757103352, - "narHash": "sha256-PtT7ix43ss8PONJ1VJw3f6t2yAoGH+q462Sn8lrmWmk=", + "lastModified": 1757943327, + "narHash": "sha256-w6cDExPBqbq7fTLo4dZ1ozDGeq3yV6dSN4n/sAaS6OM=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "11b2a10c7be726321bb854403fdeec391e798bf0", + "rev": "67a709cfe5d0643dafd798b0b613ed579de8be05", "type": "github" }, "original": { @@ -264,11 +264,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1757068644, - "narHash": "sha256-NOrUtIhTkIIumj1E/Rsv1J37Yi3xGStISEo8tZm3KW4=", + "lastModified": 1757745802, + "narHash": "sha256-hLEO2TPj55KcUFUU1vgtHE9UEIOjRcH/4QbmfHNF820=", "owner": "nixos", "repo": "nixpkgs", - "rev": "8eb28adfa3dc4de28e792e3bf49fcf9007ca8ac9", + "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", "type": "github" }, "original": { @@ -319,11 +319,11 @@ ] }, "locked": { - "lastModified": 1754988908, - "narHash": "sha256-t+voe2961vCgrzPFtZxha0/kmFSHFobzF00sT8p9h0U=", + "lastModified": 1757847158, + "narHash": "sha256-TumOaykhZO8SOs/faz6GQhqkOcFLoQvESLSF1cJ4mZc=", "owner": "Mic92", "repo": "sops-nix", - "rev": "3223c7a92724b5d804e9988c6b447a0d09017d48", + "rev": "ee6f91c1c11acf7957d94a130de77561ec24b8ab", "type": "github" }, "original": { From dbd5d36913bd38c4485e46d8992f3bd861afa558 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Mon, 15 Sep 2025 14:54:31 -0500 Subject: [PATCH 24/62] feat: drafted out reverse proxy config for panoramax --- modules/nixos-modules/server/panoramax.nix | 26 +++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/modules/nixos-modules/server/panoramax.nix b/modules/nixos-modules/server/panoramax.nix index 0ebd82f..ad21738 100644 --- a/modules/nixos-modules/server/panoramax.nix +++ b/modules/nixos-modules/server/panoramax.nix @@ -53,6 +53,12 @@ in { description = "The panoramax package to use"; }; + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that panoramax will be hosted at"; + default = "panoramax"; + }; + # TODO: sgblur config port = mkOption { type = types.nullOr types.port; @@ -233,7 +239,25 @@ in { } ( lib.mkIf config.host.reverse_proxy.enable { - # TODO: configure reverse proxy here + host = { + reverse_proxy.subdomains.${config.services.panoramax.subdomain} = { + target = "http://localhost:${toString config.services.panoramax.port}"; + + websockets.enable = true; + forwardHeaders.enable = true; + + extraConfig = '' + # allow large file uploads for panoramic images + client_max_body_size 100M; + + # set timeout for image processing + proxy_read_timeout 300s; + proxy_send_timeout 300s; + send_timeout 300s; + proxy_redirect off; + ''; + }; + }; } ) ( From 01325c306867c786f2828594ec0eef1fe98c64ab Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Mon, 15 Sep 2025 15:25:05 -0500 Subject: [PATCH 25/62] feat: drafted out database configuration and sgblur config --- modules/nixos-modules/server/panoramax.nix | 229 ++++++++++++++++----- 1 file changed, 181 insertions(+), 48 deletions(-) diff --git a/modules/nixos-modules/server/panoramax.nix b/modules/nixos-modules/server/panoramax.nix index ad21738..dd026cd 100644 --- a/modules/nixos-modules/server/panoramax.nix +++ b/modules/nixos-modules/server/panoramax.nix @@ -6,16 +6,14 @@ ... }: with lib; let - cfg = config.services.panoramax; - # Database configuration assertions - dbUrlConfigured = cfg.database.url != null; + dbUrlConfigured = config.services.panoramax.database.url != null; individualDbConfigured = all (x: x != null) [ - cfg.database.host - cfg.database.port - cfg.database.username - cfg.database.password - cfg.database.name + config.services.panoramax.database.host + config.services.panoramax.database.port + config.services.panoramax.database.username + config.services.panoramax.database.password + config.services.panoramax.database.name ]; envContent = '' @@ -23,23 +21,24 @@ with lib; let FLASK_APP=geovisio ${ if dbUrlConfigured - then "DB_URL=${cfg.database.url}" + then "DB_URL=${config.services.panoramax.database.url}" else '' - DB_HOST=${cfg.database.host} - DB_PORT=${toString cfg.database.port} - DB_USERNAME=${cfg.database.username} - DB_PASSWORD=${cfg.database.password} - DB_NAME=${cfg.database.name} + DB_HOST=${config.services.panoramax.database.host} + DB_PORT=${toString config.services.panoramax.database.port} + DB_USERNAME=${config.services.panoramax.database.username} + DB_PASSWORD=${config.services.panoramax.database.password} + DB_NAME=${config.services.panoramax.database.name} '' } - ${optionalString (cfg.storage.fsUrl != null) "FS_URL=${cfg.storage.fsUrl}"} - ${optionalString (cfg.infrastructure.nbProxies != null) "INFRA_NB_PROXIES=${toString cfg.infrastructure.nbProxies}"} - ${optionalString (cfg.flask.secretKey != null) "FLASK_SECRET_KEY=${cfg.flask.secretKey}"} - ${optionalString (cfg.flask.sessionCookieDomain != null) "FLASK_SESSION_COOKIE_DOMAIN=${cfg.flask.sessionCookieDomain}"} - ${optionalString (cfg.api.pictures.licenseSpdxId != null) "API_PICTURES_LICENSE_SPDX_ID=${cfg.api.pictures.licenseSpdxId}"} - ${optionalString (cfg.api.pictures.licenseUrl != null) "API_PICTURES_LICENSE_URL=${cfg.api.pictures.licenseUrl}"} - ${optionalString (cfg.port != null) "PORT=${toString cfg.port}"} - ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name}=${value}") cfg.extraEnvironment)} + ${optionalString (config.services.panoramax.storage.fsUrl != null) "FS_URL=${config.services.panoramax.storage.fsUrl}"} + ${optionalString (config.services.panoramax.infrastructure.nbProxies != null) "INFRA_NB_PROXIES=${toString config.services.panoramax.infrastructure.nbProxies}"} + ${optionalString (config.services.panoramax.flask.secretKey != null) "FLASK_SECRET_KEY=${config.services.panoramax.flask.secretKey}"} + ${optionalString (config.services.panoramax.flask.sessionCookieDomain != null) "FLASK_SESSION_COOKIE_DOMAIN=${config.services.panoramax.flask.sessionCookieDomain}"} + ${optionalString (config.services.panoramax.api.pictures.licenseSpdxId != null) "API_PICTURES_LICENSE_SPDX_ID=${config.services.panoramax.api.pictures.licenseSpdxId}"} + ${optionalString (config.services.panoramax.api.pictures.licenseUrl != null) "API_PICTURES_LICENSE_URL=${config.services.panoramax.api.pictures.licenseUrl}"} + ${optionalString (config.services.panoramax.port != null) "PORT=${toString config.services.panoramax.port}"} + ${optionalString (config.services.panoramax.sgblur.enable) "SGBLUR_API_URL=${config.services.panoramax.sgblur.url}"} + ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name}=${value}") config.services.panoramax.extraEnvironment)} ''; envFile = pkgs.writeText "panoramax.env" envContent; @@ -59,26 +58,13 @@ in { default = "panoramax"; }; - # TODO: sgblur config - port = mkOption { - type = types.nullOr types.port; - default = 5000; - description = "Port for the Panoramax service"; - }; - - host = mkOption { - type = types.str; - default = "127.0.0.1"; - description = "Host to bind the Panoramax service to"; - }; - - urlScheme = mkOption { - type = types.enum ["http" "https"]; - default = "https"; - description = "URL scheme for the application"; - }; - database = { + createDB = mkOption { + type = types.bool; + default = true; + description = "Whether to automatically create the database and user"; + }; + url = mkOption { type = types.nullOr types.str; default = null; @@ -113,12 +99,62 @@ in { }; name = mkOption { - type = types.nullOr types.str; + type = types.str; default = "panoramax"; description = "Database name (ignored if database.url is set)"; }; }; + sgblur = { + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable sgblur integration for face and license plate blurring"; + }; + + package = mkOption { + type = types.package; + default = pkgs.sgblur; + description = "The sgblur package to use"; + }; + + port = mkOption { + type = types.port; + default = 8080; + description = "Port for the sgblur service"; + }; + + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Host to bind the sgblur service to"; + }; + + url = mkOption { + type = types.str; + default = "http://127.0.0.1:8080"; + description = "URL where sgblur service is accessible"; + }; + }; + + port = mkOption { + type = types.nullOr types.port; + default = 5000; + description = "Port for the Panoramax service"; + }; + + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Host to bind the Panoramax service to"; + }; + + urlScheme = mkOption { + type = types.enum ["http" "https"]; + default = "https"; + description = "URL scheme for the application"; + }; + storage = { fsUrl = mkOption { type = types.nullOr types.str; @@ -179,10 +215,14 @@ in { config = lib.mkIf config.services.panoramax.enable ( lib.mkMerge [ { - environment.systemPackages = with pkgs; [ - config.services.panoramax.package - python3Packages.waitress - ]; + environment.systemPackages = with pkgs; + [ + config.services.panoramax.package + python3Packages.waitress + ] + ++ optionals config.services.panoramax.sgblur.enable [ + config.services.panoramax.sgblur.package + ]; systemd.services.panoramax = { description = "Panoramax Service"; @@ -233,10 +273,103 @@ in { } ''; } - ]; + { + assertion = !config.services.panoramax.database.createDB || config.services.panoramax.database.url == null || (lib.hasPrefix "/run/" config.services.panoramax.database.url || lib.hasPrefix "unix:" config.services.panoramax.database.url || lib.hasPrefix "/" config.services.panoramax.database.host); + message = '' + Panoramax createDB option can only be used with socket connections when a database URL is provided. + Socket connections are identified by: + - URLs starting with "unix:" + - URLs starting with "/run/" + - Host paths starting with "/" - # TODO: auto config db + Current configuration: + - createDB: ${lib.boolToString config.services.panoramax.database.createDB} + - database.url: ${ + if config.services.panoramax.database.url != null + then config.services.panoramax.database.url + else "not set" + } + - database.host: ${config.services.panoramax.database.host} + ''; + } + ]; } + ( + lib.mkIf config.services.panoramax.sgblur.enable { + systemd.services.sgblur = { + description = "SGBlur AI-powered face and license plate blurring service"; + after = ["network.target"]; + wantedBy = ["multi-user.target"]; + serviceConfig = { + ExecStart = "${config.services.panoramax.sgblur.package}/bin/uvicorn sgblur.main:app --host ${config.services.panoramax.sgblur.host} --port ${toString config.services.panoramax.sgblur.port}"; + Restart = "always"; + User = "sgblur"; + Group = "sgblur"; + WorkingDirectory = "/var/lib/sgblur"; + Environment = "PYTHONPATH=${config.services.panoramax.sgblur.package}/lib/python3.11/site-packages"; + }; + }; + + users.users.sgblur = { + isSystemUser = true; + group = "sgblur"; + home = "/var/lib/sgblur"; + createHome = true; + }; + + users.groups.sgblur = {}; + + systemd.tmpfiles.rules = [ + "d /var/lib/sgblur 0755 sgblur sgblur -" + ]; + + # Update panoramax service dependencies when sgblur is enabled + systemd.services.panoramax = { + after = ["sgblur.service"]; + wants = ["sgblur.service"]; + }; + } + ) + ( + lib.mkIf config.services.panoramax.database.createDB { + services.postgresql = { + enable = true; + ensureDatabases = [config.services.panoramax.database.name]; + ensureUsers = [ + { + name = config.services.panoramax.database.username; + ensureDBOwnership = true; + ensureClauses.login = true; + } + ]; + extensions = ps: with ps; [postgis]; + settings = { + shared_preload_libraries = ["postgis"]; + }; + }; + + systemd.services.postgresql.serviceConfig.ExecStartPost = let + sqlFile = pkgs.writeText "panoramax-postgis-setup.sql" '' + CREATE EXTENSION IF NOT EXISTS postgis; + CREATE EXTENSION IF NOT EXISTS postgis_topology; + CREATE EXTENSION IF NOT EXISTS fuzzystrmatch; + CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder; + + ALTER SCHEMA public OWNER TO ${config.services.panoramax.database.username}; + GRANT ALL ON SCHEMA public TO ${config.services.panoramax.database.username}; + ''; + in [ + '' + ${lib.getExe' config.services.postgresql.package "psql"} -d "${config.services.panoramax.database.name}" -f "${sqlFile}" + '' + ]; + + systemd.services.panoramax = { + after = ["postgresql.service"]; + requires = ["postgresql.service"]; + }; + } + ) ( lib.mkIf config.host.reverse_proxy.enable { host = { From b2e5ae1f98be4cece2bbab8ae1a9c7ba5d7df9aa Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Tue, 16 Sep 2025 09:58:35 -0500 Subject: [PATCH 26/62] build: updated flake lock --- flake.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/flake.lock b/flake.lock index ca41b02..b6e48bb 100644 --- a/flake.lock +++ b/flake.lock @@ -46,11 +46,11 @@ }, "locked": { "dir": "pkgs/firefox-addons", - "lastModified": 1757822625, - "narHash": "sha256-w+V97GrUZK5Lt50DOzhmFGPf3coxfj4TTNHa0rHswuE=", + "lastModified": 1757995413, + "narHash": "sha256-vaU/7/PXoym6vnspGxhR29V9klGe9iy9zmp6x7w38f8=", "owner": "rycee", "repo": "nur-expressions", - "rev": "5a1d5f5453eef0ea2510d9860d2f803911df6776", + "rev": "4ae8996b3e139926c784acd22824cde46cd28833", "type": "gitlab" }, "original": { @@ -118,11 +118,11 @@ ] }, "locked": { - "lastModified": 1757920978, - "narHash": "sha256-Mv16aegXLulgyDunijP6SPFJNm8lSXb2w3Q0X+vZ9TY=", + "lastModified": 1757997814, + "narHash": "sha256-F+1aoG+3NH4jDDEmhnDUReISyq6kQBBuktTUqCUWSiw=", "owner": "nix-community", "repo": "home-manager", - "rev": "11cc5449c50e0e5b785be3dfcb88245232633eb8", + "rev": "5820376beb804de9acf07debaaff1ac84728b708", "type": "github" }, "original": { @@ -217,11 +217,11 @@ ] }, "locked": { - "lastModified": 1757901553, - "narHash": "sha256-gW45THWkxnzWpPtjuaDeTnpKFB6i5cZmxk4WuGKhCNc=", + "lastModified": 1757987448, + "narHash": "sha256-ltDT7EIfLHV42p99HnDfDviC8jN7tcOed1qsLEFypl8=", "owner": "nix-community", "repo": "nix-vscode-extensions", - "rev": "846f1334090a2c44d77850c00d0c17a27ad66618", + "rev": "e496568b0e69d9d54c8cfef96ed1370952ad9786", "type": "github" }, "original": { @@ -319,11 +319,11 @@ ] }, "locked": { - "lastModified": 1757847158, - "narHash": "sha256-TumOaykhZO8SOs/faz6GQhqkOcFLoQvESLSF1cJ4mZc=", + "lastModified": 1758007585, + "narHash": "sha256-HYnwlbY6RE5xVd5rh0bYw77pnD8lOgbT4mlrfjgNZ0c=", "owner": "Mic92", "repo": "sops-nix", - "rev": "ee6f91c1c11acf7957d94a130de77561ec24b8ab", + "rev": "f77d4cfa075c3de66fc9976b80e0c4fc69e2c139", "type": "github" }, "original": { From cdeb4e108b4604acf9ec15bd2e9bcfb906a2f0a2 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Tue, 16 Sep 2025 10:14:33 -0500 Subject: [PATCH 27/62] refactor: split server modules into smaller more manageable files --- modules/nixos-modules/server/actual.nix | 56 --- modules/nixos-modules/server/actual/const.nix | 3 + .../nixos-modules/server/actual/default.nix | 34 ++ .../nixos-modules/server/actual/fail2ban.nix | 9 + .../server/actual/impermanence.nix | 26 ++ modules/nixos-modules/server/actual/proxy.nix | 13 + modules/nixos-modules/server/default.nix | 23 +- modules/nixos-modules/server/forgejo.nix | 128 ------ .../nixos-modules/server/forgejo/const.nix | 4 + .../nixos-modules/server/forgejo/database.nix | 41 ++ .../nixos-modules/server/forgejo/default.nix | 61 +++ .../nixos-modules/server/forgejo/fail2ban.nix | 32 ++ .../server/forgejo/impermanence.nix | 28 ++ .../nixos-modules/server/forgejo/proxy.nix | 18 + .../nixos-modules/server/home-assistant.nix | 230 ---------- .../server/home-assistant/database.nix | 56 +++ .../server/home-assistant/default.nix | 118 +++++ .../home-assistant/extensions/default.nix | 12 + .../home-assistant/extensions/jellyfin.nix | 9 + .../home-assistant/extensions/sonos.nix | 11 + .../home-assistant/extensions/wyoming.nix | 9 + .../server/home-assistant/fail2ban.nix | 39 ++ .../server/home-assistant/impermanence.nix | 26 ++ .../server/home-assistant/proxy.nix | 24 ++ modules/nixos-modules/server/immich.nix | 99 ----- .../nixos-modules/server/immich/database.nix | 26 ++ .../nixos-modules/server/immich/default.nix | 28 ++ .../nixos-modules/server/immich/fail2ban.nix | 26 ++ .../server/immich/impermanence.nix | 25 ++ modules/nixos-modules/server/immich/proxy.nix | 27 ++ modules/nixos-modules/server/jellyfin.nix | 147 ------- .../nixos-modules/server/jellyfin/default.nix | 48 +++ .../server/jellyfin/fail2ban.nix | 32 ++ .../server/jellyfin/impermanence.nix | 66 +++ .../nixos-modules/server/jellyfin/proxy.nix | 25 ++ modules/nixos-modules/server/panoramax.nix | 408 ------------------ .../server/panoramax/default.nix | 340 +++++++++++++++ .../server/panoramax/fail2ban.nix | 11 + .../server/panoramax/impermanence.nix | 14 + .../nixos-modules/server/panoramax/proxy.nix | 27 ++ modules/nixos-modules/server/paperless.nix | 113 ----- .../server/paperless/database.nix | 34 ++ .../server/paperless/default.nix | 40 ++ .../server/paperless/fail2ban.nix | 34 ++ .../server/paperless/impermanence.nix | 25 ++ .../nixos-modules/server/paperless/proxy.nix | 21 + modules/nixos-modules/server/searx.nix | 78 ---- .../nixos-modules/server/searx/default.nix | 71 +++ modules/nixos-modules/server/searx/proxy.nix | 14 + 49 files changed, 1519 insertions(+), 1270 deletions(-) delete mode 100644 modules/nixos-modules/server/actual.nix create mode 100644 modules/nixos-modules/server/actual/const.nix create mode 100644 modules/nixos-modules/server/actual/default.nix create mode 100644 modules/nixos-modules/server/actual/fail2ban.nix create mode 100644 modules/nixos-modules/server/actual/impermanence.nix create mode 100644 modules/nixos-modules/server/actual/proxy.nix delete mode 100644 modules/nixos-modules/server/forgejo.nix create mode 100644 modules/nixos-modules/server/forgejo/const.nix create mode 100644 modules/nixos-modules/server/forgejo/database.nix create mode 100644 modules/nixos-modules/server/forgejo/default.nix create mode 100644 modules/nixos-modules/server/forgejo/fail2ban.nix create mode 100644 modules/nixos-modules/server/forgejo/impermanence.nix create mode 100644 modules/nixos-modules/server/forgejo/proxy.nix delete mode 100644 modules/nixos-modules/server/home-assistant.nix create mode 100644 modules/nixos-modules/server/home-assistant/database.nix create mode 100644 modules/nixos-modules/server/home-assistant/default.nix create mode 100644 modules/nixos-modules/server/home-assistant/extensions/default.nix create mode 100644 modules/nixos-modules/server/home-assistant/extensions/jellyfin.nix create mode 100644 modules/nixos-modules/server/home-assistant/extensions/sonos.nix create mode 100644 modules/nixos-modules/server/home-assistant/extensions/wyoming.nix create mode 100644 modules/nixos-modules/server/home-assistant/fail2ban.nix create mode 100644 modules/nixos-modules/server/home-assistant/impermanence.nix create mode 100644 modules/nixos-modules/server/home-assistant/proxy.nix delete mode 100644 modules/nixos-modules/server/immich.nix create mode 100644 modules/nixos-modules/server/immich/database.nix create mode 100644 modules/nixos-modules/server/immich/default.nix create mode 100644 modules/nixos-modules/server/immich/fail2ban.nix create mode 100644 modules/nixos-modules/server/immich/impermanence.nix create mode 100644 modules/nixos-modules/server/immich/proxy.nix delete mode 100644 modules/nixos-modules/server/jellyfin.nix create mode 100644 modules/nixos-modules/server/jellyfin/default.nix create mode 100644 modules/nixos-modules/server/jellyfin/fail2ban.nix create mode 100644 modules/nixos-modules/server/jellyfin/impermanence.nix create mode 100644 modules/nixos-modules/server/jellyfin/proxy.nix delete mode 100644 modules/nixos-modules/server/panoramax.nix create mode 100644 modules/nixos-modules/server/panoramax/default.nix create mode 100644 modules/nixos-modules/server/panoramax/fail2ban.nix create mode 100644 modules/nixos-modules/server/panoramax/impermanence.nix create mode 100644 modules/nixos-modules/server/panoramax/proxy.nix delete mode 100644 modules/nixos-modules/server/paperless.nix create mode 100644 modules/nixos-modules/server/paperless/database.nix create mode 100644 modules/nixos-modules/server/paperless/default.nix create mode 100644 modules/nixos-modules/server/paperless/fail2ban.nix create mode 100644 modules/nixos-modules/server/paperless/impermanence.nix create mode 100644 modules/nixos-modules/server/paperless/proxy.nix delete mode 100644 modules/nixos-modules/server/searx.nix create mode 100644 modules/nixos-modules/server/searx/default.nix create mode 100644 modules/nixos-modules/server/searx/proxy.nix diff --git a/modules/nixos-modules/server/actual.nix b/modules/nixos-modules/server/actual.nix deleted file mode 100644 index 80f4fab..0000000 --- a/modules/nixos-modules/server/actual.nix +++ /dev/null @@ -1,56 +0,0 @@ -{ - lib, - config, - ... -}: let - dataDirectory = "/var/lib/actual/"; -in { - options.services.actual = { - subdomain = lib.mkOption { - type = lib.types.str; - default = "actual"; - description = "subdomain of base domain that actual will be hosted at"; - }; - }; - - config = lib.mkIf config.services.actual.enable (lib.mkMerge [ - { - systemd.tmpfiles.rules = [ - "d ${dataDirectory} 2770 actual actual" - ]; - - services.actual = { - settings = { - ACTUAL_DATA_DIR = dataDirectory; - }; - }; - } - (lib.mkIf config.host.reverse_proxy.enable { - host = { - reverse_proxy.subdomains.${config.services.actual.subdomain} = { - target = "http://localhost:${toString config.services.actual.settings.port}"; - }; - }; - }) - (lib.mkIf config.services.fail2ban.enable { - # TODO: configuration for fail2ban for actual - }) - (lib.mkIf config.host.impermanence.enable { - assertions = [ - { - assertion = config.services.actual.settings.ACTUAL_DATA_DIR == dataDirectory; - message = "actual data location does not match persistence"; - } - ]; - environment.persistence."/persist/system/root" = { - directories = [ - { - directory = dataDirectory; - user = "actual"; - group = "actual"; - } - ]; - }; - }) - ]); -} diff --git a/modules/nixos-modules/server/actual/const.nix b/modules/nixos-modules/server/actual/const.nix new file mode 100644 index 0000000..13b068e --- /dev/null +++ b/modules/nixos-modules/server/actual/const.nix @@ -0,0 +1,3 @@ +{ + dataDirectory = "/var/lib/actual/"; +} diff --git a/modules/nixos-modules/server/actual/default.nix b/modules/nixos-modules/server/actual/default.nix new file mode 100644 index 0000000..bef7a05 --- /dev/null +++ b/modules/nixos-modules/server/actual/default.nix @@ -0,0 +1,34 @@ +{ + lib, + config, + ... +}: let + const = import ./const.nix; + dataDirectory = const.dataDirectory; +in { + imports = [ + ./proxy.nix + ./fail2ban.nix + ./impermanence.nix + ]; + + options.services.actual = { + subdomain = lib.mkOption { + type = lib.types.str; + default = "actual"; + description = "subdomain of base domain that actual will be hosted at"; + }; + }; + + config = lib.mkIf config.services.actual.enable { + systemd.tmpfiles.rules = [ + "d ${dataDirectory} 2770 actual actual" + ]; + + services.actual = { + settings = { + ACTUAL_DATA_DIR = dataDirectory; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/actual/fail2ban.nix b/modules/nixos-modules/server/actual/fail2ban.nix new file mode 100644 index 0000000..3ad754e --- /dev/null +++ b/modules/nixos-modules/server/actual/fail2ban.nix @@ -0,0 +1,9 @@ +{ + lib, + config, + ... +}: { + config = lib.mkIf (config.services.actual.enable && config.services.fail2ban.enable) { + # TODO: configuration for fail2ban for actual + }; +} diff --git a/modules/nixos-modules/server/actual/impermanence.nix b/modules/nixos-modules/server/actual/impermanence.nix new file mode 100644 index 0000000..5eee95a --- /dev/null +++ b/modules/nixos-modules/server/actual/impermanence.nix @@ -0,0 +1,26 @@ +{ + lib, + config, + ... +}: let + const = import ./const.nix; + dataDirectory = const.dataDirectory; +in { + config = lib.mkIf (config.services.actual.enable && config.host.impermanence.enable) { + assertions = [ + { + assertion = config.services.actual.settings.ACTUAL_DATA_DIR == dataDirectory; + message = "actual data location does not match persistence"; + } + ]; + environment.persistence."/persist/system/root" = { + directories = [ + { + directory = dataDirectory; + user = "actual"; + group = "actual"; + } + ]; + }; + }; +} diff --git a/modules/nixos-modules/server/actual/proxy.nix b/modules/nixos-modules/server/actual/proxy.nix new file mode 100644 index 0000000..e20a6cd --- /dev/null +++ b/modules/nixos-modules/server/actual/proxy.nix @@ -0,0 +1,13 @@ +{ + lib, + config, + ... +}: { + config = lib.mkIf (config.services.actual.enable && config.host.reverse_proxy.enable) { + host = { + reverse_proxy.subdomains.${config.services.actual.subdomain} = { + target = "http://localhost:${toString config.services.actual.settings.port}"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/default.nix b/modules/nixos-modules/server/default.nix index 87f3dae..15f833b 100644 --- a/modules/nixos-modules/server/default.nix +++ b/modules/nixos-modules/server/default.nix @@ -1,19 +1,20 @@ {...}: { imports = [ - ./fail2ban.nix - ./network_storage ./reverse_proxy.nix + ./fail2ban.nix ./postgres.nix + ./network_storage ./podman.nix - ./jellyfin.nix - ./forgejo.nix - ./searx.nix - ./home-assistant.nix - ./wyoming.nix - ./immich.nix + + ./actual + ./immich + ./panoramax + ./forgejo + ./home-assistant + ./jellyfin + ./paperless + ./searx ./qbittorent.nix - ./paperless.nix - ./actual.nix - ./panoramax.nix + ./wyoming.nix ]; } diff --git a/modules/nixos-modules/server/forgejo.nix b/modules/nixos-modules/server/forgejo.nix deleted file mode 100644 index 3b19695..0000000 --- a/modules/nixos-modules/server/forgejo.nix +++ /dev/null @@ -1,128 +0,0 @@ -{ - lib, - config, - pkgs, - ... -}: let - forgejoPort = 8081; - stateDir = "/var/lib/forgejo"; - db_user = "forgejo"; - sshPort = 22222; -in { - options.services.forgejo = { - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that forgejo will be hosted at"; - default = "forgejo"; - }; - }; - - config = lib.mkIf config.services.forgejo.enable (lib.mkMerge [ - { - assertions = [ - { - assertion = config.services.forgejo.settings.server.BUILTIN_SSH_SERVER_USER == config.users.users.git.name; - message = "Forgejo BUILTIN_SSH_SERVER_USER hardcoded value does not match expected git user name"; - } - ]; - host = { - postgres = { - enable = true; - extraUsers = { - ${db_user} = { - isClient = true; - createUser = true; - }; - }; - extraDatabases = { - ${db_user} = { - name = db_user; - }; - }; - }; - }; - - services.forgejo = { - database = { - type = "postgres"; - socket = "/run/postgresql"; - }; - lfs.enable = true; - settings = { - server = { - DOMAIN = "${config.services.forgejo.subdomain}.${config.host.reverse_proxy.hostname}"; - HTTP_PORT = forgejoPort; - START_SSH_SERVER = true; - SSH_LISTEN_PORT = sshPort; - SSH_PORT = 22; - BUILTIN_SSH_SERVER_USER = "git"; - ROOT_URL = "https://git.jan-leila.com"; - }; - service = { - DISABLE_REGISTRATION = true; - }; - database = { - DB_TYPE = "postgres"; - NAME = db_user; - USER = db_user; - }; - }; - }; - - networking.firewall.allowedTCPPorts = [ - config.services.forgejo.settings.server.SSH_LISTEN_PORT - ]; - } - (lib.mkIf config.host.reverse_proxy.enable { - host = { - reverse_proxy.subdomains.${config.services.forgejo.subdomain} = { - target = "http://localhost:${toString forgejoPort}"; - }; - }; - }) - (lib.mkIf config.services.fail2ban.enable { - environment.etc = { - "fail2ban/filter.d/forgejo.local".text = lib.mkIf config.services.forgejo.enable ( - pkgs.lib.mkDefault (pkgs.lib.mkAfter '' - [Definition] - failregex = ".*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from " - '') - ); - }; - - services.fail2ban = { - jails = { - forgejo-iptables.settings = lib.mkIf config.services.forgejo.enable { - enabled = true; - filter = "forgejo"; - action = ''iptables-multiport[name=HTTP, port="http,https"]''; - logpath = "${config.services.forgejo.settings.log.ROOT_PATH}/*.log"; - backend = "auto"; - findtime = 600; - bantime = 600; - maxretry = 5; - }; - }; - }; - }) - (lib.mkIf config.host.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/const.nix b/modules/nixos-modules/server/forgejo/const.nix new file mode 100644 index 0000000..10e3974 --- /dev/null +++ b/modules/nixos-modules/server/forgejo/const.nix @@ -0,0 +1,4 @@ +{ + httpPort = 8081; + sshPort = 22222; +} diff --git a/modules/nixos-modules/server/forgejo/database.nix b/modules/nixos-modules/server/forgejo/database.nix new file mode 100644 index 0000000..0417aab --- /dev/null +++ b/modules/nixos-modules/server/forgejo/database.nix @@ -0,0 +1,41 @@ +{ + lib, + config, + ... +}: { + config = lib.mkIf config.services.forgejo.enable ( + lib.mkMerge [ + { + host = { + postgres = { + enable = true; + }; + }; + + assertions = [ + { + assertion = config.services.forgejo.settings.database.DB_TYPE == "postgres"; + message = "Forgejo database type must be postgres"; + } + ]; + } + (lib.mkIf config.host.postgres.enable { + host = { + postgres = { + extraUsers = { + forgejo = { + isClient = true; + createUser = true; + }; + }; + extraDatabases = { + forgejo = { + name = "forgejo"; + }; + }; + }; + }; + }) + ] + ); +} diff --git a/modules/nixos-modules/server/forgejo/default.nix b/modules/nixos-modules/server/forgejo/default.nix new file mode 100644 index 0000000..cec2630 --- /dev/null +++ b/modules/nixos-modules/server/forgejo/default.nix @@ -0,0 +1,61 @@ +{ + lib, + config, + ... +}: let + const = import ./const.nix; + httpPort = const.httpPort; + sshPort = const.sshPort; + db_user = "forgejo"; +in { + imports = [ + ./proxy.nix + ./database.nix + ./fail2ban.nix + ./impermanence.nix + ]; + + options.services.forgejo = { + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that forgejo will be hosted at"; + default = "forgejo"; + }; + }; + + config = lib.mkIf config.services.forgejo.enable { + assertions = [ + { + assertion = config.services.forgejo.settings.server.BUILTIN_SSH_SERVER_USER == config.users.users.git.name; + message = "Forgejo BUILTIN_SSH_SERVER_USER hardcoded value does not match expected git user name"; + } + ]; + + services.forgejo = { + database = { + type = "postgres"; + socket = "/run/postgresql"; + }; + lfs.enable = true; + settings = { + server = { + DOMAIN = "${config.services.forgejo.subdomain}.${config.host.reverse_proxy.hostname}"; + HTTP_PORT = httpPort; + START_SSH_SERVER = true; + SSH_LISTEN_PORT = sshPort; + SSH_PORT = 22; + BUILTIN_SSH_SERVER_USER = "git"; + ROOT_URL = "https://git.jan-leila.com"; + }; + service = { + DISABLE_REGISTRATION = true; + }; + database = { + DB_TYPE = "postgres"; + NAME = db_user; + USER = db_user; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/forgejo/fail2ban.nix b/modules/nixos-modules/server/forgejo/fail2ban.nix new file mode 100644 index 0000000..213c804 --- /dev/null +++ b/modules/nixos-modules/server/forgejo/fail2ban.nix @@ -0,0 +1,32 @@ +{ + lib, + config, + pkgs, + ... +}: { + config = lib.mkIf (config.services.forgejo.enable && config.services.fail2ban.enable) { + environment.etc = { + "fail2ban/filter.d/forgejo.local".text = lib.mkIf config.services.forgejo.enable ( + pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [Definition] + failregex = ".*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from " + '') + ); + }; + + services.fail2ban = { + jails = { + forgejo-iptables.settings = lib.mkIf config.services.forgejo.enable { + enabled = true; + filter = "forgejo"; + action = ''iptables-multiport[name=HTTP, port="http,https"]''; + logpath = "${config.services.forgejo.settings.log.ROOT_PATH}/*.log"; + backend = "auto"; + findtime = 600; + bantime = 600; + maxretry = 5; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/forgejo/impermanence.nix b/modules/nixos-modules/server/forgejo/impermanence.nix new file mode 100644 index 0000000..04f21a5 --- /dev/null +++ b/modules/nixos-modules/server/forgejo/impermanence.nix @@ -0,0 +1,28 @@ +{ + lib, + config, + ... +}: let + stateDir = "/var/lib/forgejo"; +in { + config = lib.mkIf (config.services.forgejo.enable && config.host.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/proxy.nix b/modules/nixos-modules/server/forgejo/proxy.nix new file mode 100644 index 0000000..9e85f78 --- /dev/null +++ b/modules/nixos-modules/server/forgejo/proxy.nix @@ -0,0 +1,18 @@ +{ + lib, + config, + ... +}: let + const = import ./const.nix; + httpPort = const.httpPort; +in { + config = lib.mkIf (config.services.forgejo.enable && config.host.reverse_proxy.enable) { + host.reverse_proxy.subdomains.${config.services.forgejo.subdomain} = { + target = "http://localhost:${toString httpPort}"; + }; + + networking.firewall.allowedTCPPorts = [ + config.services.forgejo.settings.server.SSH_LISTEN_PORT + ]; + }; +} diff --git a/modules/nixos-modules/server/home-assistant.nix b/modules/nixos-modules/server/home-assistant.nix deleted file mode 100644 index baf6683..0000000 --- a/modules/nixos-modules/server/home-assistant.nix +++ /dev/null @@ -1,230 +0,0 @@ -{ - lib, - pkgs, - config, - ... -}: let - configDir = "/var/lib/hass"; - dbUser = "hass"; -in { - options.services.home-assistant = { - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that home-assistant will be hosted at"; - default = "home-assistant"; - }; - - database = lib.mkOption { - type = lib.types.enum [ - "builtin" - "postgres" - ]; - description = "what database do we want to use"; - default = "builtin"; - }; - - extensions = { - sonos = { - enable = lib.mkEnableOption "enable the sonos plugin"; - port = lib.mkOption { - type = lib.types.int; - default = 1400; - description = "what port to use for sonos discovery"; - }; - }; - jellyfin = { - enable = lib.mkEnableOption "enable the jellyfin plugin"; - }; - wyoming = { - enable = lib.mkEnableOption "enable wyoming"; - }; - }; - }; - - config = lib.mkIf config.services.home-assistant.enable (lib.mkMerge [ - { - services.home-assistant = { - configDir = configDir; - extraComponents = [ - "default_config" - "esphome" - "met" - "radio_browser" - "isal" - "zha" - "webostv" - "tailscale" - "syncthing" - "analytics_insights" - "unifi" - "openweathermap" - "ollama" - "mobile_app" - "logbook" - "ssdp" - "usb" - "webhook" - "bluetooth" - "dhcp" - "energy" - "history" - "backup" - "assist_pipeline" - "conversation" - "sun" - "zeroconf" - "cpuspeed" - ]; - config = { - http = { - server_port = 8123; - use_x_forwarded_for = true; - trusted_proxies = ["127.0.0.1" "::1"]; - ip_ban_enabled = true; - login_attempts_threshold = 10; - }; - homeassistant = { - external_url = "https://${config.services.home-assistant.subdomain}.${config.host.reverse_proxy.hostname}"; - # internal_url = "http://192.168.1.2:8123"; - }; - recorder.db_url = "postgresql://@/${dbUser}"; - "automation manual" = []; - "automation ui" = "!include automations.yaml"; - mobile_app = {}; - }; - extraPackages = python3Packages: - with python3Packages; [ - hassil - numpy - gtts - ]; - }; - - # TODO: configure /var/lib/hass/secrets.yaml via sops - - networking.firewall.allowedUDPPorts = [ - 1900 - ]; - - systemd.tmpfiles.rules = [ - "f ${config.services.home-assistant.configDir}/automations.yaml 0755 hass hass" - ]; - } - (lib.mkIf (config.services.home-assistant.extensions.sonos.enable) { - services.home-assistant.extraComponents = ["sonos"]; - networking.firewall.allowedTCPPorts = [ - config.services.home-assistant.extensions.sonos.port - ]; - }) - (lib.mkIf (config.services.home-assistant.extensions.jellyfin.enable) { - services.home-assistant.extraComponents = ["jellyfin"]; - # TODO: configure port, address, and login information here - }) - (lib.mkIf (config.services.home-assistant.extensions.wyoming.enable) { - services.home-assistant.extraComponents = ["wyoming"]; - services.wyoming.enable = true; - }) - (lib.mkIf (config.services.home-assistant.database == "postgres") { - host = { - postgres = { - enable = true; - extraUsers = { - ${dbUser} = { - isClient = true; - createUser = true; - }; - }; - extraDatabases = { - ${dbUser} = { - name = dbUser; - }; - }; - }; - }; - - services.home-assistant = { - extraPackages = python3Packages: - with python3Packages; [ - psycopg2 - ]; - }; - - systemd.services.home-assistant = { - requires = [ - config.systemd.services.postgresql.name - ]; - }; - }) - (lib.mkIf config.host.reverse_proxy.enable { - host = { - reverse_proxy.subdomains.${config.services.home-assistant.subdomain} = { - target = "http://localhost:${toString config.services.home-assistant.config.http.server_port}"; - - websockets.enable = true; - forwardHeaders.enable = true; - - extraConfig = '' - add_header Upgrade $http_upgrade; - add_header Connection \"upgrade\"; - - proxy_buffering off; - - proxy_read_timeout 90; - ''; - }; - }; - }) - (lib.mkIf config.services.fail2ban.enable { - environment.etc = { - "fail2ban/filter.d/hass.local".text = lib.mkIf config.services.home-assistant.enable ( - pkgs.lib.mkDefault (pkgs.lib.mkAfter '' - [INCLUDES] - before = common.conf - - [Definition] - failregex = ^%(__prefix_line)s.*Login attempt or request with invalid authentication from .*$ - - ignoreregex = - - [Init] - datepattern = ^%%Y-%%m-%%d %%H:%%M:%%S - '') - ); - }; - - services.fail2ban = { - jails = { - home-assistant-iptables.settings = lib.mkIf config.services.home-assistant.enable { - enabled = true; - filter = "hass"; - action = ''iptables-multiport[name=HTTP, port="http,https"]''; - logpath = "${config.services.home-assistant.configDir}/*.log"; - backend = "auto"; - findtime = 600; - bantime = 600; - maxretry = 5; - }; - }; - }; - }) - (lib.mkIf config.host.impermanence.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/database.nix b/modules/nixos-modules/server/home-assistant/database.nix new file mode 100644 index 0000000..0ac8002 --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/database.nix @@ -0,0 +1,56 @@ +{ + lib, + config, + ... +}: let + dbUser = "hass"; +in { + config = lib.mkIf config.services.home-assistant.enable ( + lib.mkMerge [ + { + host = { + postgres = { + enable = true; + }; + }; + + assertions = [ + { + assertion = config.services.home-assistant.database == "postgres"; + message = "Home Assistant database type must be postgres"; + } + ]; + } + (lib.mkIf config.host.postgres.enable { + host = { + postgres = { + extraUsers = { + ${dbUser} = { + isClient = true; + createUser = true; + }; + }; + extraDatabases = { + ${dbUser} = { + name = dbUser; + }; + }; + }; + }; + + services.home-assistant = { + extraPackages = python3Packages: + with python3Packages; [ + psycopg2 + ]; + }; + + systemd.services.home-assistant = { + requires = [ + config.systemd.services.postgresql.name + ]; + }; + }) + ] + ); +} diff --git a/modules/nixos-modules/server/home-assistant/default.nix b/modules/nixos-modules/server/home-assistant/default.nix new file mode 100644 index 0000000..6edf0c0 --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/default.nix @@ -0,0 +1,118 @@ +{ + lib, + config, + ... +}: { + imports = [ + ./proxy.nix + ./database.nix + ./fail2ban.nix + ./impermanence.nix + ./extensions + ]; + + options.services.home-assistant = { + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that home-assistant will be hosted at"; + default = "home-assistant"; + }; + + database = lib.mkOption { + type = lib.types.enum [ + "builtin" + "postgres" + ]; + description = "what database do we want to use"; + default = "builtin"; + }; + + extensions = { + sonos = { + enable = lib.mkEnableOption "enable the sonos plugin"; + port = lib.mkOption { + type = lib.types.int; + default = 1400; + description = "what port to use for sonos discovery"; + }; + }; + jellyfin = { + enable = lib.mkEnableOption "enable the jellyfin plugin"; + }; + wyoming = { + enable = lib.mkEnableOption "enable wyoming"; + }; + }; + }; + + config = lib.mkIf config.services.home-assistant.enable (lib.mkMerge [ + { + services.home-assistant = { + configDir = "/var/lib/hass"; + extraComponents = [ + "default_config" + "esphome" + "met" + "radio_browser" + "isal" + "zha" + "webostv" + "tailscale" + "syncthing" + "analytics_insights" + "unifi" + "openweathermap" + "ollama" + "mobile_app" + "logbook" + "ssdp" + "usb" + "webhook" + "bluetooth" + "dhcp" + "energy" + "history" + "backup" + "assist_pipeline" + "conversation" + "sun" + "zeroconf" + "cpuspeed" + ]; + config = { + http = { + server_port = 8123; + use_x_forwarded_for = true; + trusted_proxies = ["127.0.0.1" "::1"]; + ip_ban_enabled = true; + login_attempts_threshold = 10; + }; + homeassistant = { + external_url = "https://${config.services.home-assistant.subdomain}.${config.host.reverse_proxy.hostname}"; + # internal_url = "http://192.168.1.2:8123"; + }; + recorder.db_url = "postgresql://@/${config.services.home-assistant.configDir}"; + "automation manual" = []; + "automation ui" = "!include automations.yaml"; + mobile_app = {}; + }; + extraPackages = python3Packages: + with python3Packages; [ + hassil + numpy + gtts + ]; + }; + + # TODO: configure /var/lib/hass/secrets.yaml via sops + + networking.firewall.allowedUDPPorts = [ + 1900 + ]; + + systemd.tmpfiles.rules = [ + "f ${config.services.home-assistant.configDir}/automations.yaml 0755 hass hass" + ]; + } + ]); +} diff --git a/modules/nixos-modules/server/home-assistant/extensions/default.nix b/modules/nixos-modules/server/home-assistant/extensions/default.nix new file mode 100644 index 0000000..9ef84a3 --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/extensions/default.nix @@ -0,0 +1,12 @@ +{ + config, + lib, + pkgs, + ... +}: { + imports = [ + ./sonos.nix + ./jellyfin.nix + ./wyoming.nix + ]; +} diff --git a/modules/nixos-modules/server/home-assistant/extensions/jellyfin.nix b/modules/nixos-modules/server/home-assistant/extensions/jellyfin.nix new file mode 100644 index 0000000..29af274 --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/extensions/jellyfin.nix @@ -0,0 +1,9 @@ +{ + lib, + config, + ... +}: +lib.mkIf (config.services.home-assistant.extensions.jellyfin.enable) { + services.home-assistant.extraComponents = ["jellyfin"]; + # TODO: configure port, address, and login information here +} diff --git a/modules/nixos-modules/server/home-assistant/extensions/sonos.nix b/modules/nixos-modules/server/home-assistant/extensions/sonos.nix new file mode 100644 index 0000000..c70649f --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/extensions/sonos.nix @@ -0,0 +1,11 @@ +{ + lib, + config, + ... +}: +lib.mkIf (config.services.home-assistant.extensions.sonos.enable) { + services.home-assistant.extraComponents = ["sonos"]; + networking.firewall.allowedTCPPorts = [ + config.services.home-assistant.extensions.sonos.port + ]; +} diff --git a/modules/nixos-modules/server/home-assistant/extensions/wyoming.nix b/modules/nixos-modules/server/home-assistant/extensions/wyoming.nix new file mode 100644 index 0000000..840d360 --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/extensions/wyoming.nix @@ -0,0 +1,9 @@ +{ + lib, + config, + ... +}: +lib.mkIf (config.services.home-assistant.extensions.wyoming.enable) { + services.home-assistant.extraComponents = ["wyoming"]; + services.wyoming.enable = true; +} diff --git a/modules/nixos-modules/server/home-assistant/fail2ban.nix b/modules/nixos-modules/server/home-assistant/fail2ban.nix new file mode 100644 index 0000000..6ac5900 --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/fail2ban.nix @@ -0,0 +1,39 @@ +{ + lib, + pkgs, + config, + ... +}: +lib.mkIf (config.services.fail2ban.enable && config.services.home-assistant.enable) { + environment.etc = { + "fail2ban/filter.d/hass.local".text = ( + pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [INCLUDES] + before = common.conf + + [Definition] + failregex = ^%(__prefix_line)s.*Login attempt or request with invalid authentication from .*$ + + ignoreregex = + + [Init] + datepattern = ^%%Y-%%m-%%d %%H:%%M:%%S + '') + ); + }; + + services.fail2ban = { + jails = { + home-assistant-iptables.settings = { + enabled = true; + filter = "hass"; + action = ''iptables-multiport[name=HTTP, port="http,https"]''; + logpath = "${config.services.home-assistant.configDir}/*.log"; + backend = "auto"; + findtime = 600; + bantime = 600; + maxretry = 5; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/home-assistant/impermanence.nix b/modules/nixos-modules/server/home-assistant/impermanence.nix new file mode 100644 index 0000000..8c056a1 --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/impermanence.nix @@ -0,0 +1,26 @@ +{ + 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/proxy.nix b/modules/nixos-modules/server/home-assistant/proxy.nix new file mode 100644 index 0000000..63396b5 --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/proxy.nix @@ -0,0 +1,24 @@ +{ + lib, + config, + ... +}: +lib.mkIf (config.host.reverse_proxy.enable && config.services.home-assistant.enable) { + host = { + reverse_proxy.subdomains.${config.services.home-assistant.subdomain} = { + target = "http://localhost:${toString config.services.home-assistant.config.http.server_port}"; + + websockets.enable = true; + forwardHeaders.enable = true; + + extraConfig = '' + add_header Upgrade $http_upgrade; + add_header Connection \"upgrade\"; + + proxy_buffering off; + + proxy_read_timeout 90; + ''; + }; + }; +} diff --git a/modules/nixos-modules/server/immich.nix b/modules/nixos-modules/server/immich.nix deleted file mode 100644 index fa376e4..0000000 --- a/modules/nixos-modules/server/immich.nix +++ /dev/null @@ -1,99 +0,0 @@ -{ - lib, - config, - pkgs, - ... -}: let - mediaLocation = "/var/lib/immich"; -in { - options.services.immich = { - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that immich will be hosted at"; - default = "immich"; - }; - }; - - config = lib.mkIf config.services.immich.enable (lib.mkMerge [ - { - host = { - postgres = { - enable = true; - extraUsers = { - ${config.services.immich.database.user} = { - isClient = true; - }; - }; - }; - }; - - networking.firewall.interfaces.${config.services.tailscale.interfaceName} = { - allowedUDPPorts = [ - config.services.immich.port - ]; - allowedTCPPorts = [ - config.services.immich.port - ]; - }; - } - (lib.mkIf config.host.reverse_proxy.enable { - host = { - reverse_proxy.subdomains.${config.services.immich.subdomain} = { - target = "http://localhost:${toString config.services.immich.port}"; - - websockets.enable = true; - forwardHeaders.enable = true; - - extraConfig = '' - # allow large file uploads - client_max_body_size 50000M; - - # set timeout - proxy_read_timeout 600s; - proxy_send_timeout 600s; - send_timeout 600s; - proxy_redirect off; - ''; - }; - }; - }) - (lib.mkIf config.services.fail2ban.enable { - environment.etc = { - "fail2ban/filter.d/immich.local".text = lib.mkIf config.services.immich.enable ( - pkgs.lib.mkDefault (pkgs.lib.mkAfter '' - [Definition] - failregex = immich-server.*Failed login attempt for user.+from ip address\s? - journalmatch = CONTAINER_TAG=immich-server - '') - ); - }; - - services.fail2ban = { - jails = { - immich-iptables.settings = lib.mkIf config.services.immich.enable { - enabled = true; - filter = "immich"; - backend = "systemd"; - }; - }; - }; - }) - (lib.mkIf config.host.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/database.nix b/modules/nixos-modules/server/immich/database.nix new file mode 100644 index 0000000..74b1aaa --- /dev/null +++ b/modules/nixos-modules/server/immich/database.nix @@ -0,0 +1,26 @@ +{ + lib, + config, + ... +}: { + config = lib.mkIf config.services.immich.enable (lib.mkMerge [ + { + host = { + postgres = { + enable = true; + }; + }; + } + (lib.mkIf config.host.postgres.enable { + host = { + postgres = { + extraUsers = { + ${config.services.immich.database.user} = { + isClient = true; + }; + }; + }; + }; + }) + ]); +} diff --git a/modules/nixos-modules/server/immich/default.nix b/modules/nixos-modules/server/immich/default.nix new file mode 100644 index 0000000..9d782f0 --- /dev/null +++ b/modules/nixos-modules/server/immich/default.nix @@ -0,0 +1,28 @@ +{lib, ...}: { + imports = [ + ./proxy.nix + ./database.nix + ./fail2ban.nix + ./impermanence.nix + ]; + + options.services.immich = { + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that immich will be hosted at"; + default = "immich"; + }; + }; + + # NOTE: This shouldn't be needed now that we are out of testing + # config = lib.mkIf config.services.immich.enable { + # networking.firewall.interfaces.${config.services.tailscale.interfaceName} = { + # allowedUDPPorts = [ + # config.services.immich.port + # ]; + # allowedTCPPorts = [ + # config.services.immich.port + # ]; + # }; + # }; +} diff --git a/modules/nixos-modules/server/immich/fail2ban.nix b/modules/nixos-modules/server/immich/fail2ban.nix new file mode 100644 index 0000000..c9ec87b --- /dev/null +++ b/modules/nixos-modules/server/immich/fail2ban.nix @@ -0,0 +1,26 @@ +{ + lib, + config, + pkgs, + ... +}: { + config = lib.mkIf (config.services.fail2ban.enable && config.services.immich.enable) { + environment.etc = { + "fail2ban/filter.d/immich.local".text = pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [Definition] + failregex = immich-server.*Failed login attempt for user.+from ip address\s? + journalmatch = CONTAINER_TAG=immich-server + ''); + }; + + services.fail2ban = { + jails = { + immich-iptables.settings = { + enabled = true; + filter = "immich"; + backend = "systemd"; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/immich/impermanence.nix b/modules/nixos-modules/server/immich/impermanence.nix new file mode 100644 index 0000000..f63d178 --- /dev/null +++ b/modules/nixos-modules/server/immich/impermanence.nix @@ -0,0 +1,25 @@ +{ + lib, + config, + ... +}: let + mediaLocation = "/var/lib/immich"; +in { + config = lib.mkIf (config.services.immich.enable && config.host.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/proxy.nix b/modules/nixos-modules/server/immich/proxy.nix new file mode 100644 index 0000000..9d8790a --- /dev/null +++ b/modules/nixos-modules/server/immich/proxy.nix @@ -0,0 +1,27 @@ +{ + lib, + config, + ... +}: { + config = lib.mkIf (config.services.immich.enable && config.host.reverse_proxy.enable) { + host = { + reverse_proxy.subdomains.${config.services.immich.subdomain} = { + target = "http://localhost:${toString config.services.immich.port}"; + + websockets.enable = true; + forwardHeaders.enable = true; + + extraConfig = '' + # allow large file uploads + client_max_body_size 50000M; + + # set timeout + proxy_read_timeout 600s; + proxy_send_timeout 600s; + send_timeout 600s; + proxy_redirect off; + ''; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/jellyfin.nix b/modules/nixos-modules/server/jellyfin.nix deleted file mode 100644 index 85c870f..0000000 --- a/modules/nixos-modules/server/jellyfin.nix +++ /dev/null @@ -1,147 +0,0 @@ -{ - lib, - pkgs, - config, - ... -}: let - jellyfinPort = 8096; - dlanPort = 1900; - jellyfin_data_directory = "/var/lib/jellyfin"; - jellyfin_cache_directory = "/var/cache/jellyfin"; -in { - options.services.jellyfin = { - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that jellyfin will be hosted at"; - default = "jellyfin"; - }; - extraSubdomains = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "ex subdomain of base domain that jellyfin will be hosted at"; - default = []; - }; - media_directory = lib.mkOption { - type = lib.types.str; - description = "directory jellyfin media will be hosted at"; - default = "/srv/jellyfin/media"; - }; - }; - - config = lib.mkIf config.services.jellyfin.enable ( - lib.mkMerge [ - { - environment.systemPackages = [ - pkgs.jellyfin - pkgs.jellyfin-web - pkgs.jellyfin-ffmpeg - ]; - - networking.firewall.allowedTCPPorts = [jellyfinPort dlanPort]; - - systemd.tmpfiles.rules = [ - "d ${config.services.jellyfin.media_directory} 2770 jellyfin jellyfin_media" - "A ${config.services.jellyfin.media_directory} - - - - u:jellyfin:rwX,g:jellyfin_media:rwX,o::-" - ]; - } - (lib.mkIf config.host.reverse_proxy.enable { - host.reverse_proxy.subdomains.jellyfin = { - target = "http://localhost:${toString jellyfinPort}"; - - subdomain = config.services.jellyfin.subdomain; - extraSubdomains = config.services.jellyfin.extraSubdomains; - - forwardHeaders.enable = true; - - extraConfig = '' - client_max_body_size 20M; - add_header X-Content-Type-Options "nosniff"; - - proxy_buffering off; - ''; - }; - }) - (lib.mkIf config.services.fail2ban.enable { - environment.etc = { - "fail2ban/filter.d/jellyfin.local".text = ( - pkgs.lib.mkDefault (pkgs.lib.mkAfter '' - [Definition] - failregex = "^.*Authentication request for .* has been denied \\\(IP: \"\"\\\)\\\." - '') - ); - }; - - services.fail2ban = { - jails = { - jellyfin-iptables.settings = { - enabled = true; - filter = "jellyfin"; - action = ''iptables-multiport[name=HTTP, port="http,https"]''; - logpath = "${config.services.jellyfin.dataDir}/log/*.log"; - backend = "auto"; - findtime = 600; - bantime = 600; - maxretry = 5; - }; - }; - }; - }) - (lib.mkIf config.host.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/default.nix b/modules/nixos-modules/server/jellyfin/default.nix new file mode 100644 index 0000000..238ce3a --- /dev/null +++ b/modules/nixos-modules/server/jellyfin/default.nix @@ -0,0 +1,48 @@ +{ + lib, + pkgs, + config, + ... +}: let + jellyfinPort = 8096; + dlanPort = 1900; +in { + imports = [ + ./proxy.nix + ./fail2ban.nix + ./impermanence.nix + ]; + + options.services.jellyfin = { + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that jellyfin will be hosted at"; + default = "jellyfin"; + }; + extraSubdomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "ex subdomain of base domain that jellyfin will be hosted at"; + default = []; + }; + media_directory = lib.mkOption { + type = lib.types.str; + description = "directory jellyfin media will be hosted at"; + default = "/srv/jellyfin/media"; + }; + }; + + config = lib.mkIf config.services.jellyfin.enable { + environment.systemPackages = [ + pkgs.jellyfin + pkgs.jellyfin-web + pkgs.jellyfin-ffmpeg + ]; + + networking.firewall.allowedTCPPorts = [jellyfinPort dlanPort]; + + systemd.tmpfiles.rules = [ + "d ${config.services.jellyfin.media_directory} 2770 jellyfin jellyfin_media" + "A ${config.services.jellyfin.media_directory} - - - - u:jellyfin:rwX,g:jellyfin_media:rwX,o::-" + ]; + }; +} diff --git a/modules/nixos-modules/server/jellyfin/fail2ban.nix b/modules/nixos-modules/server/jellyfin/fail2ban.nix new file mode 100644 index 0000000..ba8d8ba --- /dev/null +++ b/modules/nixos-modules/server/jellyfin/fail2ban.nix @@ -0,0 +1,32 @@ +{ + lib, + pkgs, + config, + ... +}: { + config = lib.mkIf (config.services.jellyfin.enable && config.services.fail2ban.enable) { + environment.etc = { + "fail2ban/filter.d/jellyfin.local".text = ( + pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [Definition] + failregex = "^.*Authentication request for .* has been denied \\\\\\(IP: \\\"\\\"\\\\\\)\\\\\\." + '') + ); + }; + + services.fail2ban = { + jails = { + jellyfin-iptables.settings = { + enabled = true; + filter = "jellyfin"; + action = ''iptables-multiport[name=HTTP, port="http,https"]''; + logpath = "${config.services.jellyfin.dataDir}/log/*.log"; + backend = "auto"; + findtime = 600; + bantime = 600; + maxretry = 5; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/jellyfin/impermanence.nix b/modules/nixos-modules/server/jellyfin/impermanence.nix new file mode 100644 index 0000000..e0b3b5d --- /dev/null +++ b/modules/nixos-modules/server/jellyfin/impermanence.nix @@ -0,0 +1,66 @@ +{ + lib, + config, + ... +}: let + jellyfin_data_directory = "/var/lib/jellyfin"; + jellyfin_cache_directory = "/var/cache/jellyfin"; +in { + config = lib.mkIf (config.services.jellyfin.enable && config.host.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/proxy.nix b/modules/nixos-modules/server/jellyfin/proxy.nix new file mode 100644 index 0000000..5edb865 --- /dev/null +++ b/modules/nixos-modules/server/jellyfin/proxy.nix @@ -0,0 +1,25 @@ +{ + lib, + config, + ... +}: let + jellyfinPort = 8096; +in { + config = lib.mkIf (config.services.jellyfin.enable && config.host.reverse_proxy.enable) { + host.reverse_proxy.subdomains.jellyfin = { + target = "http://localhost:${toString jellyfinPort}"; + + subdomain = config.services.jellyfin.subdomain; + extraSubdomains = config.services.jellyfin.extraSubdomains; + + forwardHeaders.enable = true; + + extraConfig = '' + client_max_body_size 20M; + add_header X-Content-Type-Options "nosniff"; + + proxy_buffering off; + ''; + }; + }; +} diff --git a/modules/nixos-modules/server/panoramax.nix b/modules/nixos-modules/server/panoramax.nix deleted file mode 100644 index dd026cd..0000000 --- a/modules/nixos-modules/server/panoramax.nix +++ /dev/null @@ -1,408 +0,0 @@ -{ - config, - lib, - pkgs, - osConfig, - ... -}: -with lib; let - # Database configuration assertions - dbUrlConfigured = config.services.panoramax.database.url != null; - individualDbConfigured = all (x: x != null) [ - config.services.panoramax.database.host - config.services.panoramax.database.port - config.services.panoramax.database.username - config.services.panoramax.database.password - config.services.panoramax.database.name - ]; - - envContent = '' - # Panoramax Configuration - FLASK_APP=geovisio - ${ - if dbUrlConfigured - then "DB_URL=${config.services.panoramax.database.url}" - else '' - DB_HOST=${config.services.panoramax.database.host} - DB_PORT=${toString config.services.panoramax.database.port} - DB_USERNAME=${config.services.panoramax.database.username} - DB_PASSWORD=${config.services.panoramax.database.password} - DB_NAME=${config.services.panoramax.database.name} - '' - } - ${optionalString (config.services.panoramax.storage.fsUrl != null) "FS_URL=${config.services.panoramax.storage.fsUrl}"} - ${optionalString (config.services.panoramax.infrastructure.nbProxies != null) "INFRA_NB_PROXIES=${toString config.services.panoramax.infrastructure.nbProxies}"} - ${optionalString (config.services.panoramax.flask.secretKey != null) "FLASK_SECRET_KEY=${config.services.panoramax.flask.secretKey}"} - ${optionalString (config.services.panoramax.flask.sessionCookieDomain != null) "FLASK_SESSION_COOKIE_DOMAIN=${config.services.panoramax.flask.sessionCookieDomain}"} - ${optionalString (config.services.panoramax.api.pictures.licenseSpdxId != null) "API_PICTURES_LICENSE_SPDX_ID=${config.services.panoramax.api.pictures.licenseSpdxId}"} - ${optionalString (config.services.panoramax.api.pictures.licenseUrl != null) "API_PICTURES_LICENSE_URL=${config.services.panoramax.api.pictures.licenseUrl}"} - ${optionalString (config.services.panoramax.port != null) "PORT=${toString config.services.panoramax.port}"} - ${optionalString (config.services.panoramax.sgblur.enable) "SGBLUR_API_URL=${config.services.panoramax.sgblur.url}"} - ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name}=${value}") config.services.panoramax.extraEnvironment)} - ''; - - envFile = pkgs.writeText "panoramax.env" envContent; -in { - options.services.panoramax = { - enable = lib.mkEnableOption "panoramax"; - - package = lib.mkOption { - type = lib.types.package; - default = pkgs.panoramax; - description = "The panoramax package to use"; - }; - - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that panoramax will be hosted at"; - default = "panoramax"; - }; - - database = { - createDB = mkOption { - type = types.bool; - default = true; - description = "Whether to automatically create the database and user"; - }; - - url = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Complete database URL connection string (e.g., "postgresql://user:password@host:port/dbname"). - If provided, individual database options (host, port, username, password, name) are ignored. - ''; - }; - - port = mkOption { - type = types.nullOr types.port; - default = 5432; - description = "Database port (ignored if database.url is set)"; - }; - - host = mkOption { - type = types.nullOr types.str; - default = "localhost"; - description = "Database host (ignored if database.url is set)"; - }; - - username = mkOption { - type = types.nullOr types.str; - default = "panoramax"; - description = "Database username (ignored if database.url is set)"; - }; - - password = mkOption { - type = types.nullOr types.str; - default = null; - description = "Database password (ignored if database.url is set)"; - }; - - name = mkOption { - type = types.str; - default = "panoramax"; - description = "Database name (ignored if database.url is set)"; - }; - }; - - sgblur = { - enable = mkOption { - type = types.bool; - default = false; - description = "Whether to enable sgblur integration for face and license plate blurring"; - }; - - package = mkOption { - type = types.package; - default = pkgs.sgblur; - description = "The sgblur package to use"; - }; - - port = mkOption { - type = types.port; - default = 8080; - description = "Port for the sgblur service"; - }; - - host = mkOption { - type = types.str; - default = "127.0.0.1"; - description = "Host to bind the sgblur service to"; - }; - - url = mkOption { - type = types.str; - default = "http://127.0.0.1:8080"; - description = "URL where sgblur service is accessible"; - }; - }; - - port = mkOption { - type = types.nullOr types.port; - default = 5000; - description = "Port for the Panoramax service"; - }; - - host = mkOption { - type = types.str; - default = "127.0.0.1"; - description = "Host to bind the Panoramax service to"; - }; - - urlScheme = mkOption { - type = types.enum ["http" "https"]; - default = "https"; - description = "URL scheme for the application"; - }; - - storage = { - fsUrl = mkOption { - type = types.nullOr types.str; - default = "/var/lib/panoramax/storage"; - description = "File system URL for storage"; - }; - }; - - infrastructure = { - nbProxies = mkOption { - type = types.nullOr types.int; - default = 1; - description = "Number of proxies in front of the application"; - }; - }; - - flask = { - secretKey = mkOption { - type = types.nullOr types.str; - default = null; - description = "Flask secret key for session security"; - }; - - sessionCookieDomain = mkOption { - type = types.nullOr types.str; - default = null; - description = "Flask session cookie domain"; - }; - }; - - api = { - pictures = { - licenseSpdxId = mkOption { - type = types.nullOr types.str; - default = null; - description = "SPDX license identifier for API pictures"; - }; - - licenseUrl = mkOption { - type = types.nullOr types.str; - default = null; - description = "License URL for API pictures"; - }; - }; - }; - - extraEnvironment = mkOption { - type = types.attrsOf types.str; - default = {}; - description = "Additional environment variables"; - example = { - CUSTOM_SETTING = "value"; - DEBUG = "true"; - }; - }; - }; - - config = lib.mkIf config.services.panoramax.enable ( - lib.mkMerge [ - { - environment.systemPackages = with pkgs; - [ - config.services.panoramax.package - python3Packages.waitress - ] - ++ optionals config.services.panoramax.sgblur.enable [ - config.services.panoramax.sgblur.package - ]; - - systemd.services.panoramax = { - description = "Panoramax Service"; - after = ["network.target"]; - wantedBy = ["multi-user.target"]; - serviceConfig = { - ExecStart = "${pkgs.python3Packages.waitress}/bin/waitress-serve --env-file=${envFile} --host=${config.services.panoramax.host} --port=${toString config.services.panoramax.port} --url-scheme=${config.services.panoramax.urlScheme} --call geovisio:create_app"; - Restart = "always"; - User = "panoramax"; - Group = "panoramax"; - WorkingDirectory = "/var/lib/panoramax"; - Environment = "PYTHONPATH=${config.services.panoramax.package}/lib/python3.11/site-packages"; - }; - }; - - users.users.panoramax = { - isSystemUser = true; - group = "panoramax"; - home = "/var/lib/panoramax"; - createHome = true; - }; - - users.groups.panoramax = {}; - - systemd.tmpfiles.rules = [ - "d /var/lib/panoramax 0755 panoramax panoramax -" - "d ${config.services.panoramax.storage.fsUrl} 0755 panoramax panoramax -" - ]; - - assertions = [ - { - assertion = dbUrlConfigured || individualDbConfigured; - message = '' - Panoramax database configuration requires either: - - A complete database URL (services.panoramax.database.url), OR - - All individual database options (host, port, username, password, name) - - Currently configured: - - database.url: ${ - if dbUrlConfigured - then "✓ configured" - else "✗ not configured" - } - - individual options: ${ - if individualDbConfigured - then "✓ all configured" - else "✗ some missing" - } - ''; - } - { - assertion = !config.services.panoramax.database.createDB || config.services.panoramax.database.url == null || (lib.hasPrefix "/run/" config.services.panoramax.database.url || lib.hasPrefix "unix:" config.services.panoramax.database.url || lib.hasPrefix "/" config.services.panoramax.database.host); - message = '' - Panoramax createDB option can only be used with socket connections when a database URL is provided. - Socket connections are identified by: - - URLs starting with "unix:" - - URLs starting with "/run/" - - Host paths starting with "/" - - Current configuration: - - createDB: ${lib.boolToString config.services.panoramax.database.createDB} - - database.url: ${ - if config.services.panoramax.database.url != null - then config.services.panoramax.database.url - else "not set" - } - - database.host: ${config.services.panoramax.database.host} - ''; - } - ]; - } - ( - lib.mkIf config.services.panoramax.sgblur.enable { - systemd.services.sgblur = { - description = "SGBlur AI-powered face and license plate blurring service"; - after = ["network.target"]; - wantedBy = ["multi-user.target"]; - serviceConfig = { - ExecStart = "${config.services.panoramax.sgblur.package}/bin/uvicorn sgblur.main:app --host ${config.services.panoramax.sgblur.host} --port ${toString config.services.panoramax.sgblur.port}"; - Restart = "always"; - User = "sgblur"; - Group = "sgblur"; - WorkingDirectory = "/var/lib/sgblur"; - Environment = "PYTHONPATH=${config.services.panoramax.sgblur.package}/lib/python3.11/site-packages"; - }; - }; - - users.users.sgblur = { - isSystemUser = true; - group = "sgblur"; - home = "/var/lib/sgblur"; - createHome = true; - }; - - users.groups.sgblur = {}; - - systemd.tmpfiles.rules = [ - "d /var/lib/sgblur 0755 sgblur sgblur -" - ]; - - # Update panoramax service dependencies when sgblur is enabled - systemd.services.panoramax = { - after = ["sgblur.service"]; - wants = ["sgblur.service"]; - }; - } - ) - ( - lib.mkIf config.services.panoramax.database.createDB { - services.postgresql = { - enable = true; - ensureDatabases = [config.services.panoramax.database.name]; - ensureUsers = [ - { - name = config.services.panoramax.database.username; - ensureDBOwnership = true; - ensureClauses.login = true; - } - ]; - extensions = ps: with ps; [postgis]; - settings = { - shared_preload_libraries = ["postgis"]; - }; - }; - - systemd.services.postgresql.serviceConfig.ExecStartPost = let - sqlFile = pkgs.writeText "panoramax-postgis-setup.sql" '' - CREATE EXTENSION IF NOT EXISTS postgis; - CREATE EXTENSION IF NOT EXISTS postgis_topology; - CREATE EXTENSION IF NOT EXISTS fuzzystrmatch; - CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder; - - ALTER SCHEMA public OWNER TO ${config.services.panoramax.database.username}; - GRANT ALL ON SCHEMA public TO ${config.services.panoramax.database.username}; - ''; - in [ - '' - ${lib.getExe' config.services.postgresql.package "psql"} -d "${config.services.panoramax.database.name}" -f "${sqlFile}" - '' - ]; - - systemd.services.panoramax = { - after = ["postgresql.service"]; - requires = ["postgresql.service"]; - }; - } - ) - ( - lib.mkIf config.host.reverse_proxy.enable { - host = { - reverse_proxy.subdomains.${config.services.panoramax.subdomain} = { - target = "http://localhost:${toString config.services.panoramax.port}"; - - websockets.enable = true; - forwardHeaders.enable = true; - - extraConfig = '' - # allow large file uploads for panoramic images - client_max_body_size 100M; - - # set timeout for image processing - proxy_read_timeout 300s; - proxy_send_timeout 300s; - send_timeout 300s; - proxy_redirect off; - ''; - }; - }; - } - ) - ( - lib.mkIf config.services.fail2ban { - # TODO: configure options for fail2ban - } - ) - ( - lib.mkIf osConfig.host.impermanence.enable { - # TODO: configure impermanence for panoramax data - } - ) - ] - ); -} diff --git a/modules/nixos-modules/server/panoramax/default.nix b/modules/nixos-modules/server/panoramax/default.nix new file mode 100644 index 0000000..e506b80 --- /dev/null +++ b/modules/nixos-modules/server/panoramax/default.nix @@ -0,0 +1,340 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + # Database configuration assertions + dbUrlConfigured = config.services.panoramax.database.url != null; + individualDbConfigured = all (x: x != null) [ + config.services.panoramax.database.host + config.services.panoramax.database.port + config.services.panoramax.database.username + config.services.panoramax.database.password + config.services.panoramax.database.name + ]; + + envContent = '' + # Panoramax Configuration + FLASK_APP=geovisio + ${ + if dbUrlConfigured + then "DB_URL=${config.services.panoramax.database.url}" + else '' + DB_HOST=${config.services.panoramax.database.host} + DB_PORT=${toString config.services.panoramax.database.port} + DB_USERNAME=${config.services.panoramax.database.username} + DB_PASSWORD=${config.services.panoramax.database.password} + DB_NAME=${config.services.panoramax.database.name} + '' + } + ${optionalString (config.services.panoramax.storage.fsUrl != null) "FS_URL=${config.services.panoramax.storage.fsUrl}"} + ${optionalString (config.services.panoramax.infrastructure.nbProxies != null) "INFRA_NB_PROXIES=${toString config.services.panoramax.infrastructure.nbProxies}"} + ${optionalString (config.services.panoramax.flask.secretKey != null) "FLASK_SECRET_KEY=${config.services.panoramax.flask.secretKey}"} + ${optionalString (config.services.panoramax.flask.sessionCookieDomain != null) "FLASK_SESSION_COOKIE_DOMAIN=${config.services.panoramax.flask.sessionCookieDomain}"} + ${optionalString (config.services.panoramax.api.pictures.licenseSpdxId != null) "API_PICTURES_LICENSE_SPDX_ID=${config.services.panoramax.api.pictures.licenseSpdxId}"} + ${optionalString (config.services.panoramax.api.pictures.licenseUrl != null) "API_PICTURES_LICENSE_URL=${config.services.panoramax.api.pictures.licenseUrl}"} + ${optionalString (config.services.panoramax.port != null) "PORT=${toString config.services.panoramax.port}"} + ${optionalString (config.services.panoramax.sgblur.enable) "SGBLUR_API_URL=${config.services.panoramax.sgblur.url}"} + ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name}=${value}") config.services.panoramax.extraEnvironment)} + ''; + + envFile = pkgs.writeText "panoramax.env" envContent; +in { + imports = [ + ./proxy.nix + ./fail2ban.nix + ./impermanence.nix + ]; + + options.services.panoramax = { + enable = lib.mkEnableOption "panoramax"; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.panoramax; + description = "The panoramax package to use"; + }; + + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that panoramax will be hosted at"; + default = "panoramax"; + }; + + database = { + createDB = mkOption { + type = types.bool; + default = true; + description = "Whether to automatically create the database and user"; + }; + + url = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Complete database URL connection string (e.g., "postgresql://user:password@host:port/dbname"). + If provided, individual database options (host, port, username, password, name) are ignored. + ''; + }; + + port = mkOption { + type = types.nullOr types.port; + default = 5432; + description = "Database port (ignored if database.url is set)"; + }; + + host = mkOption { + type = types.nullOr types.str; + default = "localhost"; + description = "Database host (ignored if database.url is set)"; + }; + + username = mkOption { + type = types.nullOr types.str; + default = "panoramax"; + description = "Database username (ignored if database.url is set)"; + }; + + password = mkOption { + type = types.nullOr types.str; + default = null; + description = "Database password (ignored if database.url is set)"; + }; + + name = mkOption { + type = types.str; + default = "panoramax"; + description = "Database name (ignored if database.url is set)"; + }; + }; + + sgblur = { + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable sgblur integration for face and license plate blurring"; + }; + + package = mkOption { + type = types.package; + default = pkgs.sgblur; + description = "The sgblur package to use"; + }; + + port = mkOption { + type = types.port; + default = 8080; + description = "Port for the sgblur service"; + }; + + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Host to bind the sgblur service to"; + }; + + url = mkOption { + type = types.str; + default = "http://127.0.0.1:8080"; + description = "URL where sgblur service is accessible"; + }; + }; + + port = mkOption { + type = types.nullOr types.port; + default = 5000; + description = "Port for the Panoramax service"; + }; + + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Host to bind the Panoramax service to"; + }; + + urlScheme = mkOption { + type = types.enum ["http" "https"]; + default = "https"; + description = "URL scheme for the application"; + }; + + storage = { + fsUrl = mkOption { + type = types.nullOr types.str; + default = "/var/lib/panoramax/storage"; + description = "File system URL for storage"; + }; + }; + + infrastructure = { + nbProxies = mkOption { + type = types.nullOr types.int; + default = 1; + description = "Number of proxies in front of the application"; + }; + }; + + flask = { + secretKey = mkOption { + type = types.nullOr types.str; + default = null; + description = "Flask secret key for session security"; + }; + + sessionCookieDomain = mkOption { + type = types.nullOr types.str; + default = null; + description = "Flask session cookie domain"; + }; + }; + + api = { + pictures = { + licenseSpdxId = mkOption { + type = types.nullOr types.str; + default = null; + description = "SPDX license identifier for API pictures"; + }; + + licenseUrl = mkOption { + type = types.nullOr types.str; + default = null; + description = "License URL for API pictures"; + }; + }; + }; + + extraEnvironment = mkOption { + type = types.attrsOf types.str; + default = {}; + description = "Additional environment variables"; + example = { + CUSTOM_SETTING = "value"; + DEBUG = "true"; + }; + }; + }; + + config = lib.mkIf config.services.panoramax.enable (lib.mkMerge [ + { + environment.systemPackages = with pkgs; + [ + config.services.panoramax.package + python3Packages.waitress + ] + ++ optionals config.services.panoramax.sgblur.enable [ + config.services.panoramax.sgblur.package + ]; + + systemd.services.panoramax = { + description = "Panoramax Service"; + after = ["network.target"]; + wantedBy = ["multi-user.target"]; + serviceConfig = { + ExecStart = "${pkgs.python3Packages.waitress}/bin/waitress-serve --env-file=${envFile} --host=${config.services.panoramax.host} --port=${toString config.services.panoramax.port} --url-scheme=${config.services.panoramax.urlScheme} --call geovisio:create_app"; + Restart = "always"; + User = "panoramax"; + Group = "panoramax"; + WorkingDirectory = "/var/lib/panoramax"; + Environment = "PYTHONPATH=${config.services.panoramax.package}/lib/python3.11/site-packages"; + }; + }; + + users.users.panoramax = { + isSystemUser = true; + group = "panoramax"; + home = "/var/lib/panoramax"; + createHome = true; + }; + + users.groups.panoramax = {}; + + systemd.tmpfiles.rules = [ + "d /var/lib/panoramax 0755 panoramax panoramax -" + "d ${config.services.panoramax.storage.fsUrl} 0755 panoramax panoramax -" + ]; + + assertions = [ + { + assertion = dbUrlConfigured || individualDbConfigured; + message = '' + Panoramax database configuration requires either: + - A complete database URL (services.panoramax.database.url), OR + - All individual database options (host, port, username, password, name) + + Currently configured: + - database.url: ${ + if dbUrlConfigured + then "✓ configured" + else "✗ not configured" + } + - individual options: ${ + if individualDbConfigured + then "✓ all configured" + else "✗ some missing" + } + ''; + } + { + assertion = !config.services.panoramax.database.createDB || config.services.panoramax.database.url == null || (lib.hasPrefix "/run/" config.services.panoramax.database.url || lib.hasPrefix "unix:" config.services.panoramax.database.url || lib.hasPrefix "/" config.services.panoramax.database.host); + message = '' + Panoramax createDB option can only be used with socket connections when a database URL is provided. + Socket connections are identified by: + - URLs starting with "unix:" + - URLs starting with "/run/" + - Host paths starting with "/" + + Current configuration: + - createDB: ${lib.boolToString config.services.panoramax.database.createDB} + - database.url: ${ + if config.services.panoramax.database.url != null + then config.services.panoramax.database.url + else "not set" + } + - database.host: ${config.services.panoramax.database.host} + ''; + } + ]; + } + (lib.mkIf config.services.panoramax.database.createDB { + systemd.services.panoramax = { + after = ["postgresql.service"]; + requires = ["postgresql.service"]; + }; + + services.postgresql = { + enable = true; + ensureDatabases = [config.services.panoramax.database.name]; + ensureUsers = [ + { + name = config.services.panoramax.database.username; + ensureDBOwnership = true; + ensureClauses.login = true; + } + ]; + extensions = ps: with ps; [postgis]; + settings = { + shared_preload_libraries = ["postgis"]; + }; + }; + + systemd.services.postgresql.serviceConfig.ExecStartPost = let + sqlFile = pkgs.writeText "panoramax-postgis-setup.sql" '' + CREATE EXTENSION IF NOT EXISTS postgis; + CREATE EXTENSION IF NOT EXISTS postgis_topology; + CREATE EXTENSION IF NOT EXISTS fuzzystrmatch; + CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder; + + ALTER SCHEMA public OWNER TO ${config.services.panoramax.database.username}; + GRANT ALL ON SCHEMA public TO ${config.services.panoramax.database.username}; + ''; + in [ + '' + ${lib.getExe' config.services.postgresql.package "psql"} -d "${config.services.panoramax.database.name}" -f "${sqlFile}" + '' + ]; + }) + ]); +} diff --git a/modules/nixos-modules/server/panoramax/fail2ban.nix b/modules/nixos-modules/server/panoramax/fail2ban.nix new file mode 100644 index 0000000..649b53a --- /dev/null +++ b/modules/nixos-modules/server/panoramax/fail2ban.nix @@ -0,0 +1,11 @@ +{ + lib, + config, + ... +}: { + config = lib.mkIf (config.services.panoramax.enable && config.services.fail2ban.enable) { + # TODO: configure options for fail2ban + # This is a placeholder - panoramax fail2ban configuration would need to be defined + # based on the specific log patterns and security requirements + }; +} diff --git a/modules/nixos-modules/server/panoramax/impermanence.nix b/modules/nixos-modules/server/panoramax/impermanence.nix new file mode 100644 index 0000000..011c322 --- /dev/null +++ b/modules/nixos-modules/server/panoramax/impermanence.nix @@ -0,0 +1,14 @@ +{ + lib, + config, + osConfig, + ... +}: { + config = lib.mkIf (config.services.panoramax.enable && osConfig.host.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/proxy.nix b/modules/nixos-modules/server/panoramax/proxy.nix new file mode 100644 index 0000000..70e3f5b --- /dev/null +++ b/modules/nixos-modules/server/panoramax/proxy.nix @@ -0,0 +1,27 @@ +{ + lib, + config, + ... +}: { + config = lib.mkIf (config.services.panoramax.enable && config.host.reverse_proxy.enable) { + host = { + reverse_proxy.subdomains.${config.services.panoramax.subdomain} = { + target = "http://localhost:${toString config.services.panoramax.port}"; + + websockets.enable = true; + forwardHeaders.enable = true; + + extraConfig = '' + # allow large file uploads for panoramic images + client_max_body_size 100M; + + # set timeout for image processing + proxy_read_timeout 300s; + proxy_send_timeout 300s; + send_timeout 300s; + proxy_redirect off; + ''; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/paperless.nix b/modules/nixos-modules/server/paperless.nix deleted file mode 100644 index 303d742..0000000 --- a/modules/nixos-modules/server/paperless.nix +++ /dev/null @@ -1,113 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: let - dataDir = "/var/lib/paperless"; -in { - options.services.paperless = { - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that paperless will be hosted at"; - default = "paperless"; - }; - database = { - user = lib.mkOption { - type = lib.types.str; - description = "what is the user and database that we are going to use for paperless"; - default = "paperless"; - }; - }; - }; - - config = lib.mkIf config.services.paperless.enable (lib.mkMerge [ - { - host = { - postgres = { - enable = true; - extraUsers = { - ${config.services.paperless.database.user} = { - isClient = true; - createUser = true; - }; - }; - extraDatabases = { - ${config.services.paperless.database.user} = { - name = config.services.paperless.database.user; - }; - }; - }; - }; - services.paperless = { - domain = "${config.services.paperless.subdomain}.${config.host.reverse_proxy.hostname}"; - configureTika = true; - settings = { - PAPERLESS_DBENGINE = "postgresql"; - PAPERLESS_DBHOST = "/run/postgresql"; - PAPERLESS_DBNAME = config.services.paperless.database.user; - PAPERLESS_DBUSER = config.services.paperless.database.user; - }; - }; - } - (lib.mkIf config.host.reverse_proxy.enable { - host = { - reverse_proxy.subdomains.${config.services.paperless.subdomain} = { - target = "http://${config.services.paperless.address}:${toString config.services.paperless.port}"; - - websockets.enable = true; - forwardHeaders.enable = true; - - extraConfig = '' - # allow large file uploads - client_max_body_size 50000M; - ''; - }; - }; - }) - (lib.mkIf config.services.fail2ban.enable { - environment.etc = { - "fail2ban/filter.d/paperless.local".text = ( - pkgs.lib.mkDefault (pkgs.lib.mkAfter '' - [Definition] - failregex = Login failed for user `.*` from (?:IP|private IP) ``\.$ - ignoreregex = - - '') - ); - }; - - services.fail2ban = { - jails = { - paperless.settings = { - enabled = true; - filter = "paperless"; - action = ''iptables-multiport[name=HTTP, port="http,https"]''; - logpath = "${config.services.paperless.dataDir}/log/*.log"; - backend = "auto"; - findtime = 600; - bantime = 600; - maxretry = 5; - }; - }; - }; - }) - (lib.mkIf config.host.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/database.nix b/modules/nixos-modules/server/paperless/database.nix new file mode 100644 index 0000000..6f4ce51 --- /dev/null +++ b/modules/nixos-modules/server/paperless/database.nix @@ -0,0 +1,34 @@ +{ + config, + lib, + ... +}: { + config = lib.mkIf config.services.paperless.enable (lib.mkMerge [ + { + host = { + postgres = { + enable = true; + }; + }; + } + ( + lib.mkIf config.host.postgres.enable { + host = { + postgres = { + extraUsers = { + ${config.services.paperless.database.user} = { + isClient = true; + createUser = true; + }; + }; + extraDatabases = { + ${config.services.paperless.database.user} = { + name = config.services.paperless.database.user; + }; + }; + }; + }; + } + ) + ]); +} diff --git a/modules/nixos-modules/server/paperless/default.nix b/modules/nixos-modules/server/paperless/default.nix new file mode 100644 index 0000000..ec01fef --- /dev/null +++ b/modules/nixos-modules/server/paperless/default.nix @@ -0,0 +1,40 @@ +{ + config, + lib, + ... +}: { + imports = [ + ./proxy.nix + ./database.nix + ./fail2ban.nix + ./impermanence.nix + ]; + + options.services.paperless = { + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that paperless will be hosted at"; + default = "paperless"; + }; + database = { + user = lib.mkOption { + type = lib.types.str; + description = "what is the user and database that we are going to use for paperless"; + default = "paperless"; + }; + }; + }; + + config = lib.mkIf config.services.paperless.enable { + services.paperless = { + domain = "${config.services.paperless.subdomain}.${config.host.reverse_proxy.hostname}"; + configureTika = true; + settings = { + PAPERLESS_DBENGINE = "postgresql"; + PAPERLESS_DBHOST = "/run/postgresql"; + PAPERLESS_DBNAME = config.services.paperless.database.user; + PAPERLESS_DBUSER = config.services.paperless.database.user; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/paperless/fail2ban.nix b/modules/nixos-modules/server/paperless/fail2ban.nix new file mode 100644 index 0000000..e1a70f9 --- /dev/null +++ b/modules/nixos-modules/server/paperless/fail2ban.nix @@ -0,0 +1,34 @@ +{ + config, + lib, + pkgs, + ... +}: { + config = lib.mkIf (config.services.paperless.enable && config.services.fail2ban.enable) { + environment.etc = { + "fail2ban/filter.d/paperless.local".text = ( + pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [Definition] + failregex = Login failed for user `.*` from (?:IP|private IP) ``\.$ + ignoreregex = + + '') + ); + }; + + services.fail2ban = { + jails = { + paperless.settings = { + enabled = true; + filter = "paperless"; + action = ''iptables-multiport[name=HTTP, port="http,https"]''; + logpath = "${config.services.paperless.dataDir}/log/*.log"; + backend = "auto"; + findtime = 600; + bantime = 600; + maxretry = 5; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/paperless/impermanence.nix b/modules/nixos-modules/server/paperless/impermanence.nix new file mode 100644 index 0000000..d9e17bd --- /dev/null +++ b/modules/nixos-modules/server/paperless/impermanence.nix @@ -0,0 +1,25 @@ +{ + config, + lib, + ... +}: let + dataDir = "/var/lib/paperless"; +in { + config = lib.mkIf (config.services.paperless.enable && config.host.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/proxy.nix b/modules/nixos-modules/server/paperless/proxy.nix new file mode 100644 index 0000000..cb0f157 --- /dev/null +++ b/modules/nixos-modules/server/paperless/proxy.nix @@ -0,0 +1,21 @@ +{ + config, + lib, + ... +}: { + config = lib.mkIf (config.services.paperless.enable && config.host.reverse_proxy.enable) { + host = { + reverse_proxy.subdomains.${config.services.paperless.subdomain} = { + target = "http://${config.services.paperless.address}:${toString config.services.paperless.port}"; + + websockets.enable = true; + forwardHeaders.enable = true; + + extraConfig = '' + # allow large file uploads + client_max_body_size 50000M; + ''; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/searx.nix b/modules/nixos-modules/server/searx.nix deleted file mode 100644 index 0e547af..0000000 --- a/modules/nixos-modules/server/searx.nix +++ /dev/null @@ -1,78 +0,0 @@ -{ - config, - lib, - inputs, - ... -}: { - options.services.searx = { - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that searx will be hosted at"; - default = "searx"; - }; - }; - - config = lib.mkIf config.services.searx.enable ( - lib.mkMerge [ - { - sops.secrets = { - "services/searx" = { - sopsFile = "${inputs.secrets}/defiant-services.yaml"; - }; - }; - services.searx = { - environmentFile = config.sops.secrets."services/searx".path; - - # Rate limiting - limiterSettings = { - real_ip = { - x_for = 1; - ipv4_prefix = 32; - ipv6_prefix = 56; - }; - - botdetection = { - ip_limit = { - filter_link_local = true; - link_token = true; - }; - }; - }; - - settings = { - server = { - port = 8083; - secret_key = "@SEARXNG_SECRET@"; - }; - - # Search engine settings - search = { - safe_search = 2; - autocomplete_min = 2; - autocomplete = "duckduckgo"; - }; - - # Enabled plugins - enabled_plugins = [ - "Basic Calculator" - "Hash plugin" - "Tor check plugin" - "Open Access DOI rewrite" - "Hostnames plugin" - "Unit converter plugin" - "Tracker URL remover" - ]; - }; - }; - } - (lib.mkIf config.host.reverse_proxy.enable { - host = { - reverse_proxy.subdomains.searx = { - subdomain = config.services.searx.subdomain; - target = "http://localhost:${toString config.services.searx.settings.server.port}"; - }; - }; - }) - ] - ); -} diff --git a/modules/nixos-modules/server/searx/default.nix b/modules/nixos-modules/server/searx/default.nix new file mode 100644 index 0000000..73ec489 --- /dev/null +++ b/modules/nixos-modules/server/searx/default.nix @@ -0,0 +1,71 @@ +{ + config, + lib, + inputs, + ... +}: { + imports = [ + ./proxy.nix + ]; + + options.services.searx = { + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that searx will be hosted at"; + default = "searx"; + }; + }; + + config = lib.mkIf config.services.searx.enable { + sops.secrets = { + "services/searx" = { + sopsFile = "${inputs.secrets}/defiant-services.yaml"; + }; + }; + + services.searx = { + environmentFile = config.sops.secrets."services/searx".path; + + # Rate limiting + limiterSettings = { + real_ip = { + x_for = 1; + ipv4_prefix = 32; + ipv6_prefix = 56; + }; + + botdetection = { + ip_limit = { + filter_link_local = true; + link_token = true; + }; + }; + }; + + settings = { + server = { + port = 8083; + secret_key = "@SEARXNG_SECRET@"; + }; + + # Search engine settings + search = { + safe_search = 2; + autocomplete_min = 2; + autocomplete = "duckduckgo"; + }; + + # Enabled plugins + enabled_plugins = [ + "Basic Calculator" + "Hash plugin" + "Tor check plugin" + "Open Access DOI rewrite" + "Hostnames plugin" + "Unit converter plugin" + "Tracker URL remover" + ]; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/searx/proxy.nix b/modules/nixos-modules/server/searx/proxy.nix new file mode 100644 index 0000000..d925918 --- /dev/null +++ b/modules/nixos-modules/server/searx/proxy.nix @@ -0,0 +1,14 @@ +{ + config, + lib, + ... +}: { + config = lib.mkIf (config.services.searx.enable && config.host.reverse_proxy.enable) { + host = { + reverse_proxy.subdomains.searx = { + subdomain = config.services.searx.subdomain; + target = "http://localhost:${toString config.services.searx.settings.server.port}"; + }; + }; + }; +} From dfdd6bcc82c088eb2aac31737df2e388a13398c1 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Tue, 16 Sep 2025 10:20:00 -0500 Subject: [PATCH 28/62] chore: removed resolved item from research topics --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 13d1206..62040e3 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,6 @@ nix multi user, multi system, configuration with `sops` secret management, `home - Look into this for auto rotating sops keys `https://technotim.live/posts/rotate-sops-encryption-keys/` - Look into this for npins https://jade.fyi/blog/pinning-nixos-with-npins/ - https://nixos-and-flakes.thiscute.world/ -- nix config mcp https://github.com/utensils/mcp-nixos # Tasks: @@ -70,4 +69,4 @@ nix multi user, multi system, configuration with `sops` secret management, `home - ISO target that contains authorized keys for nixos-anywhere https://github.com/diegofariasm/yggdrasil/blob/4acc43ebc7bcbf2e41376d14268e382007e94d78/hosts/bootstrap/default.nix - panoramax instance - mastodon instance -- move searx, jellyfin, paperless, and immich to only be accessible via vpn \ No newline at end of file +- move searx, home-assistant, actual, jellyfin, paperless, and immich to only be accessible via vpn \ No newline at end of file From 9b02e300801a0db6e2e7c3911af2843776099e56 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Tue, 16 Sep 2025 10:44:00 -0500 Subject: [PATCH 29/62] refactor: moved subdomain options into proxy file --- .../nixos-modules/server/actual/default.nix | 8 ----- modules/nixos-modules/server/actual/proxy.nix | 8 +++++ .../nixos-modules/server/forgejo/default.nix | 8 ----- .../nixos-modules/server/forgejo/proxy.nix | 8 +++++ .../server/home-assistant/default.nix | 6 ---- .../server/home-assistant/proxy.nix | 35 ++++++++++++------- .../nixos-modules/server/immich/default.nix | 10 +----- modules/nixos-modules/server/immich/proxy.nix | 8 +++++ .../nixos-modules/server/jellyfin/default.nix | 10 ------ .../nixos-modules/server/jellyfin/proxy.nix | 13 +++++++ .../server/panoramax/default.nix | 6 ---- .../nixos-modules/server/panoramax/proxy.nix | 8 +++++ .../server/paperless/default.nix | 5 --- .../nixos-modules/server/paperless/proxy.nix | 8 +++++ .../nixos-modules/server/searx/default.nix | 8 ----- modules/nixos-modules/server/searx/proxy.nix | 8 +++++ 16 files changed, 84 insertions(+), 73 deletions(-) diff --git a/modules/nixos-modules/server/actual/default.nix b/modules/nixos-modules/server/actual/default.nix index bef7a05..546240e 100644 --- a/modules/nixos-modules/server/actual/default.nix +++ b/modules/nixos-modules/server/actual/default.nix @@ -12,14 +12,6 @@ in { ./impermanence.nix ]; - options.services.actual = { - subdomain = lib.mkOption { - type = lib.types.str; - default = "actual"; - description = "subdomain of base domain that actual will be hosted at"; - }; - }; - config = lib.mkIf config.services.actual.enable { systemd.tmpfiles.rules = [ "d ${dataDirectory} 2770 actual actual" diff --git a/modules/nixos-modules/server/actual/proxy.nix b/modules/nixos-modules/server/actual/proxy.nix index e20a6cd..6ca51e4 100644 --- a/modules/nixos-modules/server/actual/proxy.nix +++ b/modules/nixos-modules/server/actual/proxy.nix @@ -3,6 +3,14 @@ config, ... }: { + options.services.actual = { + subdomain = lib.mkOption { + type = lib.types.str; + default = "actual"; + description = "subdomain of base domain that actual will be hosted at"; + }; + }; + config = lib.mkIf (config.services.actual.enable && config.host.reverse_proxy.enable) { host = { reverse_proxy.subdomains.${config.services.actual.subdomain} = { diff --git a/modules/nixos-modules/server/forgejo/default.nix b/modules/nixos-modules/server/forgejo/default.nix index cec2630..1fdc8d9 100644 --- a/modules/nixos-modules/server/forgejo/default.nix +++ b/modules/nixos-modules/server/forgejo/default.nix @@ -15,14 +15,6 @@ in { ./impermanence.nix ]; - options.services.forgejo = { - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that forgejo will be hosted at"; - default = "forgejo"; - }; - }; - config = lib.mkIf config.services.forgejo.enable { assertions = [ { diff --git a/modules/nixos-modules/server/forgejo/proxy.nix b/modules/nixos-modules/server/forgejo/proxy.nix index 9e85f78..51f769d 100644 --- a/modules/nixos-modules/server/forgejo/proxy.nix +++ b/modules/nixos-modules/server/forgejo/proxy.nix @@ -6,6 +6,14 @@ const = import ./const.nix; httpPort = const.httpPort; in { + options.services.forgejo = { + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that forgejo will be hosted at"; + default = "forgejo"; + }; + }; + config = lib.mkIf (config.services.forgejo.enable && config.host.reverse_proxy.enable) { host.reverse_proxy.subdomains.${config.services.forgejo.subdomain} = { target = "http://localhost:${toString httpPort}"; diff --git a/modules/nixos-modules/server/home-assistant/default.nix b/modules/nixos-modules/server/home-assistant/default.nix index 6edf0c0..83d8ba7 100644 --- a/modules/nixos-modules/server/home-assistant/default.nix +++ b/modules/nixos-modules/server/home-assistant/default.nix @@ -12,12 +12,6 @@ ]; options.services.home-assistant = { - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that home-assistant will be hosted at"; - default = "home-assistant"; - }; - database = lib.mkOption { type = lib.types.enum [ "builtin" diff --git a/modules/nixos-modules/server/home-assistant/proxy.nix b/modules/nixos-modules/server/home-assistant/proxy.nix index 63396b5..ba8f20d 100644 --- a/modules/nixos-modules/server/home-assistant/proxy.nix +++ b/modules/nixos-modules/server/home-assistant/proxy.nix @@ -2,23 +2,32 @@ lib, config, ... -}: -lib.mkIf (config.host.reverse_proxy.enable && config.services.home-assistant.enable) { - host = { - reverse_proxy.subdomains.${config.services.home-assistant.subdomain} = { - target = "http://localhost:${toString config.services.home-assistant.config.http.server_port}"; +}: { + options.services.home-assistant = { + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that home-assistant will be hosted at"; + default = "home-assistant"; + }; + }; - websockets.enable = true; - forwardHeaders.enable = true; + config = lib.mkIf (config.host.reverse_proxy.enable && config.services.home-assistant.enable) { + host = { + reverse_proxy.subdomains.${config.services.home-assistant.subdomain} = { + target = "http://localhost:${toString config.services.home-assistant.config.http.server_port}"; - extraConfig = '' - add_header Upgrade $http_upgrade; - add_header Connection \"upgrade\"; + websockets.enable = true; + forwardHeaders.enable = true; - proxy_buffering off; + extraConfig = '' + add_header Upgrade $http_upgrade; + add_header Connection \"upgrade\"; - proxy_read_timeout 90; - ''; + proxy_buffering off; + + proxy_read_timeout 90; + ''; + }; }; }; } diff --git a/modules/nixos-modules/server/immich/default.nix b/modules/nixos-modules/server/immich/default.nix index 9d782f0..4d93c0b 100644 --- a/modules/nixos-modules/server/immich/default.nix +++ b/modules/nixos-modules/server/immich/default.nix @@ -1,4 +1,4 @@ -{lib, ...}: { +{...}: { imports = [ ./proxy.nix ./database.nix @@ -6,14 +6,6 @@ ./impermanence.nix ]; - options.services.immich = { - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that immich will be hosted at"; - default = "immich"; - }; - }; - # NOTE: This shouldn't be needed now that we are out of testing # config = lib.mkIf config.services.immich.enable { # networking.firewall.interfaces.${config.services.tailscale.interfaceName} = { diff --git a/modules/nixos-modules/server/immich/proxy.nix b/modules/nixos-modules/server/immich/proxy.nix index 9d8790a..dae2420 100644 --- a/modules/nixos-modules/server/immich/proxy.nix +++ b/modules/nixos-modules/server/immich/proxy.nix @@ -3,6 +3,14 @@ config, ... }: { + options.services.immich = { + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that immich will be hosted at"; + default = "immich"; + }; + }; + config = lib.mkIf (config.services.immich.enable && config.host.reverse_proxy.enable) { host = { reverse_proxy.subdomains.${config.services.immich.subdomain} = { diff --git a/modules/nixos-modules/server/jellyfin/default.nix b/modules/nixos-modules/server/jellyfin/default.nix index 238ce3a..0d88481 100644 --- a/modules/nixos-modules/server/jellyfin/default.nix +++ b/modules/nixos-modules/server/jellyfin/default.nix @@ -14,16 +14,6 @@ in { ]; options.services.jellyfin = { - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that jellyfin will be hosted at"; - default = "jellyfin"; - }; - extraSubdomains = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "ex subdomain of base domain that jellyfin will be hosted at"; - default = []; - }; media_directory = lib.mkOption { type = lib.types.str; description = "directory jellyfin media will be hosted at"; diff --git a/modules/nixos-modules/server/jellyfin/proxy.nix b/modules/nixos-modules/server/jellyfin/proxy.nix index 5edb865..1020a19 100644 --- a/modules/nixos-modules/server/jellyfin/proxy.nix +++ b/modules/nixos-modules/server/jellyfin/proxy.nix @@ -5,6 +5,19 @@ }: let jellyfinPort = 8096; in { + options.services.jellyfin = { + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that jellyfin will be hosted at"; + default = "jellyfin"; + }; + extraSubdomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "ex subdomain of base domain that jellyfin will be hosted at"; + default = []; + }; + }; + config = lib.mkIf (config.services.jellyfin.enable && config.host.reverse_proxy.enable) { host.reverse_proxy.subdomains.jellyfin = { target = "http://localhost:${toString jellyfinPort}"; diff --git a/modules/nixos-modules/server/panoramax/default.nix b/modules/nixos-modules/server/panoramax/default.nix index e506b80..779f284 100644 --- a/modules/nixos-modules/server/panoramax/default.nix +++ b/modules/nixos-modules/server/panoramax/default.nix @@ -57,12 +57,6 @@ in { description = "The panoramax package to use"; }; - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that panoramax will be hosted at"; - default = "panoramax"; - }; - database = { createDB = mkOption { type = types.bool; diff --git a/modules/nixos-modules/server/panoramax/proxy.nix b/modules/nixos-modules/server/panoramax/proxy.nix index 70e3f5b..79f9326 100644 --- a/modules/nixos-modules/server/panoramax/proxy.nix +++ b/modules/nixos-modules/server/panoramax/proxy.nix @@ -3,6 +3,14 @@ config, ... }: { + options.services.panoramax = { + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that panoramax will be hosted at"; + default = "panoramax"; + }; + }; + config = lib.mkIf (config.services.panoramax.enable && config.host.reverse_proxy.enable) { host = { reverse_proxy.subdomains.${config.services.panoramax.subdomain} = { diff --git a/modules/nixos-modules/server/paperless/default.nix b/modules/nixos-modules/server/paperless/default.nix index ec01fef..a6878eb 100644 --- a/modules/nixos-modules/server/paperless/default.nix +++ b/modules/nixos-modules/server/paperless/default.nix @@ -11,11 +11,6 @@ ]; options.services.paperless = { - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that paperless will be hosted at"; - default = "paperless"; - }; database = { user = lib.mkOption { type = lib.types.str; diff --git a/modules/nixos-modules/server/paperless/proxy.nix b/modules/nixos-modules/server/paperless/proxy.nix index cb0f157..2910f07 100644 --- a/modules/nixos-modules/server/paperless/proxy.nix +++ b/modules/nixos-modules/server/paperless/proxy.nix @@ -3,6 +3,14 @@ lib, ... }: { + options.services.paperless = { + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that paperless will be hosted at"; + default = "paperless"; + }; + }; + config = lib.mkIf (config.services.paperless.enable && config.host.reverse_proxy.enable) { host = { reverse_proxy.subdomains.${config.services.paperless.subdomain} = { diff --git a/modules/nixos-modules/server/searx/default.nix b/modules/nixos-modules/server/searx/default.nix index 73ec489..ac84c1d 100644 --- a/modules/nixos-modules/server/searx/default.nix +++ b/modules/nixos-modules/server/searx/default.nix @@ -8,14 +8,6 @@ ./proxy.nix ]; - options.services.searx = { - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that searx will be hosted at"; - default = "searx"; - }; - }; - config = lib.mkIf config.services.searx.enable { sops.secrets = { "services/searx" = { diff --git a/modules/nixos-modules/server/searx/proxy.nix b/modules/nixos-modules/server/searx/proxy.nix index d925918..0c1eae1 100644 --- a/modules/nixos-modules/server/searx/proxy.nix +++ b/modules/nixos-modules/server/searx/proxy.nix @@ -3,6 +3,14 @@ lib, ... }: { + options.services.searx = { + subdomain = lib.mkOption { + type = lib.types.str; + description = "subdomain of base domain that searx will be hosted at"; + default = "searx"; + }; + }; + config = lib.mkIf (config.services.searx.enable && config.host.reverse_proxy.enable) { host = { reverse_proxy.subdomains.searx = { From e2e07c9a70c4f3ff591b72f8b90292fe62b7ca1a Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Tue, 16 Sep 2025 12:09:41 -0500 Subject: [PATCH 30/62] feat: reworked databse config for panoramax --- .../server/panoramax/default.nix | 330 +----------------- .../server/panoramax/panoramax.nix | 253 ++++++++++++++ 2 files changed, 255 insertions(+), 328 deletions(-) create mode 100644 modules/nixos-modules/server/panoramax/panoramax.nix diff --git a/modules/nixos-modules/server/panoramax/default.nix b/modules/nixos-modules/server/panoramax/default.nix index 779f284..f029ee3 100644 --- a/modules/nixos-modules/server/panoramax/default.nix +++ b/modules/nixos-modules/server/panoramax/default.nix @@ -1,334 +1,8 @@ -{ - config, - lib, - pkgs, - ... -}: -with lib; let - # Database configuration assertions - dbUrlConfigured = config.services.panoramax.database.url != null; - individualDbConfigured = all (x: x != null) [ - config.services.panoramax.database.host - config.services.panoramax.database.port - config.services.panoramax.database.username - config.services.panoramax.database.password - config.services.panoramax.database.name - ]; - - envContent = '' - # Panoramax Configuration - FLASK_APP=geovisio - ${ - if dbUrlConfigured - then "DB_URL=${config.services.panoramax.database.url}" - else '' - DB_HOST=${config.services.panoramax.database.host} - DB_PORT=${toString config.services.panoramax.database.port} - DB_USERNAME=${config.services.panoramax.database.username} - DB_PASSWORD=${config.services.panoramax.database.password} - DB_NAME=${config.services.panoramax.database.name} - '' - } - ${optionalString (config.services.panoramax.storage.fsUrl != null) "FS_URL=${config.services.panoramax.storage.fsUrl}"} - ${optionalString (config.services.panoramax.infrastructure.nbProxies != null) "INFRA_NB_PROXIES=${toString config.services.panoramax.infrastructure.nbProxies}"} - ${optionalString (config.services.panoramax.flask.secretKey != null) "FLASK_SECRET_KEY=${config.services.panoramax.flask.secretKey}"} - ${optionalString (config.services.panoramax.flask.sessionCookieDomain != null) "FLASK_SESSION_COOKIE_DOMAIN=${config.services.panoramax.flask.sessionCookieDomain}"} - ${optionalString (config.services.panoramax.api.pictures.licenseSpdxId != null) "API_PICTURES_LICENSE_SPDX_ID=${config.services.panoramax.api.pictures.licenseSpdxId}"} - ${optionalString (config.services.panoramax.api.pictures.licenseUrl != null) "API_PICTURES_LICENSE_URL=${config.services.panoramax.api.pictures.licenseUrl}"} - ${optionalString (config.services.panoramax.port != null) "PORT=${toString config.services.panoramax.port}"} - ${optionalString (config.services.panoramax.sgblur.enable) "SGBLUR_API_URL=${config.services.panoramax.sgblur.url}"} - ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name}=${value}") config.services.panoramax.extraEnvironment)} - ''; - - envFile = pkgs.writeText "panoramax.env" envContent; -in { +{...}: { imports = [ ./proxy.nix ./fail2ban.nix ./impermanence.nix + ./panoramax.nix ]; - - options.services.panoramax = { - enable = lib.mkEnableOption "panoramax"; - - package = lib.mkOption { - type = lib.types.package; - default = pkgs.panoramax; - description = "The panoramax package to use"; - }; - - database = { - createDB = mkOption { - type = types.bool; - default = true; - description = "Whether to automatically create the database and user"; - }; - - url = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Complete database URL connection string (e.g., "postgresql://user:password@host:port/dbname"). - If provided, individual database options (host, port, username, password, name) are ignored. - ''; - }; - - port = mkOption { - type = types.nullOr types.port; - default = 5432; - description = "Database port (ignored if database.url is set)"; - }; - - host = mkOption { - type = types.nullOr types.str; - default = "localhost"; - description = "Database host (ignored if database.url is set)"; - }; - - username = mkOption { - type = types.nullOr types.str; - default = "panoramax"; - description = "Database username (ignored if database.url is set)"; - }; - - password = mkOption { - type = types.nullOr types.str; - default = null; - description = "Database password (ignored if database.url is set)"; - }; - - name = mkOption { - type = types.str; - default = "panoramax"; - description = "Database name (ignored if database.url is set)"; - }; - }; - - sgblur = { - enable = mkOption { - type = types.bool; - default = false; - description = "Whether to enable sgblur integration for face and license plate blurring"; - }; - - package = mkOption { - type = types.package; - default = pkgs.sgblur; - description = "The sgblur package to use"; - }; - - port = mkOption { - type = types.port; - default = 8080; - description = "Port for the sgblur service"; - }; - - host = mkOption { - type = types.str; - default = "127.0.0.1"; - description = "Host to bind the sgblur service to"; - }; - - url = mkOption { - type = types.str; - default = "http://127.0.0.1:8080"; - description = "URL where sgblur service is accessible"; - }; - }; - - port = mkOption { - type = types.nullOr types.port; - default = 5000; - description = "Port for the Panoramax service"; - }; - - host = mkOption { - type = types.str; - default = "127.0.0.1"; - description = "Host to bind the Panoramax service to"; - }; - - urlScheme = mkOption { - type = types.enum ["http" "https"]; - default = "https"; - description = "URL scheme for the application"; - }; - - storage = { - fsUrl = mkOption { - type = types.nullOr types.str; - default = "/var/lib/panoramax/storage"; - description = "File system URL for storage"; - }; - }; - - infrastructure = { - nbProxies = mkOption { - type = types.nullOr types.int; - default = 1; - description = "Number of proxies in front of the application"; - }; - }; - - flask = { - secretKey = mkOption { - type = types.nullOr types.str; - default = null; - description = "Flask secret key for session security"; - }; - - sessionCookieDomain = mkOption { - type = types.nullOr types.str; - default = null; - description = "Flask session cookie domain"; - }; - }; - - api = { - pictures = { - licenseSpdxId = mkOption { - type = types.nullOr types.str; - default = null; - description = "SPDX license identifier for API pictures"; - }; - - licenseUrl = mkOption { - type = types.nullOr types.str; - default = null; - description = "License URL for API pictures"; - }; - }; - }; - - extraEnvironment = mkOption { - type = types.attrsOf types.str; - default = {}; - description = "Additional environment variables"; - example = { - CUSTOM_SETTING = "value"; - DEBUG = "true"; - }; - }; - }; - - config = lib.mkIf config.services.panoramax.enable (lib.mkMerge [ - { - environment.systemPackages = with pkgs; - [ - config.services.panoramax.package - python3Packages.waitress - ] - ++ optionals config.services.panoramax.sgblur.enable [ - config.services.panoramax.sgblur.package - ]; - - systemd.services.panoramax = { - description = "Panoramax Service"; - after = ["network.target"]; - wantedBy = ["multi-user.target"]; - serviceConfig = { - ExecStart = "${pkgs.python3Packages.waitress}/bin/waitress-serve --env-file=${envFile} --host=${config.services.panoramax.host} --port=${toString config.services.panoramax.port} --url-scheme=${config.services.panoramax.urlScheme} --call geovisio:create_app"; - Restart = "always"; - User = "panoramax"; - Group = "panoramax"; - WorkingDirectory = "/var/lib/panoramax"; - Environment = "PYTHONPATH=${config.services.panoramax.package}/lib/python3.11/site-packages"; - }; - }; - - users.users.panoramax = { - isSystemUser = true; - group = "panoramax"; - home = "/var/lib/panoramax"; - createHome = true; - }; - - users.groups.panoramax = {}; - - systemd.tmpfiles.rules = [ - "d /var/lib/panoramax 0755 panoramax panoramax -" - "d ${config.services.panoramax.storage.fsUrl} 0755 panoramax panoramax -" - ]; - - assertions = [ - { - assertion = dbUrlConfigured || individualDbConfigured; - message = '' - Panoramax database configuration requires either: - - A complete database URL (services.panoramax.database.url), OR - - All individual database options (host, port, username, password, name) - - Currently configured: - - database.url: ${ - if dbUrlConfigured - then "✓ configured" - else "✗ not configured" - } - - individual options: ${ - if individualDbConfigured - then "✓ all configured" - else "✗ some missing" - } - ''; - } - { - assertion = !config.services.panoramax.database.createDB || config.services.panoramax.database.url == null || (lib.hasPrefix "/run/" config.services.panoramax.database.url || lib.hasPrefix "unix:" config.services.panoramax.database.url || lib.hasPrefix "/" config.services.panoramax.database.host); - message = '' - Panoramax createDB option can only be used with socket connections when a database URL is provided. - Socket connections are identified by: - - URLs starting with "unix:" - - URLs starting with "/run/" - - Host paths starting with "/" - - Current configuration: - - createDB: ${lib.boolToString config.services.panoramax.database.createDB} - - database.url: ${ - if config.services.panoramax.database.url != null - then config.services.panoramax.database.url - else "not set" - } - - database.host: ${config.services.panoramax.database.host} - ''; - } - ]; - } - (lib.mkIf config.services.panoramax.database.createDB { - systemd.services.panoramax = { - after = ["postgresql.service"]; - requires = ["postgresql.service"]; - }; - - services.postgresql = { - enable = true; - ensureDatabases = [config.services.panoramax.database.name]; - ensureUsers = [ - { - name = config.services.panoramax.database.username; - ensureDBOwnership = true; - ensureClauses.login = true; - } - ]; - extensions = ps: with ps; [postgis]; - settings = { - shared_preload_libraries = ["postgis"]; - }; - }; - - systemd.services.postgresql.serviceConfig.ExecStartPost = let - sqlFile = pkgs.writeText "panoramax-postgis-setup.sql" '' - CREATE EXTENSION IF NOT EXISTS postgis; - CREATE EXTENSION IF NOT EXISTS postgis_topology; - CREATE EXTENSION IF NOT EXISTS fuzzystrmatch; - CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder; - - ALTER SCHEMA public OWNER TO ${config.services.panoramax.database.username}; - GRANT ALL ON SCHEMA public TO ${config.services.panoramax.database.username}; - ''; - in [ - '' - ${lib.getExe' config.services.postgresql.package "psql"} -d "${config.services.panoramax.database.name}" -f "${sqlFile}" - '' - ]; - }) - ]); } diff --git a/modules/nixos-modules/server/panoramax/panoramax.nix b/modules/nixos-modules/server/panoramax/panoramax.nix new file mode 100644 index 0000000..cdbc632 --- /dev/null +++ b/modules/nixos-modules/server/panoramax/panoramax.nix @@ -0,0 +1,253 @@ +{ + config, + lib, + pkgs, + ... +}: let + dbUrlConfigured = config.services.panoramax.database.url != null; + individualDbConfigured = lib.all (x: x != null) [ + config.services.panoramax.database.host + config.services.panoramax.database.port + config.services.panoramax.database.username + config.services.panoramax.database.password + config.services.panoramax.database.name + ]; + + envContent = '' + # Panoramax Configuration + FLASK_APP=geovisio + ${ + if dbUrlConfigured + then "DB_URL=${config.services.panoramax.database.url}" + else '' + DB_HOST=${config.services.panoramax.database.host} + DB_PORT=${toString config.services.panoramax.database.port} + DB_USERNAME=${config.services.panoramax.database.username} + DB_PASSWORD=${config.services.panoramax.database.password} + DB_NAME=${config.services.panoramax.database.name} + '' + } + ${lib.optionalString (config.services.panoramax.storage.fsUrl != null) "FS_URL=${config.services.panoramax.storage.fsUrl}"} + ${lib.optionalString (config.services.panoramax.infrastructure.nbProxies != null) "INFRA_NB_PROXIES=${toString config.services.panoramax.infrastructure.nbProxies}"} + ${lib.optionalString (config.services.panoramax.flask.secretKey != null) "FLASK_SECRET_KEY=${config.services.panoramax.flask.secretKey}"} + ${lib.optionalString (config.services.panoramax.flask.sessionCookieDomain != null) "FLASK_SESSION_COOKIE_DOMAIN=${config.services.panoramax.flask.sessionCookieDomain}"} + ${lib.optionalString (config.services.panoramax.api.pictures.licenseSpdxId != null) "API_PICTURES_LICENSE_SPDX_ID=${config.services.panoramax.api.pictures.licenseSpdxId}"} + ${lib.optionalString (config.services.panoramax.api.pictures.licenseUrl != null) "API_PICTURES_LICENSE_URL=${config.services.panoramax.api.pictures.licenseUrl}"} + ${lib.optionalString (config.services.panoramax.port != null) "PORT=${toString config.services.panoramax.port}"} + ${lib.optionalString (config.services.panoramax.sgblur.enable) "SGBLUR_API_URL=${config.services.panoramax.sgblur.url}"} + ${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name}=${value}") config.services.panoramax.extraEnvironment)} + ''; + + envFile = pkgs.writeText "panoramax.env" envContent; +in { + options.services = { + panoramax = { + enable = lib.mkEnableOption "panoramax"; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.panoramax; + description = "The panoramax package to use"; + }; + + user = lib.mkOption { + type = lib.types.str; + default = "panoramax"; + description = "The user panoramax should run as."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "panoramax"; + description = "The group panoramax should run as."; + }; + + host = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = "Host to bind the panoramax service to"; + }; + + port = lib.mkOption { + type = lib.types.nullOr lib.types.port; + default = 5000; + description = "Port for the panoramax service"; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to open the panoramax port in the firewall"; + }; + + settings = { + urlScheme = lib.mkOption { + type = lib.types.enum ["http" "https"]; + default = "https"; + description = "URL scheme for the application"; + }; + + storage = { + fsUrl = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "/var/lib/panoramax/storage"; + description = "File system URL for storage"; + }; + }; + + infrastructure = { + nbProxies = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = 1; + description = "Number of proxies in front of the application"; + }; + }; + + flask = { + secretKey = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Flask secret key for session security"; + }; + + sessionCookieDomain = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Flask session cookie domain"; + }; + }; + + api = { + pictures = { + licenseSpdxId = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "SPDX license identifier for API pictures"; + }; + + licenseUrl = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "License URL for API pictures"; + }; + }; + }; + + extraEnvironment = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; + description = "Additional environment variables"; + example = { + CUSTOM_SETTING = "value"; + DEBUG = "true"; + }; + }; + }; + + database = { + createDB = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to automatically create the database and user"; + }; + + name = lib.mkOption { + type = lib.types.str; + default = "panoramax"; + description = "The name of the panoramax database"; + }; + + host = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "/run/postgresql"; + description = "Hostname or address of the postgresql server. If an absolute path is given here, it will be interpreted as a unix socket path."; + }; + + port = lib.mkOption { + type = lib.types.nullOr lib.types.port; + default = 5432; + description = "Port of the postgresql server."; + }; + + user = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "panoramax"; + description = "The database user for panoramax."; + }; + + # TODO: password file for external database + }; + + sgblur = { + # TODO: configs to bind to sgblur + }; + }; + sgblur = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to enable sgblur integration for face and license plate blurring"; + }; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.sgblur; + description = "The sgblur package to use"; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 8080; + description = "Port for the sgblur service"; + }; + + host = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = "Host to bind the sgblur service to"; + }; + + url = lib.mkOption { + type = lib.types.str; + default = "http://127.0.0.1:8080"; + description = "URL where sgblur service is accessible"; + }; + }; + }; + + config = lib.mkIf config.services.panoramax.enable (lib.mkMerge [ + { + # TODO: start panoramax service + } + (lib.mkIf config.services.sgblur.enable { + # TODO: start sg blur config + }) + (lib.mkIf config.services.panoramax.database.createDB { + services.postgresql = lib.mkIf config.services.panoramax.database.enable { + enable = true; + ensureDatabases = lib.mkIf config.services.panoramax.database.createDB [config.services.panoramax.database.name]; + ensureUsers = lib.mkIf config.services.panoramax.database.createDB [ + { + name = config.services.panoramax.database.user; + ensureDBOwnership = true; + ensureClauses.login = true; + } + ]; + extensions = ps: with ps; [postgis]; + }; + systemd.services.postgresql.serviceConfig.ExecStartPost = let + sqlFile = pkgs.writeText "panoramax-postgis-setup.sql" '' + CREATE EXTENSION IF NOT EXISTS postgis; + + ALTER DATABASE ${config.services.panoramax.database.name} SET TIMEZONE TO 'UTC'; + + GRANT SET ON PARAMETER session_replication_role TO ${config.services.panoramax.database.user}; + ''; + in [ + '' + ${lib.getExe' config.services.postgresql.package "psql"} -d "${config.services.panoramax.database.user}" -f "${sqlFile}" + '' + ]; + }) + ]); +} From 1b1a3f7219790da0fca2b1012d7edd30642b4cfd Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Tue, 16 Sep 2025 12:40:19 -0500 Subject: [PATCH 31/62] fix: fixed database timezone alter not working --- modules/nixos-modules/server/panoramax/panoramax.nix | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/nixos-modules/server/panoramax/panoramax.nix b/modules/nixos-modules/server/panoramax/panoramax.nix index cdbc632..aae7052 100644 --- a/modules/nixos-modules/server/panoramax/panoramax.nix +++ b/modules/nixos-modules/server/panoramax/panoramax.nix @@ -223,7 +223,7 @@ in { # TODO: start sg blur config }) (lib.mkIf config.services.panoramax.database.createDB { - services.postgresql = lib.mkIf config.services.panoramax.database.enable { + services.postgresql = { enable = true; ensureDatabases = lib.mkIf config.services.panoramax.database.createDB [config.services.panoramax.database.name]; ensureUsers = lib.mkIf config.services.panoramax.database.createDB [ @@ -239,7 +239,8 @@ in { sqlFile = pkgs.writeText "panoramax-postgis-setup.sql" '' CREATE EXTENSION IF NOT EXISTS postgis; - ALTER DATABASE ${config.services.panoramax.database.name} SET TIMEZONE TO 'UTC'; + -- TODO: how can we ensure that this runs after the databases have been created + -- ALTER DATABASE ${config.services.panoramax.database.name} SET TIMEZONE TO 'UTC'; GRANT SET ON PARAMETER session_replication_role TO ${config.services.panoramax.database.user}; ''; From 3bee0c74028118e2fd93b172178e49a54d31efcf Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Wed, 17 Sep 2025 15:15:07 -0500 Subject: [PATCH 32/62] fix: fixed pkg dependencies for panoramax --- .../nixos/defiant/configuration.nix | 4 + modules/common-modules/pkgs/default.nix | 8 + modules/common-modules/pkgs/h3-c-lib.nix | 36 ++++ modules/common-modules/pkgs/panoramax.nix | 40 ++++ .../common-modules/pkgs/python/default.nix | 18 ++ .../pkgs/python/geojson-pydantic.nix | 48 +++++ .../pkgs/python/geopic-tag-reader.nix | 70 +++++++ modules/common-modules/pkgs/python/h3.nix | 81 ++++++++ .../common-modules/pkgs/python/pyexiv2.nix | 49 +++++ .../pkgs/python/pygeofilter.nix | 52 +++++ .../common-modules/pkgs/python/pygeoif.nix | 48 +++++ modules/common-modules/pkgs/python/rfeed.nix | 40 ++++ .../server/panoramax/panoramax.nix | 177 ++++++++++++++---- 13 files changed, 632 insertions(+), 39 deletions(-) create mode 100644 modules/common-modules/pkgs/h3-c-lib.nix create mode 100644 modules/common-modules/pkgs/python/default.nix create mode 100644 modules/common-modules/pkgs/python/geojson-pydantic.nix create mode 100644 modules/common-modules/pkgs/python/geopic-tag-reader.nix create mode 100644 modules/common-modules/pkgs/python/h3.nix create mode 100644 modules/common-modules/pkgs/python/pyexiv2.nix create mode 100644 modules/common-modules/pkgs/python/pygeofilter.nix create mode 100644 modules/common-modules/pkgs/python/pygeoif.nix create mode 100644 modules/common-modules/pkgs/python/rfeed.nix diff --git a/configurations/nixos/defiant/configuration.nix b/configurations/nixos/defiant/configuration.nix index e109d45..9be3065 100644 --- a/configurations/nixos/defiant/configuration.nix +++ b/configurations/nixos/defiant/configuration.nix @@ -306,6 +306,10 @@ passwordFile = config.sops.secrets."services/paperless_password".path; }; + panoramax = { + enable = true; + }; + qbittorrent = { enable = true; mediaDir = "/srv/qbittorent"; diff --git a/modules/common-modules/pkgs/default.nix b/modules/common-modules/pkgs/default.nix index 28141c8..e608350 100644 --- a/modules/common-modules/pkgs/default.nix +++ b/modules/common-modules/pkgs/default.nix @@ -1,4 +1,8 @@ {pkgs, ...}: { + imports = [ + ./python + ]; + nixpkgs.overlays = [ (final: prev: { webtoon-dl = @@ -31,5 +35,9 @@ (final: prev: { sgblur = pkgs.python3.pkgs.callPackage ./sgblur.nix {}; }) + (final: prev: { + # Override h3 C library to version 4.3.0 + h3 = pkgs.callPackage ./h3-c-lib.nix {}; + }) ]; } diff --git a/modules/common-modules/pkgs/h3-c-lib.nix b/modules/common-modules/pkgs/h3-c-lib.nix new file mode 100644 index 0000000..2615d3c --- /dev/null +++ b/modules/common-modules/pkgs/h3-c-lib.nix @@ -0,0 +1,36 @@ +{ + lib, + stdenv, + fetchFromGitHub, + cmake, + doxygen, +}: +stdenv.mkDerivation rec { + pname = "h3"; + version = "4.3.0"; + + src = fetchFromGitHub { + owner = "uber"; + repo = "h3"; + rev = "v${version}"; + hash = "sha256-DUILKZ1QvML6qg+WdOxir6zRsgTvk+En6yjeFf6MQBg="; + }; + + nativeBuildInputs = [ + cmake + doxygen + ]; + + cmakeFlags = [ + "-DBUILD_SHARED_LIBS=ON" + "-DBUILD_TESTING=OFF" + ]; + + meta = with lib; { + homepage = "https://github.com/uber/h3"; + description = "Hexagonal hierarchical geospatial indexing system"; + license = licenses.asl20; + maintainers = []; + platforms = platforms.all; + }; +} diff --git a/modules/common-modules/pkgs/panoramax.nix b/modules/common-modules/pkgs/panoramax.nix index e2dad14..75b5e0e 100644 --- a/modules/common-modules/pkgs/panoramax.nix +++ b/modules/common-modules/pkgs/panoramax.nix @@ -10,8 +10,27 @@ authlib, sentry-sdk, python-dateutil, + dateparser, croniter, pydantic, + flask-cors, + flask-compress, + flask-babel, + flasgger, + yoyo-migrations, + psycopg, + psycopg-pool, + tzdata, + email-validator, + pydantic-extra-types, + python-multipart, + fs, + fs-s3fs, + geopic-tag-reader, + pygeofilter, + pygeoif, + rfeed, + geojson-pydantic, ... }: let pname = "geovisio"; @@ -42,8 +61,29 @@ in authlib sentry-sdk python-dateutil + dateparser croniter pydantic + flask-cors + flask-compress + flask-babel + flasgger + yoyo-migrations + psycopg + psycopg-pool + tzdata + email-validator + pydantic-extra-types + python-multipart + fs + fs-s3fs + geopic-tag-reader + pygeofilter + pygeoif + rfeed + geojson-pydantic + # Missing from nixpkgs - may need custom packages: + # flask-executor ]; # Skip tests as they may require network access or specific setup diff --git a/modules/common-modules/pkgs/python/default.nix b/modules/common-modules/pkgs/python/default.nix new file mode 100644 index 0000000..f69c512 --- /dev/null +++ b/modules/common-modules/pkgs/python/default.nix @@ -0,0 +1,18 @@ +{...}: { + nixpkgs.overlays = [ + (final: prev: { + python3 = prev.python3.override { + packageOverrides = pythonPrev: pythonFinal: { + h3 = pythonPrev.callPackage ./h3.nix {h3 = final.h3;}; + pygeofilter = pythonPrev.callPackage ./pygeofilter.nix {}; + pygeoif = pythonPrev.callPackage ./pygeoif.nix {}; + rfeed = pythonPrev.callPackage ./rfeed.nix {}; + pyexiv2 = pythonPrev.callPackage ./pyexiv2.nix {}; + geojson-pydantic = pythonPrev.callPackage ./geojson-pydantic.nix {}; + geopic-tag-reader = pythonPrev.callPackage ./geopic-tag-reader.nix {}; + }; + }; + python3Packages = final.python3.pkgs; + }) + ]; +} diff --git a/modules/common-modules/pkgs/python/geojson-pydantic.nix b/modules/common-modules/pkgs/python/geojson-pydantic.nix new file mode 100644 index 0000000..96ec6b5 --- /dev/null +++ b/modules/common-modules/pkgs/python/geojson-pydantic.nix @@ -0,0 +1,48 @@ +{ + lib, + fetchPypi, + buildPythonPackage, + flit-core, + pydantic, + geojson, + ... +}: let + pname = "geojson_pydantic"; + version = "2.0.0"; +in + buildPythonPackage { + inherit pname version; + + pyproject = true; + + src = fetchPypi { + inherit pname version; + hash = "sha256-ti6LRFAt0a1Ri19zkDWoGSSnb5gMvbOk6JFu+RO+JC4="; + }; + + build-system = [ + flit-core + ]; + + dependencies = [ + pydantic + geojson + ]; + + # Skip tests as they may require specific setup + doCheck = false; + + # Disable runtime dependencies check + dontCheckRuntimeDeps = true; + + # Basic imports check + pythonImportsCheck = ["geojson_pydantic"]; + + meta = with lib; { + description = "Pydantic models for GeoJSON objects"; + homepage = "https://github.com/developmentseed/geojson-pydantic"; + license = licenses.mit; + maintainers = []; + platforms = platforms.all; + }; + } diff --git a/modules/common-modules/pkgs/python/geopic-tag-reader.nix b/modules/common-modules/pkgs/python/geopic-tag-reader.nix new file mode 100644 index 0000000..bd8451f --- /dev/null +++ b/modules/common-modules/pkgs/python/geopic-tag-reader.nix @@ -0,0 +1,70 @@ +{ + lib, + fetchFromGitLab, + buildPythonPackage, + flit-core, + typer, + xmltodict, + timezonefinder, + pytz, + types-pytz, + types-python-dateutil, + rtree, + python-dateutil, + pyexiv2, + ... +}: let + pname = "geopic-tag-reader"; + version = "1.8.0"; +in + buildPythonPackage { + inherit pname version; + + pyproject = true; + + src = fetchFromGitLab { + owner = "panoramax"; + repo = "server/geo-picture-tag-reader"; + rev = version; + sha256 = "0lzf5xxxcdqmq28bpvgpkxf5jxmh2nawwa4rl4yg04bdsi16rf1j"; + }; + + build-system = [ + flit-core + ]; + + dependencies = [ + typer + xmltodict + pyexiv2 + timezonefinder + pytz + types-pytz + types-python-dateutil + rtree + ]; + + optional-dependencies = { + write-exif = [ + python-dateutil + types-python-dateutil + ]; + }; + + # Skip tests as they may require network access or specific setup + doCheck = false; + + # Disable runtime dependencies check as some dependencies might have issues + dontCheckRuntimeDeps = true; + + # Disable imports check initially to avoid dependency issues + pythonImportsCheck = []; + + meta = with lib; { + description = "GeoPic Tag Reader - Python library to read and write standardized metadata from geolocated pictures EXIF metadata"; + homepage = "https://gitlab.com/panoramax/server/geo-picture-tag-reader"; + license = licenses.mit; + maintainers = []; + platforms = platforms.all; + }; + } diff --git a/modules/common-modules/pkgs/python/h3.nix b/modules/common-modules/pkgs/python/h3.nix new file mode 100644 index 0000000..2dc3d26 --- /dev/null +++ b/modules/common-modules/pkgs/python/h3.nix @@ -0,0 +1,81 @@ +{ + autoPatchelfHook, + buildPythonPackage, + cmake, + cython, + fetchFromGitHub, + h3, + lib, + ninja, + numpy, + pytestCheckHook, + pytest-cov-stub, + scikit-build-core, + stdenv, +}: +buildPythonPackage rec { + pname = "h3"; + version = "4.3.1"; + pyproject = true; + + # pypi version does not include tests + src = fetchFromGitHub { + owner = "uber"; + repo = "h3-py"; + tag = "v${version}"; + hash = "sha256-zt7zbBgSp2P9q7mObZeQZpW9Szip62dAYdPZ2cGTmi4="; + }; + + dontConfigure = true; + + nativeCheckInputs = [ + pytestCheckHook + pytest-cov-stub + ]; + + build-system = + [ + scikit-build-core + cmake + cython + ninja + ] + ++ lib.optionals stdenv.hostPlatform.isLinux [ + # On Linux the .so files ends up referring to libh3.so instead of the full + # Nix store path. I'm not sure why this is happening! On Darwin it works + # fine. + autoPatchelfHook + ]; + + # This is not needed per-se, it's only added for autoPatchelfHook to work + # correctly. See the note above ^^ + buildInputs = lib.optionals stdenv.hostPlatform.isLinux [h3]; + + dependencies = [numpy]; + + # The following prePatch replaces the h3lib compilation with using the h3 packaged in nixpkgs. + # + # - Remove the h3lib submodule. + # - Patch CMakeLists to avoid building h3lib, and use h3 instead. + prePatch = let + cmakeCommands = '' + include_directories(${lib.getDev h3}/include/h3) + link_directories(${h3}/lib) + ''; + in '' + rm -r src/h3lib + substituteInPlace CMakeLists.txt \ + --replace-fail "add_subdirectory(src/h3lib)" "${cmakeCommands}" \ + --replace-fail "\''${CMAKE_CURRENT_BINARY_DIR}/src/h3lib/src/h3lib/include/h3api.h" "${lib.getDev h3}/include/h3/h3api.h" + ''; + + # Extra check to make sure we can import it from Python + pythonImportsCheck = ["h3"]; + + meta = { + homepage = "https://github.com/uber/h3-py"; + description = "Hierarchical hexagonal geospatial indexing system"; + license = lib.licenses.asl20; + maintainers = [lib.maintainers.kalbasit]; + }; +} diff --git a/modules/common-modules/pkgs/python/pyexiv2.nix b/modules/common-modules/pkgs/python/pyexiv2.nix new file mode 100644 index 0000000..69fa537 --- /dev/null +++ b/modules/common-modules/pkgs/python/pyexiv2.nix @@ -0,0 +1,49 @@ +{ + lib, + fetchFromGitHub, + buildPythonPackage, + exiv2, + boost, + pybind11, + setuptools, + ... +}: let + pname = "pyexiv2"; + version = "2.15.3"; +in + buildPythonPackage { + inherit pname version; + + pyproject = true; + build-system = [setuptools]; + + src = fetchFromGitHub { + owner = "LeoHsiao1"; + repo = "pyexiv2"; + rev = "v${version}"; + sha256 = "sha256-83bFMaoXncvhRJNcCgkkC7B29wR5pjuLO/EdkQdqxxo="; + }; + + buildInputs = [ + exiv2 + boost + ]; + + nativeBuildInputs = [ + pybind11 + ]; + + # Skip tests as they may require specific test images + doCheck = false; + + # Disable runtime dependencies check initially + dontCheckRuntimeDeps = true; + + meta = with lib; { + description = "Python binding to the library exiv2"; + homepage = "https://github.com/LeoHsiao1/pyexiv2"; + license = licenses.gpl3Plus; + maintainers = []; + platforms = platforms.linux; + }; + } diff --git a/modules/common-modules/pkgs/python/pygeofilter.nix b/modules/common-modules/pkgs/python/pygeofilter.nix new file mode 100644 index 0000000..aa310f9 --- /dev/null +++ b/modules/common-modules/pkgs/python/pygeofilter.nix @@ -0,0 +1,52 @@ +{ + lib, + fetchPypi, + buildPythonPackage, + setuptools, + wheel, + lark, + python-dateutil, + shapely, + ... +}: let + pname = "pygeofilter"; + version = "0.3.1"; +in + buildPythonPackage { + inherit pname version; + + pyproject = true; + + src = fetchPypi { + inherit pname version; + hash = "sha256-+SvAYiCZ+H/os23nq92GBZ1hWontYIInNwgiI6V44VA="; + }; + + build-system = [ + setuptools + wheel + ]; + + dependencies = [ + lark + python-dateutil + shapely + ]; + + # Skip tests as they may require specific setup + doCheck = false; + + # Disable runtime dependencies check + dontCheckRuntimeDeps = true; + + # Basic imports check + pythonImportsCheck = ["pygeofilter"]; + + meta = with lib; { + description = "A pure Python parser implementation of OGC filtering standards"; + homepage = "https://github.com/geopython/pygeofilter"; + license = licenses.mit; + maintainers = []; + platforms = platforms.all; + }; + } diff --git a/modules/common-modules/pkgs/python/pygeoif.nix b/modules/common-modules/pkgs/python/pygeoif.nix new file mode 100644 index 0000000..12b8b12 --- /dev/null +++ b/modules/common-modules/pkgs/python/pygeoif.nix @@ -0,0 +1,48 @@ +{ + lib, + fetchPypi, + buildPythonPackage, + setuptools, + wheel, + typing-extensions, + ... +}: let + pname = "pygeoif"; + version = "1.5.1"; +in + buildPythonPackage { + inherit pname version; + + pyproject = true; + + src = fetchPypi { + inherit pname version; + hash = "sha256-8nprah7Lh66swrUbzFnKeb5w7RKgEE3oYBR4shPdXYE="; + }; + + build-system = [ + setuptools + wheel + ]; + + dependencies = [ + typing-extensions + ]; + + # Skip tests as they may require specific setup + doCheck = false; + + # Disable runtime dependencies check + dontCheckRuntimeDeps = true; + + # Basic imports check + pythonImportsCheck = ["pygeoif"]; + + meta = with lib; { + description = "A basic implementation of the __geo_interface__"; + homepage = "https://github.com/cleder/pygeoif"; + license = licenses.lgpl21Plus; + maintainers = []; + platforms = platforms.all; + }; + } diff --git a/modules/common-modules/pkgs/python/rfeed.nix b/modules/common-modules/pkgs/python/rfeed.nix new file mode 100644 index 0000000..0be8ab9 --- /dev/null +++ b/modules/common-modules/pkgs/python/rfeed.nix @@ -0,0 +1,40 @@ +{ + lib, + fetchPypi, + buildPythonPackage, + setuptools, + python-dateutil, +}: +buildPythonPackage rec { + pname = "rfeed"; + version = "1.1.1"; + pyproject = true; + + src = fetchPypi { + inherit pname version; + hash = "sha256-qpUG8oZrdPWjItOUoUpjwZpoJcLZR1X/GdRt0eJDSBk="; + }; + + build-system = [ + setuptools + ]; + + dependencies = [ + python-dateutil + ]; + + # No tests available in the package + doCheck = false; + + pythonImportsCheck = [ + "rfeed" + ]; + + meta = with lib; { + description = "RSS feed generation library for Python"; + homepage = "https://pypi.org/project/rfeed/"; + license = licenses.mit; + maintainers = []; + platforms = platforms.all; + }; +} diff --git a/modules/nixos-modules/server/panoramax/panoramax.nix b/modules/nixos-modules/server/panoramax/panoramax.nix index aae7052..2af9982 100644 --- a/modules/nixos-modules/server/panoramax/panoramax.nix +++ b/modules/nixos-modules/server/panoramax/panoramax.nix @@ -3,43 +3,7 @@ lib, pkgs, ... -}: let - dbUrlConfigured = config.services.panoramax.database.url != null; - individualDbConfigured = lib.all (x: x != null) [ - config.services.panoramax.database.host - config.services.panoramax.database.port - config.services.panoramax.database.username - config.services.panoramax.database.password - config.services.panoramax.database.name - ]; - - envContent = '' - # Panoramax Configuration - FLASK_APP=geovisio - ${ - if dbUrlConfigured - then "DB_URL=${config.services.panoramax.database.url}" - else '' - DB_HOST=${config.services.panoramax.database.host} - DB_PORT=${toString config.services.panoramax.database.port} - DB_USERNAME=${config.services.panoramax.database.username} - DB_PASSWORD=${config.services.panoramax.database.password} - DB_NAME=${config.services.panoramax.database.name} - '' - } - ${lib.optionalString (config.services.panoramax.storage.fsUrl != null) "FS_URL=${config.services.panoramax.storage.fsUrl}"} - ${lib.optionalString (config.services.panoramax.infrastructure.nbProxies != null) "INFRA_NB_PROXIES=${toString config.services.panoramax.infrastructure.nbProxies}"} - ${lib.optionalString (config.services.panoramax.flask.secretKey != null) "FLASK_SECRET_KEY=${config.services.panoramax.flask.secretKey}"} - ${lib.optionalString (config.services.panoramax.flask.sessionCookieDomain != null) "FLASK_SESSION_COOKIE_DOMAIN=${config.services.panoramax.flask.sessionCookieDomain}"} - ${lib.optionalString (config.services.panoramax.api.pictures.licenseSpdxId != null) "API_PICTURES_LICENSE_SPDX_ID=${config.services.panoramax.api.pictures.licenseSpdxId}"} - ${lib.optionalString (config.services.panoramax.api.pictures.licenseUrl != null) "API_PICTURES_LICENSE_URL=${config.services.panoramax.api.pictures.licenseUrl}"} - ${lib.optionalString (config.services.panoramax.port != null) "PORT=${toString config.services.panoramax.port}"} - ${lib.optionalString (config.services.panoramax.sgblur.enable) "SGBLUR_API_URL=${config.services.panoramax.sgblur.url}"} - ${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name}=${value}") config.services.panoramax.extraEnvironment)} - ''; - - envFile = pkgs.writeText "panoramax.env" envContent; -in { +}: { options.services = { panoramax = { enable = lib.mkEnableOption "panoramax"; @@ -217,10 +181,145 @@ in { config = lib.mkIf config.services.panoramax.enable (lib.mkMerge [ { - # TODO: start panoramax service + # Create panoramax user and group + users.users.${config.services.panoramax.user} = { + isSystemUser = true; + group = config.services.panoramax.group; + home = "/var/lib/panoramax"; + createHome = true; + }; + + users.groups.${config.services.panoramax.group} = {}; + + # Ensure storage directory exists with correct permissions + systemd.tmpfiles.rules = [ + "d '${config.services.panoramax.settings.storage.fsUrl}' 0755 ${config.services.panoramax.user} ${config.services.panoramax.group} - -" + ]; + + systemd.services.panoramax-api = { + description = "Panoramax API server (self hosted map street view)"; + after = ["network.target" "postgresql.service"]; + wantedBy = ["multi-user.target"]; + + environment = + { + # Core Flask configuration + FLASK_APP = "geovisio"; + + # Database configuration + DB_HOST = config.services.panoramax.database.host; + DB_PORT = toString config.services.panoramax.database.port; + DB_USERNAME = config.services.panoramax.database.user; + DB_NAME = config.services.panoramax.database.name; + + # Storage configuration + FS_URL = config.services.panoramax.settings.storage.fsUrl; + + # Infrastructure configuration + INFRA_NB_PROXIES = toString config.services.panoramax.settings.infrastructure.nbProxies; + + # Application configuration + PORT = toString config.services.panoramax.port; + + # Python path to include the panoramax package + PYTHONPATH = "${config.services.panoramax.package}/${pkgs.python3.sitePackages}"; + } + // (lib.optionalAttrs (config.services.panoramax.settings.flask.secretKey != null) { + FLASK_SECRET_KEY = config.services.panoramax.settings.flask.secretKey; + }) + // (lib.optionalAttrs (config.services.panoramax.settings.flask.sessionCookieDomain != null) { + FLASK_SESSION_COOKIE_DOMAIN = config.services.panoramax.settings.flask.sessionCookieDomain; + }) + // (lib.optionalAttrs (config.services.panoramax.settings.api.pictures.licenseSpdxId != null) { + API_PICTURES_LICENSE_SPDX_ID = config.services.panoramax.settings.api.pictures.licenseSpdxId; + }) + // (lib.optionalAttrs (config.services.panoramax.settings.api.pictures.licenseUrl != null) { + API_PICTURES_LICENSE_URL = config.services.panoramax.settings.api.pictures.licenseUrl; + }) + // (lib.optionalAttrs config.services.sgblur.enable { + SGBLUR_API_URL = config.services.sgblur.url; + }) + // config.services.panoramax.settings.extraEnvironment; + + path = with pkgs; [ + (python3.withPackages (ps: with ps; [config.services.panoramax.package waitress])) + ]; + + serviceConfig = { + ExecStart = "${pkgs.python3.withPackages (ps: with ps; [config.services.panoramax.package waitress])}/bin/waitress-serve --port ${toString config.services.panoramax.port} --call geovisio:create_app"; + User = config.services.panoramax.user; + Group = config.services.panoramax.group; + WorkingDirectory = "/var/lib/panoramax"; + Restart = "always"; + RestartSec = 5; + + # Security hardening + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + ReadWritePaths = [ + "/var/lib/panoramax" + config.services.panoramax.settings.storage.fsUrl + ]; + NoNewPrivileges = true; + PrivateDevices = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictSUIDSGID = true; + RestrictRealtime = true; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + SystemCallArchitectures = "native"; + }; + }; + + # Open firewall if requested + networking.firewall.allowedTCPPorts = lib.mkIf config.services.panoramax.openFirewall [ + config.services.panoramax.port + ]; } (lib.mkIf config.services.sgblur.enable { - # TODO: start sg blur config + # SGBlur service configuration + systemd.services.sgblur = { + description = "SGBlur face and license plate blurring service"; + after = ["network.target"]; + wantedBy = ["multi-user.target"]; + + path = with pkgs; [ + config.services.sgblur.package + python3 + python3Packages.waitress + ]; + + serviceConfig = { + ExecStart = "${pkgs.python3Packages.waitress}/bin/waitress-serve --host ${config.services.sgblur.host} --port ${toString config.services.sgblur.port} src.detect.detect_api:app"; + WorkingDirectory = "${config.services.sgblur.package}"; + Restart = "always"; + RestartSec = 5; + + # Basic security hardening + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + NoNewPrivileges = true; + PrivateDevices = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictSUIDSGID = true; + RestrictRealtime = true; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + SystemCallArchitectures = "native"; + }; + }; + + networking.firewall.allowedTCPPorts = lib.mkIf config.services.panoramax.openFirewall [ + config.services.sgblur.port + ]; }) (lib.mkIf config.services.panoramax.database.createDB { services.postgresql = { From 7e6fa744af1710f58828f9cee05bce32075b03e2 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Wed, 17 Sep 2025 19:42:15 -0500 Subject: [PATCH 33/62] fix: wrapped prostudiomasters in --in-process-gpu flag --- modules/common-modules/pkgs/default.nix | 1 - .../common-modules/pkgs/prostudiomasters.nix | 25 ++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/modules/common-modules/pkgs/default.nix b/modules/common-modules/pkgs/default.nix index e608350..c97f97c 100644 --- a/modules/common-modules/pkgs/default.nix +++ b/modules/common-modules/pkgs/default.nix @@ -10,7 +10,6 @@ ./webtoon-dl.nix {}; }) - # TODO: this package always needs to be called with the --in-process-gpu flag for some reason, can we automate that? (final: prev: { prostudiomasters = pkgs.callPackage diff --git a/modules/common-modules/pkgs/prostudiomasters.nix b/modules/common-modules/pkgs/prostudiomasters.nix index c1c03fe..1a3ad01 100644 --- a/modules/common-modules/pkgs/prostudiomasters.nix +++ b/modules/common-modules/pkgs/prostudiomasters.nix @@ -1,6 +1,7 @@ { fetchurl, appimageTools, + writeShellScript, }: let pname = "prostudiomasters"; version = "2.5.6"; @@ -8,7 +9,25 @@ url = "https://download.prostudiomasters.com/linux/ProStudioMasters-${version}.AppImage"; hash = "sha256-7owOwdcucFfl+JsVj+Seau2KOz0J4P/ep7WrBSNSmbs="; }; -in - appimageTools.wrapType2 { + + # Create the base AppImage wrapper + baseApp = appimageTools.wrapType2 { inherit pname version src; - } + }; + + # Create a wrapper script that automatically adds the --in-process-gpu flag + wrapper = writeShellScript "prostudiomasters-wrapper" '' + exec ${baseApp}/bin/prostudiomasters --in-process-gpu "$@" + ''; +in + # Override the base app to use our wrapper script + baseApp.overrideAttrs (oldAttrs: { + buildCommand = + oldAttrs.buildCommand + + '' + # Replace the original binary with our wrapper + rm $out/bin/prostudiomasters + cp ${wrapper} $out/bin/prostudiomasters + chmod +x $out/bin/prostudiomasters + ''; + }) From 333c68a8cd047098c111aa409b0ea89d30d50561 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Wed, 17 Sep 2025 22:18:15 -0500 Subject: [PATCH 34/62] feat: created db config for panoramax --- .../nixos/defiant/configuration.nix | 1 + .../server/panoramax/database.nix | 29 +++++++++++++++++++ .../server/panoramax/default.nix | 1 + 3 files changed, 31 insertions(+) create mode 100644 modules/nixos-modules/server/panoramax/database.nix diff --git a/configurations/nixos/defiant/configuration.nix b/configurations/nixos/defiant/configuration.nix index 9be3065..a309704 100644 --- a/configurations/nixos/defiant/configuration.nix +++ b/configurations/nixos/defiant/configuration.nix @@ -308,6 +308,7 @@ panoramax = { enable = true; + openFirewall = true; }; qbittorrent = { diff --git a/modules/nixos-modules/server/panoramax/database.nix b/modules/nixos-modules/server/panoramax/database.nix new file mode 100644 index 0000000..3cf3455 --- /dev/null +++ b/modules/nixos-modules/server/panoramax/database.nix @@ -0,0 +1,29 @@ +{ + lib, + config, + ... +}: { + config = lib.mkIf config.services.panoramax.enable (lib.mkMerge [ + { + host = { + postgres = { + enable = true; + }; + }; + } + (lib.mkIf config.host.postgres.enable { + host = { + postgres = { + extraUsers = { + ${config.services.panoramax.database.user} = { + isClient = true; + }; + }; + extraDatabases = { + ${config.services.panoramax.database.name} = {}; + }; + }; + }; + }) + ]); +} diff --git a/modules/nixos-modules/server/panoramax/default.nix b/modules/nixos-modules/server/panoramax/default.nix index f029ee3..4c6b9ea 100644 --- a/modules/nixos-modules/server/panoramax/default.nix +++ b/modules/nixos-modules/server/panoramax/default.nix @@ -4,5 +4,6 @@ ./fail2ban.nix ./impermanence.nix ./panoramax.nix + ./database.nix ]; } From 2cdc39f3dcc29090d48d54bc8f24a67303e99ff2 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 18 Sep 2025 14:19:57 -0500 Subject: [PATCH 35/62] fix: disabled broken panoramax config --- .../nixos/defiant/configuration.nix | 2 +- .../server/panoramax/database.nix | 27 +++++++++++-------- .../server/panoramax/panoramax.nix | 18 ++++++++----- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/configurations/nixos/defiant/configuration.nix b/configurations/nixos/defiant/configuration.nix index a309704..d10bea0 100644 --- a/configurations/nixos/defiant/configuration.nix +++ b/configurations/nixos/defiant/configuration.nix @@ -307,7 +307,7 @@ }; panoramax = { - enable = true; + enable = false; openFirewall = true; }; diff --git a/modules/nixos-modules/server/panoramax/database.nix b/modules/nixos-modules/server/panoramax/database.nix index 3cf3455..8679f9a 100644 --- a/modules/nixos-modules/server/panoramax/database.nix +++ b/modules/nixos-modules/server/panoramax/database.nix @@ -11,19 +11,24 @@ }; }; } - (lib.mkIf config.host.postgres.enable { - host = { - postgres = { - extraUsers = { - ${config.services.panoramax.database.user} = { - isClient = true; + ( + lib.mkIf config.host.postgres.enable { + host = { + postgres = { + extraUsers = { + ${config.services.panoramax.database.user} = { + isClient = true; + createUser = true; + }; + }; + extraDatabases = { + ${config.services.panoramax.database.name} = { + name = config.services.panoramax.database.user; + }; }; }; - extraDatabases = { - ${config.services.panoramax.database.name} = {}; - }; }; - }; - }) + } + ) ]); } diff --git a/modules/nixos-modules/server/panoramax/panoramax.nix b/modules/nixos-modules/server/panoramax/panoramax.nix index 2af9982..fd77db7 100644 --- a/modules/nixos-modules/server/panoramax/panoramax.nix +++ b/modules/nixos-modules/server/panoramax/panoramax.nix @@ -206,12 +206,6 @@ # Core Flask configuration FLASK_APP = "geovisio"; - # Database configuration - DB_HOST = config.services.panoramax.database.host; - DB_PORT = toString config.services.panoramax.database.port; - DB_USERNAME = config.services.panoramax.database.user; - DB_NAME = config.services.panoramax.database.name; - # Storage configuration FS_URL = config.services.panoramax.settings.storage.fsUrl; @@ -224,6 +218,18 @@ # Python path to include the panoramax package PYTHONPATH = "${config.services.panoramax.package}/${pkgs.python3.sitePackages}"; } + // ( + if config.services.panoramax.database.host == "/run/postgresql" + then { + DB_URL = "postgresql://${config.services.panoramax.database.user}@/${config.services.panoramax.database.name}?host=/run/postgresql"; + } + else { + DB_HOST = config.services.panoramax.database.host; + DB_PORT = toString config.services.panoramax.database.port; + DB_USERNAME = config.services.panoramax.database.user; + DB_NAME = config.services.panoramax.database.name; + } + ) // (lib.optionalAttrs (config.services.panoramax.settings.flask.secretKey != null) { FLASK_SECRET_KEY = config.services.panoramax.settings.flask.secretKey; }) From ca6de5c0cda53b0b47686bfcd58050cc789bd9b8 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 18 Sep 2025 23:40:13 -0500 Subject: [PATCH 36/62] chore: added talk to readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 62040e3..f1521ce 100644 --- a/README.md +++ b/README.md @@ -69,4 +69,5 @@ nix multi user, multi system, configuration with `sops` secret management, `home - ISO target that contains authorized keys for nixos-anywhere https://github.com/diegofariasm/yggdrasil/blob/4acc43ebc7bcbf2e41376d14268e382007e94d78/hosts/bootstrap/default.nix - panoramax instance - mastodon instance -- move searx, home-assistant, actual, jellyfin, paperless, and immich to only be accessible via vpn \ No newline at end of file +- move searx, home-assistant, actual, jellyfin, paperless, and immich to only be accessible via vpn +- graphana accessible though tailscale \ No newline at end of file From d35e2c93c1e38f8d67847e2b7be17034665615a8 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sun, 21 Sep 2025 21:04:47 -0500 Subject: [PATCH 37/62] feat: added option for auto aprove set root for vitest mcp server --- configurations/home-manager/leyla/packages/vscode/default.nix | 1 + modules/home-manager-modules/programs/vscode/claudeDev.nix | 1 + 2 files changed, 2 insertions(+) diff --git a/configurations/home-manager/leyla/packages/vscode/default.nix b/configurations/home-manager/leyla/packages/vscode/default.nix index 583f440..981156b 100644 --- a/configurations/home-manager/leyla/packages/vscode/default.nix +++ b/configurations/home-manager/leyla/packages/vscode/default.nix @@ -84,6 +84,7 @@ in { list_tests = true; run_tests = true; analyze_coverage = true; + set_project_root = true; }; }; sleep = { diff --git a/modules/home-manager-modules/programs/vscode/claudeDev.nix b/modules/home-manager-modules/programs/vscode/claudeDev.nix index 0e34f97..cebf614 100644 --- a/modules/home-manager-modules/programs/vscode/claudeDev.nix +++ b/modules/home-manager-modules/programs/vscode/claudeDev.nix @@ -105,6 +105,7 @@ in { list_tests = lib.mkEnableOption "Should the list_tests tool be auto approved for Vitest MCP server"; run_tests = lib.mkEnableOption "Should the run_tests tool be auto approved for Vitest MCP server"; analyze_coverage = lib.mkEnableOption "Should the analyze_coverage tool be auto approved for Vitest MCP server"; + set_project_root = lib.mkEnableOption "Should the set_project_root tool be auto approved for Vitest MCP server"; }; }; sleep = { From ee80636b2b4c958d3d003450371024ac71570114 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Wed, 24 Sep 2025 09:51:27 -0500 Subject: [PATCH 38/62] build: updated flake lock --- flake.lock | 71 ++++++++------------- flake.nix | 5 -- modules/common-modules/overlays/default.nix | 1 - 3 files changed, 25 insertions(+), 52 deletions(-) diff --git a/flake.lock b/flake.lock index b6e48bb..6123425 100644 --- a/flake.lock +++ b/flake.lock @@ -25,11 +25,11 @@ ] }, "locked": { - "lastModified": 1757508292, - "narHash": "sha256-7lVWL5bC6xBIMWWDal41LlGAG+9u2zUorqo3QCUL4p4=", + "lastModified": 1758287904, + "narHash": "sha256-IGmaEf3Do8o5Cwp1kXBN1wQmZwQN3NLfq5t4nHtVtcU=", "owner": "nix-community", "repo": "disko", - "rev": "146f45bee02b8bd88812cfce6ffc0f933788875a", + "rev": "67ff9807dd148e704baadbd4fd783b54282ca627", "type": "github" }, "original": { @@ -46,11 +46,11 @@ }, "locked": { "dir": "pkgs/firefox-addons", - "lastModified": 1757995413, - "narHash": "sha256-vaU/7/PXoym6vnspGxhR29V9klGe9iy9zmp6x7w38f8=", + "lastModified": 1758600213, + "narHash": "sha256-YP7+UxybMCzHPd5k93pulILnFvSisjgUAGUB/cxWbqU=", "owner": "rycee", "repo": "nur-expressions", - "rev": "4ae8996b3e139926c784acd22824cde46cd28833", + "rev": "8a0333bf11a0fab386c80fa018617bb050156ec5", "type": "gitlab" }, "original": { @@ -118,11 +118,11 @@ ] }, "locked": { - "lastModified": 1757997814, - "narHash": "sha256-F+1aoG+3NH4jDDEmhnDUReISyq6kQBBuktTUqCUWSiw=", + "lastModified": 1758719930, + "narHash": "sha256-DgHe1026Ob49CPegPMiWj1HNtlMTGQzfSZQQVlHC950=", "owner": "nix-community", "repo": "home-manager", - "rev": "5820376beb804de9acf07debaaff1ac84728b708", + "rev": "142acd7a7d9eb7f0bb647f053b4ddfd01fdfbf1d", "type": "github" }, "original": { @@ -175,11 +175,11 @@ ] }, "locked": { - "lastModified": 1757430124, - "narHash": "sha256-MhDltfXesGH8VkGv3hmJ1QEKl1ChTIj9wmGAFfWj/Wk=", + "lastModified": 1758447883, + "narHash": "sha256-yGA6MV0E4JSEXqLTb4ZZkmdJZcoQ8HUzihRRX12Bvpg=", "owner": "LnL7", "repo": "nix-darwin", - "rev": "830b3f0b50045cf0bcfd4dab65fad05bf882e196", + "rev": "25381509d5c91bbf3c30e23abc6d8476d2143cd1", "type": "github" }, "original": { @@ -217,11 +217,11 @@ ] }, "locked": { - "lastModified": 1757987448, - "narHash": "sha256-ltDT7EIfLHV42p99HnDfDviC8jN7tcOed1qsLEFypl8=", + "lastModified": 1758678836, + "narHash": "sha256-ewDKEXcKYF7L+EGVa+8E1nxK1pdwVrCHcj5UhuGA8V0=", "owner": "nix-community", "repo": "nix-vscode-extensions", - "rev": "e496568b0e69d9d54c8cfef96ed1370952ad9786", + "rev": "5007786714b3573b37cf3b8c4a33e2ddce86960d", "type": "github" }, "original": { @@ -232,11 +232,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1757943327, - "narHash": "sha256-w6cDExPBqbq7fTLo4dZ1ozDGeq3yV6dSN4n/sAaS6OM=", + "lastModified": 1758663926, + "narHash": "sha256-6CFdj7Xs616t1W4jLDH7IohAAvl5Dyib3qEv/Uqw1rk=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "67a709cfe5d0643dafd798b0b613ed579de8be05", + "rev": "170ff93c860b2a9868ed1e1102d4e52cb3d934e1", "type": "github" }, "original": { @@ -264,11 +264,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1757745802, - "narHash": "sha256-hLEO2TPj55KcUFUU1vgtHE9UEIOjRcH/4QbmfHNF820=", + "lastModified": 1758427187, + "narHash": "sha256-pHpxZ/IyCwoTQPtFIAG2QaxuSm8jWzrzBGjwQZIttJc=", "owner": "nixos", "repo": "nixpkgs", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", + "rev": "554be6495561ff07b6c724047bdd7e0716aa7b46", "type": "github" }, "original": { @@ -292,8 +292,7 @@ "nixos-hardware": "nixos-hardware", "nixpkgs": "nixpkgs_2", "secrets": "secrets", - "sops-nix": "sops-nix", - "steam-fetcher": "steam-fetcher" + "sops-nix": "sops-nix" } }, "secrets": { @@ -319,11 +318,11 @@ ] }, "locked": { - "lastModified": 1758007585, - "narHash": "sha256-HYnwlbY6RE5xVd5rh0bYw77pnD8lOgbT4mlrfjgNZ0c=", + "lastModified": 1758425756, + "narHash": "sha256-L3N8zV6wsViXiD8i3WFyrvjDdz76g3tXKEdZ4FkgQ+Y=", "owner": "Mic92", "repo": "sops-nix", - "rev": "f77d4cfa075c3de66fc9976b80e0c4fc69e2c139", + "rev": "e0fdaea3c31646e252a60b42d0ed8eafdb289762", "type": "github" }, "original": { @@ -332,26 +331,6 @@ "type": "github" } }, - "steam-fetcher": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1714795926, - "narHash": "sha256-PkgC9jqoN6cJ8XYzTA2PlrWs7aPJkM3BGiTxNqax0cA=", - "owner": "nix-community", - "repo": "steam-fetcher", - "rev": "12f66eafb7862d91b3e30c14035f96a21941bd9c", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "steam-fetcher", - "type": "github" - } - }, "systems": { "locked": { "lastModified": 1681028828, diff --git a/flake.nix b/flake.nix index 7980012..151a54b 100644 --- a/flake.nix +++ b/flake.nix @@ -72,11 +72,6 @@ url = "github:edolstra/flake-compat"; }; - steam-fetcher = { - url = "github:nix-community/steam-fetcher"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - # MCP NixOS server for Claude Dev mcp-nixos = { url = "github:utensils/mcp-nixos"; diff --git a/modules/common-modules/overlays/default.nix b/modules/common-modules/overlays/default.nix index 465e83f..2c0f712 100644 --- a/modules/common-modules/overlays/default.nix +++ b/modules/common-modules/overlays/default.nix @@ -1,7 +1,6 @@ # this folder is for derivation overlays {inputs, ...}: { nixpkgs.overlays = [ - inputs.steam-fetcher.overlays.default inputs.nix-vscode-extensions.overlays.default ]; } From 4d52c58f79d13c25fa1ca098f37db04ee9e9e33f Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 25 Sep 2025 14:58:51 -0500 Subject: [PATCH 39/62] feat: instealled media editing programs for defiant --- configurations/nixos/defiant/default.nix | 1 + configurations/nixos/defiant/packages.nix | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 configurations/nixos/defiant/packages.nix diff --git a/configurations/nixos/defiant/default.nix b/configurations/nixos/defiant/default.nix index fe850af..3013946 100644 --- a/configurations/nixos/defiant/default.nix +++ b/configurations/nixos/defiant/default.nix @@ -3,5 +3,6 @@ imports = [ ./hardware-configuration.nix ./configuration.nix + ./packages.nix ]; } diff --git a/configurations/nixos/defiant/packages.nix b/configurations/nixos/defiant/packages.nix new file mode 100644 index 0000000..45780b0 --- /dev/null +++ b/configurations/nixos/defiant/packages.nix @@ -0,0 +1,9 @@ +{pkgs, ...}: { + environment.systemPackages = with pkgs; [ + ffsubsync + sox + yt-dlp + ffmpeg + imagemagick + ]; +} From f9fe74cc8afcd50561eb4d796942246ed31c85bb Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 25 Sep 2025 19:36:34 -0500 Subject: [PATCH 40/62] feat: installed bazarr, radarr, and sonarr --- .../nixos/defiant/configuration.nix | 15 ++++++++++ .../nixos-modules/server/bazarr/default.nix | 6 ++++ .../server/bazarr/impermanence.nix | 26 +++++++++++++++++ modules/nixos-modules/server/bazarr/proxy.nix | 28 +++++++++++++++++++ modules/nixos-modules/server/default.nix | 9 ++++-- .../nixos-modules/server/radarr/default.nix | 6 ++++ .../server/radarr/impermanence.nix | 26 +++++++++++++++++ modules/nixos-modules/server/radarr/proxy.nix | 28 +++++++++++++++++++ .../nixos-modules/server/sonarr/default.nix | 6 ++++ .../server/sonarr/impermanence.nix | 26 +++++++++++++++++ modules/nixos-modules/server/sonarr/proxy.nix | 28 +++++++++++++++++++ 11 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 modules/nixos-modules/server/bazarr/default.nix create mode 100644 modules/nixos-modules/server/bazarr/impermanence.nix create mode 100644 modules/nixos-modules/server/bazarr/proxy.nix create mode 100644 modules/nixos-modules/server/radarr/default.nix create mode 100644 modules/nixos-modules/server/radarr/impermanence.nix create mode 100644 modules/nixos-modules/server/radarr/proxy.nix create mode 100644 modules/nixos-modules/server/sonarr/default.nix create mode 100644 modules/nixos-modules/server/sonarr/impermanence.nix create mode 100644 modules/nixos-modules/server/sonarr/proxy.nix diff --git a/configurations/nixos/defiant/configuration.nix b/configurations/nixos/defiant/configuration.nix index d10bea0..830af16 100644 --- a/configurations/nixos/defiant/configuration.nix +++ b/configurations/nixos/defiant/configuration.nix @@ -287,6 +287,21 @@ subdomain = "budget"; }; + sonarr = { + enable = true; + openFirewall = true; + }; + + radarr = { + enable = true; + openFirewall = true; + }; + + bazarr = { + enable = true; + openFirewall = true; + }; + home-assistant = { enable = true; subdomain = "home"; diff --git a/modules/nixos-modules/server/bazarr/default.nix b/modules/nixos-modules/server/bazarr/default.nix new file mode 100644 index 0000000..f39d940 --- /dev/null +++ b/modules/nixos-modules/server/bazarr/default.nix @@ -0,0 +1,6 @@ +{...}: { + imports = [ + ./proxy.nix + ./impermanence.nix + ]; +} diff --git a/modules/nixos-modules/server/bazarr/impermanence.nix b/modules/nixos-modules/server/bazarr/impermanence.nix new file mode 100644 index 0000000..22fb0e6 --- /dev/null +++ b/modules/nixos-modules/server/bazarr/impermanence.nix @@ -0,0 +1,26 @@ +{ + lib, + config, + ... +}: let + bazarr_data_directory = "/var/lib/bazarr"; +in { + config = lib.mkIf (config.services.bazarr.enable && config.host.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/proxy.nix b/modules/nixos-modules/server/bazarr/proxy.nix new file mode 100644 index 0000000..fe310d8 --- /dev/null +++ b/modules/nixos-modules/server/bazarr/proxy.nix @@ -0,0 +1,28 @@ +{ + lib, + config, + ... +}: { + options.services.bazarr = { + subdomain = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Subdomain for reverse proxy. If null, service will be local only."; + }; + extraSubdomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = "Extra subdomains for reverse proxy."; + }; + }; + + config = lib.mkIf (config.services.bazarr.enable && config.services.bazarr.subdomain != null) { + host.reverse_proxy.subdomains.bazarr = { + subdomain = config.services.bazarr.subdomain; + extraSubdomains = config.services.bazarr.extraSubdomains; + target = "http://127.0.0.1:6767"; + websockets.enable = true; + forwardHeaders.enable = true; + }; + }; +} diff --git a/modules/nixos-modules/server/default.nix b/modules/nixos-modules/server/default.nix index 15f833b..e550123 100644 --- a/modules/nixos-modules/server/default.nix +++ b/modules/nixos-modules/server/default.nix @@ -7,14 +7,17 @@ ./podman.nix ./actual - ./immich - ./panoramax + ./bazarr ./forgejo ./home-assistant + ./immich ./jellyfin + ./panoramax ./paperless - ./searx ./qbittorent.nix + ./radarr + ./searx + ./sonarr ./wyoming.nix ]; } diff --git a/modules/nixos-modules/server/radarr/default.nix b/modules/nixos-modules/server/radarr/default.nix new file mode 100644 index 0000000..f39d940 --- /dev/null +++ b/modules/nixos-modules/server/radarr/default.nix @@ -0,0 +1,6 @@ +{...}: { + imports = [ + ./proxy.nix + ./impermanence.nix + ]; +} diff --git a/modules/nixos-modules/server/radarr/impermanence.nix b/modules/nixos-modules/server/radarr/impermanence.nix new file mode 100644 index 0000000..4a3242c --- /dev/null +++ b/modules/nixos-modules/server/radarr/impermanence.nix @@ -0,0 +1,26 @@ +{ + lib, + config, + ... +}: let + radarr_data_directory = "/var/lib/radarr/.config/Radarr"; +in { + config = lib.mkIf (config.services.radarr.enable && config.host.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/proxy.nix b/modules/nixos-modules/server/radarr/proxy.nix new file mode 100644 index 0000000..ec5f575 --- /dev/null +++ b/modules/nixos-modules/server/radarr/proxy.nix @@ -0,0 +1,28 @@ +{ + lib, + config, + ... +}: { + options.services.radarr = { + subdomain = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Subdomain for reverse proxy. If null, service will be local only."; + }; + extraSubdomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = "Extra subdomains for reverse proxy."; + }; + }; + + config = lib.mkIf (config.services.radarr.enable && config.services.radarr.subdomain != null) { + host.reverse_proxy.subdomains.radarr = { + subdomain = config.services.radarr.subdomain; + extraSubdomains = config.services.radarr.extraSubdomains; + target = "http://127.0.0.1:7878"; + websockets.enable = true; + forwardHeaders.enable = true; + }; + }; +} diff --git a/modules/nixos-modules/server/sonarr/default.nix b/modules/nixos-modules/server/sonarr/default.nix new file mode 100644 index 0000000..f39d940 --- /dev/null +++ b/modules/nixos-modules/server/sonarr/default.nix @@ -0,0 +1,6 @@ +{...}: { + imports = [ + ./proxy.nix + ./impermanence.nix + ]; +} diff --git a/modules/nixos-modules/server/sonarr/impermanence.nix b/modules/nixos-modules/server/sonarr/impermanence.nix new file mode 100644 index 0000000..abc843c --- /dev/null +++ b/modules/nixos-modules/server/sonarr/impermanence.nix @@ -0,0 +1,26 @@ +{ + lib, + config, + ... +}: let + sonarr_data_directory = "/var/lib/sonarr/.config/NzbDrone"; +in { + config = lib.mkIf (config.services.sonarr.enable && config.host.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/proxy.nix b/modules/nixos-modules/server/sonarr/proxy.nix new file mode 100644 index 0000000..22b90a6 --- /dev/null +++ b/modules/nixos-modules/server/sonarr/proxy.nix @@ -0,0 +1,28 @@ +{ + lib, + config, + ... +}: { + options.services.sonarr = { + subdomain = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Subdomain for reverse proxy. If null, service will be local only."; + }; + extraSubdomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = "Extra subdomains for reverse proxy."; + }; + }; + + config = lib.mkIf (config.services.sonarr.enable && config.services.sonarr.subdomain != null) { + host.reverse_proxy.subdomains.sonarr = { + subdomain = config.services.sonarr.subdomain; + extraSubdomains = config.services.sonarr.extraSubdomains; + target = "http://127.0.0.1:8989"; + websockets.enable = true; + forwardHeaders.enable = true; + }; + }; +} From a8139f4265963e091062d354ab09e413a0103cda Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 25 Sep 2025 20:04:51 -0500 Subject: [PATCH 41/62] feat: installed filebot --- configurations/nixos/defiant/packages.nix | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/configurations/nixos/defiant/packages.nix b/configurations/nixos/defiant/packages.nix index 45780b0..f9cce58 100644 --- a/configurations/nixos/defiant/packages.nix +++ b/configurations/nixos/defiant/packages.nix @@ -1,9 +1,19 @@ -{pkgs, ...}: { +{ + pkgs, + lib, + ... +}: { + nixpkgs.config.allowUnfreePredicate = pkg: + builtins.elem (lib.getName pkg) [ + "filebot" + ]; + environment.systemPackages = with pkgs; [ ffsubsync sox yt-dlp ffmpeg imagemagick + filebot ]; } From d2be5c7e2459355fbed24c74918b5793dcbffae0 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 25 Sep 2025 20:10:49 -0500 Subject: [PATCH 42/62] feat: added radarr, sonarr, and bazarr to the jellyfin_media group --- modules/nixos-modules/users.nix | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/modules/nixos-modules/users.nix b/modules/nixos-modules/users.nix index 7fd43da..ea8d877 100644 --- a/modules/nixos-modules/users.nix +++ b/modules/nixos-modules/users.nix @@ -25,6 +25,9 @@ qbittorrent = 2011; paperless = 2012; actual = 2013; + radarr = 275; + sonarr = 274; + bazarr = 985; }; gids = { @@ -42,6 +45,9 @@ qbittorrent = 2011; paperless = 2012; actual = 2013; + radarr = 275; + sonarr = 274; + bazarr = 981; }; users = config.users.users; @@ -177,6 +183,24 @@ in { isSystemUser = true; group = config.users.users.actual.name; }; + + radarr = { + uid = lib.mkForce uids.radarr; + isSystemUser = true; + group = config.users.users.radarr.name; + }; + + sonarr = { + uid = lib.mkForce uids.sonarr; + isSystemUser = true; + group = config.users.users.sonarr.name; + }; + + bazarr = { + uid = lib.mkForce uids.bazarr; + isSystemUser = true; + group = config.users.users.bazarr.name; + }; }; groups = { @@ -206,6 +230,9 @@ in { gid = lib.mkForce gids.jellyfin_media; members = [ users.jellyfin.name + users.radarr.name + users.sonarr.name + users.bazarr.name leyla eve ]; @@ -287,6 +314,27 @@ in { users.actual.name ]; }; + + radarr = { + gid = lib.mkForce gids.radarr; + members = [ + users.radarr.name + ]; + }; + + sonarr = { + gid = lib.mkForce gids.sonarr; + members = [ + users.sonarr.name + ]; + }; + + bazarr = { + gid = lib.mkForce gids.bazarr; + members = [ + users.bazarr.name + ]; + }; }; }; } From a8dfcb02c86052113b5d46f242601c1b5f075a4e Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 25 Sep 2025 22:20:15 -0500 Subject: [PATCH 43/62] feat: created filebot cleanup service to run in background --- .../nixos/defiant/configuration.nix | 5 ++ configurations/nixos/defiant/default.nix | 1 + configurations/nixos/defiant/filebot.nix | 82 +++++++++++++++++++ configurations/nixos/defiant/packages.nix | 12 +-- 4 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 configurations/nixos/defiant/filebot.nix diff --git a/configurations/nixos/defiant/configuration.nix b/configurations/nixos/defiant/configuration.nix index 830af16..e5f63f7 100644 --- a/configurations/nixos/defiant/configuration.nix +++ b/configurations/nixos/defiant/configuration.nix @@ -332,6 +332,11 @@ openFirewall = true; webuiPort = 8084; }; + + filebot-cleanup = { + enable = true; + licenseFile = "/srv/jellyfin/filebot_license.psm"; + }; }; # disable computer sleeping diff --git a/configurations/nixos/defiant/default.nix b/configurations/nixos/defiant/default.nix index 3013946..05975a1 100644 --- a/configurations/nixos/defiant/default.nix +++ b/configurations/nixos/defiant/default.nix @@ -4,5 +4,6 @@ ./hardware-configuration.nix ./configuration.nix ./packages.nix + ./filebot.nix ]; } diff --git a/configurations/nixos/defiant/filebot.nix b/configurations/nixos/defiant/filebot.nix new file mode 100644 index 0000000..77d81bd --- /dev/null +++ b/configurations/nixos/defiant/filebot.nix @@ -0,0 +1,82 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.services.filebot-cleanup; +in { + options.services.filebot-cleanup = { + enable = mkEnableOption "Filebot cleanup service"; + + licenseFile = mkOption { + type = types.nullOr types.path; + default = null; + description = "Path to the Filebot license file"; + }; + + cleanupDirectory = mkOption { + type = types.str; + default = "/srv/jellyfin/filebot_cleanup"; + description = "Directory where cleaned up media files are stored"; + }; + }; + + config = mkIf cfg.enable { + users.groups.filebot_cleanup = {}; + users.users.filebot_cleanup = { + isSystemUser = true; + group = "filebot_cleanup"; + extraGroups = ["jellyfin_media"]; + home = cfg.cleanupDirectory; + createHome = true; + }; + + nixpkgs.config.allowUnfreePredicate = pkg: + builtins.elem (lib.getName pkg) [ + "filebot" + ]; + + environment.systemPackages = with pkgs; [ + filebot + ]; + + systemd.services.filebot-cleanup = { + description = "Filebot media cleanup service"; + serviceConfig = { + Type = "simple"; + User = "filebot_cleanup"; + Group = "filebot_cleanup"; + ExecStart = pkgs.writeShellScript "filebot-cleanup" '' + ${optionalString (cfg.licenseFile != null) '' + ${pkgs.filebot}/bin/filebot --license "${cfg.licenseFile}" + ''} + ${pkgs.filebot}/bin/filebot -rename -r "/srv/jellyfin/media/Movies/" --output "${cfg.cleanupDirectory}/" --format "{jellyfin}" -non-strict --action move + ${pkgs.filebot}/bin/filebot -rename -r "/srv/jellyfin/media/Shows/" --output "${cfg.cleanupDirectory}/" --format "{jellyfin}" -non-strict --action move + ''; + StandardOutput = "journal"; + StandardError = "journal"; + }; + wantedBy = ["multi-user.target"]; + }; + + environment.persistence = lib.mkIf config.host.impermanence.enable { + "/persist/system/filebot_cleanup" = { + enable = true; + hideMounts = true; + files = [ + cfg.licenseFile + ]; + directories = [ + { + directory = cfg.cleanupDirectory; + user = "filebot_cleanup"; + group = "filebot_cleanup"; + mode = "1770"; + } + ]; + }; + }; + }; +} diff --git a/configurations/nixos/defiant/packages.nix b/configurations/nixos/defiant/packages.nix index f9cce58..45780b0 100644 --- a/configurations/nixos/defiant/packages.nix +++ b/configurations/nixos/defiant/packages.nix @@ -1,19 +1,9 @@ -{ - pkgs, - lib, - ... -}: { - nixpkgs.config.allowUnfreePredicate = pkg: - builtins.elem (lib.getName pkg) [ - "filebot" - ]; - +{pkgs, ...}: { environment.systemPackages = with pkgs; [ ffsubsync sox yt-dlp ffmpeg imagemagick - filebot ]; } From 24def1e3d3428e7fe30a06eecc1786110065a14a Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 25 Sep 2025 22:31:29 -0500 Subject: [PATCH 44/62] chore: added tasks to README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f1521ce..cad757a 100644 --- a/README.md +++ b/README.md @@ -69,5 +69,9 @@ nix multi user, multi system, configuration with `sops` secret management, `home - ISO target that contains authorized keys for nixos-anywhere https://github.com/diegofariasm/yggdrasil/blob/4acc43ebc7bcbf2e41376d14268e382007e94d78/hosts/bootstrap/default.nix - panoramax instance - mastodon instance +- update proxy.nix files to contain the subdomain configs +- rework the reverse_proxy.nix file so that it is a normally named service. Then also change it so that we can hook into it with both a base domain and a subdomain to make migrating to vpn accessible services easier - move searx, home-assistant, actual, jellyfin, paperless, and immich to only be accessible via vpn +- make radarr, sonarr, and bazarr accessible over vpn +- create some sort of service that allows uploading files to jellyfin - graphana accessible though tailscale \ No newline at end of file From 1d940fd8d8275a4050876d981e769e5c1572fe20 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 25 Sep 2025 22:33:14 -0500 Subject: [PATCH 45/62] feat: disabled sonarr, radarr, and bazarr --- configurations/nixos/defiant/configuration.nix | 15 --------------- modules/nixos-modules/users.nix | 12 ++++++------ 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/configurations/nixos/defiant/configuration.nix b/configurations/nixos/defiant/configuration.nix index e5f63f7..2cde0b1 100644 --- a/configurations/nixos/defiant/configuration.nix +++ b/configurations/nixos/defiant/configuration.nix @@ -287,21 +287,6 @@ subdomain = "budget"; }; - sonarr = { - enable = true; - openFirewall = true; - }; - - radarr = { - enable = true; - openFirewall = true; - }; - - bazarr = { - enable = true; - openFirewall = true; - }; - home-assistant = { enable = true; subdomain = "home"; diff --git a/modules/nixos-modules/users.nix b/modules/nixos-modules/users.nix index ea8d877..db7d4ab 100644 --- a/modules/nixos-modules/users.nix +++ b/modules/nixos-modules/users.nix @@ -25,9 +25,9 @@ qbittorrent = 2011; paperless = 2012; actual = 2013; - radarr = 275; - sonarr = 274; - bazarr = 985; + radarr = 2014; + sonarr = 2015; + bazarr = 2016; }; gids = { @@ -45,9 +45,9 @@ qbittorrent = 2011; paperless = 2012; actual = 2013; - radarr = 275; - sonarr = 274; - bazarr = 981; + radarr = 2014; + sonarr = 2015; + bazarr = 2016; }; users = config.users.users; From c8d994814fb0d040be8e16cbc3c40ffbfb5a87e9 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 25 Sep 2025 22:35:28 -0500 Subject: [PATCH 46/62] chore: added note to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cad757a..c58d847 100644 --- a/README.md +++ b/README.md @@ -74,4 +74,5 @@ nix multi user, multi system, configuration with `sops` secret management, `home - move searx, home-assistant, actual, jellyfin, paperless, and immich to only be accessible via vpn - make radarr, sonarr, and bazarr accessible over vpn - create some sort of service that allows uploading files to jellyfin + - auto sort files into where they should go with some combination of filebot cli and picard cli - graphana accessible though tailscale \ No newline at end of file From 178b414a0ace601d899f27ea9c33899f70a87c6e Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 25 Sep 2025 22:41:10 -0500 Subject: [PATCH 47/62] chore: removed already completed task from README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index c58d847..e94eb58 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,6 @@ nix multi user, multi system, configuration with `sops` secret management, `home - ISO target that contains authorized keys for nixos-anywhere https://github.com/diegofariasm/yggdrasil/blob/4acc43ebc7bcbf2e41376d14268e382007e94d78/hosts/bootstrap/default.nix - panoramax instance - mastodon instance -- update proxy.nix files to contain the subdomain configs - rework the reverse_proxy.nix file so that it is a normally named service. Then also change it so that we can hook into it with both a base domain and a subdomain to make migrating to vpn accessible services easier - move searx, home-assistant, actual, jellyfin, paperless, and immich to only be accessible via vpn - make radarr, sonarr, and bazarr accessible over vpn From 0cb4c25467ea159cce3d29df5f617491d9aced4a Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Fri, 26 Sep 2025 20:21:58 -0500 Subject: [PATCH 48/62] fat: disabled filebot-cleanup service --- configurations/nixos/defiant/configuration.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configurations/nixos/defiant/configuration.nix b/configurations/nixos/defiant/configuration.nix index 2cde0b1..401173e 100644 --- a/configurations/nixos/defiant/configuration.nix +++ b/configurations/nixos/defiant/configuration.nix @@ -319,7 +319,7 @@ }; filebot-cleanup = { - enable = true; + enable = false; licenseFile = "/srv/jellyfin/filebot_license.psm"; }; }; From 6dfe3ac3265c6de1202ac7cd6cad2bea8697b129 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Wed, 1 Oct 2025 15:14:36 -0500 Subject: [PATCH 49/62] build: updated flake lock --- flake.lock | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/flake.lock b/flake.lock index 6123425..b5607f6 100644 --- a/flake.lock +++ b/flake.lock @@ -46,11 +46,11 @@ }, "locked": { "dir": "pkgs/firefox-addons", - "lastModified": 1758600213, - "narHash": "sha256-YP7+UxybMCzHPd5k93pulILnFvSisjgUAGUB/cxWbqU=", + "lastModified": 1759291409, + "narHash": "sha256-eAzmD4ijeWCFy4YqArNmVu8901nLQLHr6dCv94yRrFk=", "owner": "rycee", "repo": "nur-expressions", - "rev": "8a0333bf11a0fab386c80fa018617bb050156ec5", + "rev": "f9c2e6b2eebdbe0e87236a63ea323c86da79b6c5", "type": "gitlab" }, "original": { @@ -118,11 +118,11 @@ ] }, "locked": { - "lastModified": 1758719930, - "narHash": "sha256-DgHe1026Ob49CPegPMiWj1HNtlMTGQzfSZQQVlHC950=", + "lastModified": 1759337100, + "narHash": "sha256-CcT3QvZ74NGfM+lSOILcCEeU+SnqXRvl1XCRHenZ0Us=", "owner": "nix-community", "repo": "home-manager", - "rev": "142acd7a7d9eb7f0bb647f053b4ddfd01fdfbf1d", + "rev": "004753ae6b04c4b18aa07192c1106800aaacf6c3", "type": "github" }, "original": { @@ -155,11 +155,11 @@ ] }, "locked": { - "lastModified": 1755372538, - "narHash": "sha256-iWhsf1Myk6RyQ7IuNf4bWI3Sqq9pgmhKvEisCXtkxyw=", + "lastModified": 1759342933, + "narHash": "sha256-mdlUFcrOfvT0Pm+Hko/6aR3xf1ao5JA2iem4KsEVjP4=", "owner": "utensils", "repo": "mcp-nixos", - "rev": "46b4d4d3d6421bfbadc415532ef74433871e1cda", + "rev": "50b02bcba32b941d2ec48fedef68641702ca5b0f", "type": "github" }, "original": { @@ -175,11 +175,11 @@ ] }, "locked": { - "lastModified": 1758447883, - "narHash": "sha256-yGA6MV0E4JSEXqLTb4ZZkmdJZcoQ8HUzihRRX12Bvpg=", + "lastModified": 1758805352, + "narHash": "sha256-BHdc43Lkayd+72W/NXRKHzX5AZ+28F3xaUs3a88/Uew=", "owner": "LnL7", "repo": "nix-darwin", - "rev": "25381509d5c91bbf3c30e23abc6d8476d2143cd1", + "rev": "c48e963a5558eb1c3827d59d21c5193622a1477c", "type": "github" }, "original": { @@ -217,11 +217,11 @@ ] }, "locked": { - "lastModified": 1758678836, - "narHash": "sha256-ewDKEXcKYF7L+EGVa+8E1nxK1pdwVrCHcj5UhuGA8V0=", + "lastModified": 1759284197, + "narHash": "sha256-NbaOzcxsUxNm+Dday5DlV6P9CzRAonY2DNcp056oWWc=", "owner": "nix-community", "repo": "nix-vscode-extensions", - "rev": "5007786714b3573b37cf3b8c4a33e2ddce86960d", + "rev": "a87f796f1ed4b0a8babe9370791a66aac4864887", "type": "github" }, "original": { @@ -232,11 +232,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1758663926, - "narHash": "sha256-6CFdj7Xs616t1W4jLDH7IohAAvl5Dyib3qEv/Uqw1rk=", + "lastModified": 1759261527, + "narHash": "sha256-wPd5oGvBBpUEzMF0kWnXge0WITNsITx/aGI9qLHgJ4g=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "170ff93c860b2a9868ed1e1102d4e52cb3d934e1", + "rev": "e087756cf4abbe1a34f3544c480fc1034d68742f", "type": "github" }, "original": { @@ -264,11 +264,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1758427187, - "narHash": "sha256-pHpxZ/IyCwoTQPtFIAG2QaxuSm8jWzrzBGjwQZIttJc=", + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", "owner": "nixos", "repo": "nixpkgs", - "rev": "554be6495561ff07b6c724047bdd7e0716aa7b46", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", "type": "github" }, "original": { @@ -318,11 +318,11 @@ ] }, "locked": { - "lastModified": 1758425756, - "narHash": "sha256-L3N8zV6wsViXiD8i3WFyrvjDdz76g3tXKEdZ4FkgQ+Y=", + "lastModified": 1759188042, + "narHash": "sha256-f9QC2KKiNReZDG2yyKAtDZh0rSK2Xp1wkPzKbHeQVRU=", "owner": "Mic92", "repo": "sops-nix", - "rev": "e0fdaea3c31646e252a60b42d0ed8eafdb289762", + "rev": "9fcfabe085281dd793589bdc770a2e577a3caa5d", "type": "github" }, "original": { From c10c61003474a2760d92710b7bdb721cb9337d49 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 2 Oct 2025 12:49:29 -0500 Subject: [PATCH 50/62] feat: started to create polycule package --- .../home-manager/leyla/packages/default.nix | 2 + modules/common-modules/pkgs/default.nix | 3 + .../common-modules/pkgs/polycule/default.nix | 149 + .../pkgs/polycule/polycule-pubspec.lock.json | 2459 +++++++++++++++++ .../home-manager-modules/programs/default.nix | 1 + .../programs/polycule.nix | 32 + 6 files changed, 2646 insertions(+) create mode 100644 modules/common-modules/pkgs/polycule/default.nix create mode 100644 modules/common-modules/pkgs/polycule/polycule-pubspec.lock.json create mode 100644 modules/home-manager-modules/programs/polycule.nix diff --git a/configurations/home-manager/leyla/packages/default.nix b/configurations/home-manager/leyla/packages/default.nix index 6377ed2..a6da7f5 100644 --- a/configurations/home-manager/leyla/packages/default.nix +++ b/configurations/home-manager/leyla/packages/default.nix @@ -70,6 +70,8 @@ in { noisetorch.enable = true; tor-browser.enable = true; gdx-liftoff.enable = true; + # polycule package is currently broken + polycule.enable = false; }) ]; } diff --git a/modules/common-modules/pkgs/default.nix b/modules/common-modules/pkgs/default.nix index c97f97c..a2f61b1 100644 --- a/modules/common-modules/pkgs/default.nix +++ b/modules/common-modules/pkgs/default.nix @@ -38,5 +38,8 @@ # Override h3 C library to version 4.3.0 h3 = pkgs.callPackage ./h3-c-lib.nix {}; }) + (final: prev: { + polycule = pkgs.callPackage ./polycule {}; + }) ]; } diff --git a/modules/common-modules/pkgs/polycule/default.nix b/modules/common-modules/pkgs/polycule/default.nix new file mode 100644 index 0000000..d092897 --- /dev/null +++ b/modules/common-modules/pkgs/polycule/default.nix @@ -0,0 +1,149 @@ +{ + lib, + flutter329, + fetchFromGitLab, + pkg-config, + wrapGAppsHook, + gtk3, + glib, + glib-networking, + webkitgtk_4_1, + libsecret, + libnotify, + dbus, + sqlcipher, + openssl, + mpv, + alsa-lib, + libass, + ffmpeg-full, + libplacebo, + libunwind, + shaderc, + vulkan-headers, + vulkan-loader, + lcms2, + libdovi, + libdvdnav, + libdvdread, + mujs, + libbluray, + lua, + rubberband, + libuchardet, + zimg, + openal, + pipewire, + libpulseaudio, + libcaca, + libdrm, + libdisplay-info, + libgbm, + xorg, + nv-codec-headers-11, + libva, + libvdpau, +}: +flutter329.buildFlutterApplication rec { + pname = "polycule"; + version = "0.3.0"; + + src = fetchFromGitLab { + owner = "polycule_client"; + repo = "polycule"; + rev = "v${version}"; + hash = "sha256-kY1vJiDXh0rSCJNOAkO8JGiMR8kXwDHuc3T+S4MkOWY="; + }; + + pubspecLock = lib.importJSON ./polycule-pubspec.lock.json; + + gitHashes = { + matrix = "sha256-e1HGC2yZyqqYB5YAGKmUkkdDbuSzhiUenJMKJgQYIi8="; + media_kit = "sha256-1sVX+aHFLFJBtrNZrR6tWkb80vFELW2N9EejyQKlBPg="; + media_kit_libs_android_video = "sha256-N6QoktM8u9NYF8MAXLsxM9RlV8nICM4NbnmABHTRkZg="; + }; + + nativeBuildInputs = [ + pkg-config + wrapGAppsHook + ]; + + buildInputs = [ + gtk3 + glib + glib-networking + webkitgtk_4_1 + libsecret + libnotify + dbus + sqlcipher + openssl + mpv + alsa-lib + libass + ffmpeg-full + libplacebo + libunwind + shaderc + vulkan-headers + vulkan-loader + lcms2 + libdovi + libdvdnav + libdvdread + mujs + libbluray + lua + rubberband + libuchardet + zimg + openal + pipewire + libpulseaudio + libcaca + libdrm + libdisplay-info + libgbm + xorg.libXScrnSaver + xorg.libXpresent + nv-codec-headers-11 + libva + libvdpau + ]; + + flutterBuildFlags = [ + "--release" + "--target" + "lib/main.dart" + "--dart-define=POLYCULE_VERSION=v${version}" + "--dart-define=POLYCULE_IS_STABLE=true" + "--no-tree-shake-icons" + ]; + + postInstall = '' + # Install desktop files and icons from the source + install -Dm644 linux/business.braid.polycule.desktop $out/share/applications/polycule.desktop + install -Dm644 assets/logo/logo-circle.png $out/share/pixmaps/polycule.png + + # Update desktop file to use correct executable name + substituteInPlace $out/share/applications/polycule.desktop \ + --replace 'Exec=business.braid.polycule' 'Exec=polycule' + + # Create a symlink with the expected name + ln -sf $out/bin/polycule $out/bin/business.braid.polycule + ''; + + meta = with lib; { + description = "A geeky and efficient [matrix] client for power users"; + longDescription = '' + Polycule is a modern Matrix client built with Flutter, designed for power users + who want a fast, efficient, and feature-rich Matrix experience. + ''; + homepage = "https://polycule.im/"; + license = licenses.eupl12; + maintainers = []; + platforms = ["x86_64-linux" "aarch64-linux"]; + sourceProvenance = with sourceTypes; [fromSource]; + mainProgram = "polycule"; + }; +} diff --git a/modules/common-modules/pkgs/polycule/polycule-pubspec.lock.json b/modules/common-modules/pkgs/polycule/polycule-pubspec.lock.json new file mode 100644 index 0000000..e119fa2 --- /dev/null +++ b/modules/common-modules/pkgs/polycule/polycule-pubspec.lock.json @@ -0,0 +1,2459 @@ +{ + "packages": { + "_fe_analyzer_shared": { + "dependency": "transitive", + "description": { + "name": "_fe_analyzer_shared", + "sha256": "da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "85.0.0" + }, + "analyzer": { + "dependency": "transitive", + "description": { + "name": "analyzer", + "sha256": "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "7.7.1" + }, + "animations": { + "dependency": "direct main", + "description": { + "name": "animations", + "sha256": "d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.11" + }, + "app_links": { + "dependency": "direct main", + "description": { + "name": "app_links", + "sha256": "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "6.4.0" + }, + "app_links_linux": { + "dependency": "transitive", + "description": { + "name": "app_links_linux", + "sha256": "f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.0.3" + }, + "app_links_platform_interface": { + "dependency": "transitive", + "description": { + "name": "app_links_platform_interface", + "sha256": "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.2" + }, + "app_links_web": { + "dependency": "transitive", + "description": { + "name": "app_links_web", + "sha256": "af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.0.4" + }, + "archive": { + "dependency": "transitive", + "description": { + "name": "archive", + "sha256": "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "4.0.7" + }, + "args": { + "dependency": "transitive", + "description": { + "name": "args", + "sha256": "d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.7.0" + }, + "async": { + "dependency": "direct main", + "description": { + "name": "async", + "sha256": "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.13.0" + }, + "audio_session": { + "dependency": "transitive", + "description": { + "name": "audio_session", + "sha256": "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.2.2" + }, + "barcode": { + "dependency": "transitive", + "description": { + "name": "barcode", + "sha256": "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.2.9" + }, + "barcode_widget": { + "dependency": "direct main", + "description": { + "name": "barcode_widget", + "sha256": "6f2c5b08659b1a5f4d88d183e6007133ea2f96e50e7b8bb628f03266c3931427", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.4" + }, + "base58check": { + "dependency": "transitive", + "description": { + "name": "base58check", + "sha256": "6c300dfc33e598d2fe26319e13f6243fea81eaf8204cb4c6b69ef20a625319a5", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.0" + }, + "blurhash_dart": { + "dependency": "direct main", + "description": { + "name": "blurhash_dart", + "sha256": "43955b6c2e30a7d440028d1af0fa185852f3534b795cc6eb81fbf397b464409f", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.2.1" + }, + "boolean_selector": { + "dependency": "transitive", + "description": { + "name": "boolean_selector", + "sha256": "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.2" + }, + "build_cli_annotations": { + "dependency": "transitive", + "description": { + "name": "build_cli_annotations", + "sha256": "b59d2769769efd6c9ff6d4c4cede0be115a566afc591705c2040b707534b1172", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.0" + }, + "camera": { + "dependency": "transitive", + "description": { + "name": "camera", + "sha256": "d6ec2cbdbe2fa8f5e0d07d8c06368fe4effa985a4a5ddade9cc58a8cd849557d", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.11.2" + }, + "camera_android_camerax": { + "dependency": "transitive", + "description": { + "name": "camera_android_camerax", + "sha256": "58b8fe843a3c83fd1273c00cb35f5a8ae507f6cc9b2029bcf7e2abba499e28d8", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.6.19+1" + }, + "camera_avfoundation": { + "dependency": "transitive", + "description": { + "name": "camera_avfoundation", + "sha256": "e4aca5bccaf897b70cac87e5fdd789393310985202442837922fd40325e2733b", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.9.21+1" + }, + "camera_platform_interface": { + "dependency": "transitive", + "description": { + "name": "camera_platform_interface", + "sha256": "2f757024a48696ff4814a789b0bd90f5660c0fb25f393ab4564fb483327930e2", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.10.0" + }, + "camera_web": { + "dependency": "transitive", + "description": { + "name": "camera_web", + "sha256": "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.3.5" + }, + "canonical_json": { + "dependency": "transitive", + "description": { + "name": "canonical_json", + "sha256": "d6be1dd66b420c6ac9f42e3693e09edf4ff6edfee26cb4c28c1c019fdb8c0c15", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.2" + }, + "characters": { + "dependency": "transitive", + "description": { + "name": "characters", + "sha256": "f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.4.0" + }, + "checked_yaml": { + "dependency": "transitive", + "description": { + "name": "checked_yaml", + "sha256": "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.4" + }, + "cli_config": { + "dependency": "transitive", + "description": { + "name": "cli_config", + "sha256": "ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.2.0" + }, + "cli_util": { + "dependency": "transitive", + "description": { + "name": "cli_util", + "sha256": "ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.4.2" + }, + "clock": { + "dependency": "transitive", + "description": { + "name": "clock", + "sha256": "fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.2" + }, + "collection": { + "dependency": "direct main", + "description": { + "name": "collection", + "sha256": "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.19.1" + }, + "convert": { + "dependency": "transitive", + "description": { + "name": "convert", + "sha256": "b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.1.2" + }, + "coverage": { + "dependency": "transitive", + "description": { + "name": "coverage", + "sha256": "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.15.0" + }, + "cross_file": { + "dependency": "direct main", + "description": { + "name": "cross_file", + "sha256": "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.3.4+2" + }, + "crypto": { + "dependency": "transitive", + "description": { + "name": "crypto", + "sha256": "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.0.6" + }, + "csslib": { + "dependency": "direct main", + "description": { + "name": "csslib", + "sha256": "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.0.2" + }, + "cupertino_http": { + "dependency": "direct main", + "description": { + "name": "cupertino_http", + "sha256": "72187f715837290a63479a5b0ae709f4fedad0ed6bd0441c275eceaa02d5abae", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.3.0" + }, + "cupertino_icons": { + "dependency": "direct main", + "description": { + "name": "cupertino_icons", + "sha256": "ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.0.8" + }, + "dart_animated_emoji": { + "dependency": "direct main", + "description": { + "name": "dart_animated_emoji", + "sha256": "0e0865f1b56e2f2979e8caa09a7d693e30133050c5c677de301e6ca4d8da945e", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.1.2" + }, + "dbus": { + "dependency": "direct main", + "description": { + "name": "dbus", + "sha256": "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.7.11" + }, + "diacritic": { + "dependency": "direct main", + "description": { + "name": "diacritic", + "sha256": "12981945ec38931748836cd76f2b38773118d0baef3c68404bdfde9566147876", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.1.6" + }, + "diffutil_dart": { + "dependency": "direct main", + "description": { + "name": "diffutil_dart", + "sha256": "5e74883aedf87f3b703cb85e815bdc1ed9208b33501556e4a8a5572af9845c81", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "4.0.1" + }, + "dynamic_color": { + "dependency": "direct main", + "description": { + "name": "dynamic_color", + "sha256": "43a5a6679649a7731ab860334a5812f2067c2d9ce6452cf069c5e0c25336c17c", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.8.1" + }, + "emoji_extension": { + "dependency": "direct main", + "description": { + "name": "emoji_extension", + "sha256": "7678a3e3fca4f2dfbce02cf8d439a81e130ce303fdc1ad90f484f57fd5ce4ba1", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.2.0" + }, + "enhanced_enum": { + "dependency": "transitive", + "description": { + "name": "enhanced_enum", + "sha256": "074c5a8b9664799ca91e1e8b68003b8694cb19998671cbafd9c7779c13fcdecf", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.2.4" + }, + "equatable": { + "dependency": "transitive", + "description": { + "name": "equatable", + "sha256": "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.7" + }, + "fake_async": { + "dependency": "transitive", + "description": { + "name": "fake_async", + "sha256": "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.3.3" + }, + "fetch_api": { + "dependency": "transitive", + "description": { + "name": "fetch_api", + "sha256": "24cbd5616f3d4008c335c197bb90bfa0eb43b9e55c6de5c60d1f805092636034", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.3.1" + }, + "fetch_client": { + "dependency": "direct main", + "description": { + "name": "fetch_client", + "sha256": "375253f4efe64303c793fb17fe90771c591320b2ae11fb29cb5b406cc8533c00", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.4" + }, + "ffi": { + "dependency": "transitive", + "description": { + "name": "ffi", + "sha256": "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.4" + }, + "file": { + "dependency": "transitive", + "description": { + "name": "file", + "sha256": "a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "7.0.1" + }, + "file_selector": { + "dependency": "direct main", + "description": { + "name": "file_selector", + "sha256": "5019692b593455127794d5718304ff1ae15447dea286cdda9f0db2a796a1b828", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.0.3" + }, + "file_selector_android": { + "dependency": "transitive", + "description": { + "name": "file_selector_android", + "sha256": "3015702ab73987000e7ff2df5ddc99666d2bcd65cdb243f59da35729d3be6cff", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.5.1+15" + }, + "file_selector_ios": { + "dependency": "transitive", + "description": { + "name": "file_selector_ios", + "sha256": "94b98ad950b8d40d96fee8fa88640c2e4bd8afcdd4817993bd04e20310f45420", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.5.3+1" + }, + "file_selector_linux": { + "dependency": "transitive", + "description": { + "name": "file_selector_linux", + "sha256": "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.9.3+2" + }, + "file_selector_macos": { + "dependency": "transitive", + "description": { + "name": "file_selector_macos", + "sha256": "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.9.4+3" + }, + "file_selector_platform_interface": { + "dependency": "transitive", + "description": { + "name": "file_selector_platform_interface", + "sha256": "a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.6.2" + }, + "file_selector_web": { + "dependency": "transitive", + "description": { + "name": "file_selector_web", + "sha256": "c4c0ea4224d97a60a7067eca0c8fd419e708ff830e0c83b11a48faf566cec3e7", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.9.4+2" + }, + "file_selector_windows": { + "dependency": "transitive", + "description": { + "name": "file_selector_windows", + "sha256": "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.9.3+4" + }, + "fixnum": { + "dependency": "transitive", + "description": { + "name": "fixnum", + "sha256": "b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.1" + }, + "flutter": { + "dependency": "direct main", + "description": "flutter", + "source": "sdk", + "version": "0.0.0" + }, + "flutter_adaptive_scaffold": { + "dependency": "direct main", + "description": { + "name": "flutter_adaptive_scaffold", + "sha256": "5eb1d1d174304a4e67c4bb402ed38cb4a5ebdac95ce54099e91460accb33d295", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.3.3+1" + }, + "flutter_confetti": { + "dependency": "direct main", + "description": { + "name": "flutter_confetti", + "sha256": "7e46b82ea0adc456afc91037652bbfbd52a951804fde0708822fad5d68be6398", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.5.1" + }, + "flutter_driver": { + "dependency": "direct dev", + "description": "flutter", + "source": "sdk", + "version": "0.0.0" + }, + "flutter_highlighting": { + "dependency": "direct main", + "description": { + "name": "flutter_highlighting", + "sha256": "426770b1453e8302f8cc58455ebcaad33e3049e73ca18f9d3c83554552bf3baf", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.9.0+11.8.0" + }, + "flutter_html": { + "dependency": "direct main", + "description": { + "name": "flutter_html", + "sha256": "38a2fd702ffdf3243fb7441ab58aa1bc7e6922d95a50db76534de8260638558d", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.0.0" + }, + "flutter_html_svg": { + "dependency": "direct main", + "description": { + "name": "flutter_html_svg", + "sha256": "76f59c238571333d95271817c3d94688b3c4dca2735552e481e49039d3efdb13", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.0.0" + }, + "flutter_html_table": { + "dependency": "direct main", + "description": { + "name": "flutter_html_table", + "sha256": "de15300b1f6d8014e1702e7edfdf3411f362c8fb753e89bac4c99215ea94a4d8", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.0.0" + }, + "flutter_keyboard_visibility": { + "dependency": "direct main", + "description": { + "name": "flutter_keyboard_visibility", + "sha256": "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "6.0.0" + }, + "flutter_keyboard_visibility_linux": { + "dependency": "transitive", + "description": { + "name": "flutter_keyboard_visibility_linux", + "sha256": "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.0.0" + }, + "flutter_keyboard_visibility_macos": { + "dependency": "transitive", + "description": { + "name": "flutter_keyboard_visibility_macos", + "sha256": "c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.0.0" + }, + "flutter_keyboard_visibility_platform_interface": { + "dependency": "transitive", + "description": { + "name": "flutter_keyboard_visibility_platform_interface", + "sha256": "e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.0" + }, + "flutter_keyboard_visibility_web": { + "dependency": "transitive", + "description": { + "name": "flutter_keyboard_visibility_web", + "sha256": "d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.0" + }, + "flutter_keyboard_visibility_windows": { + "dependency": "transitive", + "description": { + "name": "flutter_keyboard_visibility_windows", + "sha256": "fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.0.0" + }, + "flutter_launcher_icons": { + "dependency": "direct dev", + "description": { + "name": "flutter_launcher_icons", + "sha256": "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.14.4" + }, + "flutter_layout_grid": { + "dependency": "transitive", + "description": { + "name": "flutter_layout_grid", + "sha256": "739e568db97af031d528dfd8a80d333df0e5a310a126e087690fa42cd61dfb5f", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.8" + }, + "flutter_lints": { + "dependency": "direct dev", + "description": { + "name": "flutter_lints", + "sha256": "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "6.0.0" + }, + "flutter_local_notifications": { + "dependency": "direct main", + "description": { + "name": "flutter_local_notifications", + "sha256": "20ca0a9c82ce0c855ac62a2e580ab867f3fbea82680a90647f7953832d0850ae", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "19.4.0" + }, + "flutter_local_notifications_linux": { + "dependency": "transitive", + "description": { + "name": "flutter_local_notifications_linux", + "sha256": "e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "6.0.0" + }, + "flutter_local_notifications_platform_interface": { + "dependency": "transitive", + "description": { + "name": "flutter_local_notifications_platform_interface", + "sha256": "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "9.1.0" + }, + "flutter_local_notifications_windows": { + "dependency": "transitive", + "description": { + "name": "flutter_local_notifications_windows", + "sha256": "ed46d7ae4ec9d19e4c8fa2badac5fe27ba87a3fe387343ce726f927af074ec98", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.0.2" + }, + "flutter_localizations": { + "dependency": "direct main", + "description": "flutter", + "source": "sdk", + "version": "0.0.0" + }, + "flutter_openssl_crypto": { + "dependency": "direct main", + "description": { + "name": "flutter_openssl_crypto", + "sha256": "293b4fcda13ab0710645a16e82f3d5b7de19bfc0ab2d06bcdb87637222eda5e1", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.5.0" + }, + "flutter_plugin_android_lifecycle": { + "dependency": "transitive", + "description": { + "name": "flutter_plugin_android_lifecycle", + "sha256": "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.29" + }, + "flutter_rust_bridge": { + "dependency": "transitive", + "description": { + "name": "flutter_rust_bridge", + "sha256": "b416ff56002789e636244fb4cc449f587656eff995e5a7169457eb0593fcaddb", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.10.0" + }, + "flutter_secure_storage": { + "dependency": "direct main", + "description": { + "name": "flutter_secure_storage", + "sha256": "f7eceb0bc6f4fd0441e29d43cab9ac2a1c5ffd7ea7b64075136b718c46954874", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "10.0.0-beta.4" + }, + "flutter_secure_storage_darwin": { + "dependency": "transitive", + "description": { + "name": "flutter_secure_storage_darwin", + "sha256": "f226f2a572bed96bc6542198ebaec227150786e34311d455a7e2d3d06d951845", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.1.0" + }, + "flutter_secure_storage_linux": { + "dependency": "transitive", + "description": { + "name": "flutter_secure_storage_linux", + "sha256": "9b4b73127e857cd3117d43a70fa3dddadb6e0b253be62e6a6ab85caa0742182c", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.1" + }, + "flutter_secure_storage_platform_interface": { + "dependency": "transitive", + "description": { + "name": "flutter_secure_storage_platform_interface", + "sha256": "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.1" + }, + "flutter_secure_storage_web": { + "dependency": "transitive", + "description": { + "name": "flutter_secure_storage_web", + "sha256": "4c3f233e739545c6cb09286eeec1cc4744138372b985113acc904f7263bef517", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.0" + }, + "flutter_secure_storage_windows": { + "dependency": "transitive", + "description": { + "name": "flutter_secure_storage_windows", + "sha256": "ff32af20f70a8d0e59b2938fc92de35b54a74671041c814275afd80e27df9f21", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "4.0.0" + }, + "flutter_svg": { + "dependency": "direct main", + "description": { + "name": "flutter_svg", + "sha256": "cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.2.0" + }, + "flutter_test": { + "dependency": "direct dev", + "description": "flutter", + "source": "sdk", + "version": "0.0.0" + }, + "flutter_typeahead": { + "dependency": "direct main", + "description": { + "name": "flutter_typeahead", + "sha256": "d64712c65db240b1057559b952398ebb6e498077baeebf9b0731dade62438a6d", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "5.2.0" + }, + "flutter_vodozemac": { + "dependency": "direct main", + "description": { + "name": "flutter_vodozemac", + "sha256": "2405ca121b84d1cd83200a14021022e1691b123a23bcefc36adc7740cefbc1f9", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.2.2" + }, + "flutter_web_plugins": { + "dependency": "transitive", + "description": "flutter", + "source": "sdk", + "version": "0.0.0" + }, + "flutter_zxing": { + "dependency": "direct main", + "description": { + "name": "flutter_zxing", + "sha256": "dbcd89da2c9aa84f48d7d7e1ba436825f8656a69b142abb7bcdb7c2d9c22d48c", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.2.1" + }, + "frontend_server_client": { + "dependency": "transitive", + "description": { + "name": "frontend_server_client", + "sha256": "f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "4.0.0" + }, + "fuchsia_remote_debug_protocol": { + "dependency": "transitive", + "description": "flutter", + "source": "sdk", + "version": "0.0.0" + }, + "glob": { + "dependency": "transitive", + "description": { + "name": "glob", + "sha256": "c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.3" + }, + "go_router": { + "dependency": "direct main", + "description": { + "name": "go_router", + "sha256": "8b1f37dfaf6e958c6b872322db06f946509433bec3de753c3491a42ae9ec2b48", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "16.1.0" + }, + "gtk": { + "dependency": "transitive", + "description": { + "name": "gtk", + "sha256": "e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.0" + }, + "highlighting": { + "dependency": "direct main", + "description": { + "name": "highlighting", + "sha256": "196005ed9c98ee559939fcecd466fa941b9e99b3a93394691b86780ad4da50f3", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.9.0+11.8.0" + }, + "html": { + "dependency": "direct main", + "description": { + "name": "html", + "sha256": "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.15.6" + }, + "html_unescape": { + "dependency": "transitive", + "description": { + "name": "html_unescape", + "sha256": "15362d7a18f19d7b742ef8dcb811f5fd2a2df98db9f80ea393c075189e0b61e3", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.0" + }, + "http": { + "dependency": "direct main", + "description": { + "name": "http", + "sha256": "bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.5.0" + }, + "http_parser": { + "dependency": "transitive", + "description": { + "name": "http_parser", + "sha256": "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "4.1.2" + }, + "http_profile": { + "dependency": "transitive", + "description": { + "name": "http_profile", + "sha256": "7e679e355b09aaee2ab5010915c932cce3f2d1c11c3b2dc177891687014ffa78", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.1.0" + }, + "image": { + "dependency": "direct main", + "description": { + "name": "image", + "sha256": "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "4.5.4" + }, + "image_picker": { + "dependency": "direct main", + "description": { + "name": "image_picker", + "sha256": "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.2" + }, + "image_picker_android": { + "dependency": "transitive", + "description": { + "name": "image_picker_android", + "sha256": "b08e9a04d0f8d91f4a6e767a745b9871bfbc585410205c311d0492de20a7ccd6", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.8.12+25" + }, + "image_picker_for_web": { + "dependency": "transitive", + "description": { + "name": "image_picker_for_web", + "sha256": "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.0.6" + }, + "image_picker_ios": { + "dependency": "transitive", + "description": { + "name": "image_picker_ios", + "sha256": "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.8.12+2" + }, + "image_picker_linux": { + "dependency": "transitive", + "description": { + "name": "image_picker_linux", + "sha256": "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.2.1+2" + }, + "image_picker_macos": { + "dependency": "transitive", + "description": { + "name": "image_picker_macos", + "sha256": "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.2.1+2" + }, + "image_picker_platform_interface": { + "dependency": "transitive", + "description": { + "name": "image_picker_platform_interface", + "sha256": "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.10.1" + }, + "image_picker_windows": { + "dependency": "transitive", + "description": { + "name": "image_picker_windows", + "sha256": "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.2.1+1" + }, + "import_sorter": { + "dependency": "direct main", + "description": { + "name": "import_sorter", + "sha256": "eb15738ccead84e62c31e0208ea4e3104415efcd4972b86906ca64a1187d0836", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "4.6.0" + }, + "integration_test": { + "dependency": "direct dev", + "description": "flutter", + "source": "sdk", + "version": "0.0.0" + }, + "intl": { + "dependency": "direct main", + "description": { + "name": "intl", + "sha256": "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.20.2" + }, + "io": { + "dependency": "transitive", + "description": { + "name": "io", + "sha256": "dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.0.5" + }, + "js": { + "dependency": "transitive", + "description": { + "name": "js", + "sha256": "f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.6.7" + }, + "json_annotation": { + "dependency": "transitive", + "description": { + "name": "json_annotation", + "sha256": "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "4.9.0" + }, + "just_audio": { + "dependency": "direct main", + "description": { + "name": "just_audio", + "sha256": "679637a3ec5b6e00f36472f5a3663667df00ee4822cbf5dafca0f568c710960a", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.10.4" + }, + "just_audio_media_kit": { + "dependency": "direct main", + "description": { + "name": "just_audio_media_kit", + "sha256": "f3cf04c3a50339709e87e90b4e841eef4364ab4be2bdbac0c54cc48679f84d23", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.0" + }, + "just_audio_platform_interface": { + "dependency": "transitive", + "description": { + "name": "just_audio_platform_interface", + "sha256": "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "4.6.0" + }, + "just_audio_web": { + "dependency": "transitive", + "description": { + "name": "just_audio_web", + "sha256": "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.4.16" + }, + "just_waveform": { + "dependency": "direct main", + "description": { + "name": "just_waveform", + "sha256": "8c65acd24f13b866e3377f07f8869e823f3f2d8b734938f4e6688075af40b4f2", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.0.7" + }, + "leak_tracker": { + "dependency": "transitive", + "description": { + "name": "leak_tracker", + "sha256": "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "10.0.9" + }, + "leak_tracker_flutter_testing": { + "dependency": "transitive", + "description": { + "name": "leak_tracker_flutter_testing", + "sha256": "f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.0.9" + }, + "leak_tracker_testing": { + "dependency": "transitive", + "description": { + "name": "leak_tracker_testing", + "sha256": "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.0.1" + }, + "linkify": { + "dependency": "direct main", + "description": { + "name": "linkify", + "sha256": "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "5.0.0" + }, + "lints": { + "dependency": "transitive", + "description": { + "name": "lints", + "sha256": "a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "6.0.0" + }, + "list_counter": { + "dependency": "transitive", + "description": { + "name": "list_counter", + "sha256": "c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.0.2" + }, + "locale_names": { + "dependency": "direct main", + "description": { + "name": "locale_names", + "sha256": "7a89ca54072f4f13d0f5df5a9ba69337554bf2fd057d1dd2a238898f3f159374", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.1" + }, + "logging": { + "dependency": "transitive", + "description": { + "name": "logging", + "sha256": "c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.3.0" + }, + "lottie": { + "dependency": "direct main", + "description": { + "name": "lottie", + "sha256": "c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.3.1" + }, + "markdown": { + "dependency": "transitive", + "description": { + "name": "markdown", + "sha256": "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "7.3.0" + }, + "matcher": { + "dependency": "transitive", + "description": { + "name": "matcher", + "sha256": "dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.12.17" + }, + "material_color_utilities": { + "dependency": "transitive", + "description": { + "name": "material_color_utilities", + "sha256": "f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.11.1" + }, + "matrix": { + "dependency": "direct main", + "description": { + "path": ".", + "ref": "braid/msc3861-native-oidc", + "resolved-ref": "82ad90573e0e5e1ccb2cf1e669a5861bd6db351c", + "url": "https://github.com/TheOneWithTheBraid/matrix-dart-sdk.git" + }, + "source": "git", + "version": "1.1.0" + }, + "matrix_homeserver_recommendations": { + "dependency": "direct main", + "description": { + "name": "matrix_homeserver_recommendations", + "sha256": "48cd67146dd80b925c1cce1604da4712e7963b490d31801bad70b51ff8e30cd2", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.4.1" + }, + "media_kit": { + "dependency": "direct main", + "description": { + "path": "media_kit", + "ref": "braid/stub-template", + "resolved-ref": "215972e56ceb6036b51d1dc8803d5e0ab489bfe1", + "url": "https://github.com/TheOneWithTheBraid/media-kit.git" + }, + "source": "git", + "version": "1.2.0" + }, + "media_kit_libs_android_video": { + "dependency": "direct overridden", + "description": { + "path": "libs/android/media_kit_libs_android_video", + "ref": "main", + "resolved-ref": "ad84c59faa2b871926cb31516bdeec65d7676884", + "url": "https://github.com/Predidit/media-kit.git" + }, + "source": "git", + "version": "1.3.6" + }, + "media_kit_libs_ios_video": { + "dependency": "transitive", + "description": { + "name": "media_kit_libs_ios_video", + "sha256": "b5382994eb37a4564c368386c154ad70ba0cc78dacdd3fb0cd9f30db6d837991", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.4" + }, + "media_kit_libs_linux": { + "dependency": "transitive", + "description": { + "name": "media_kit_libs_linux", + "sha256": "2b473399a49ec94452c4d4ae51cfc0f6585074398d74216092bf3d54aac37ecf", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.2.1" + }, + "media_kit_libs_macos_video": { + "dependency": "transitive", + "description": { + "name": "media_kit_libs_macos_video", + "sha256": "f26aa1452b665df288e360393758f84b911f70ffb3878032e1aabba23aa1032d", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.4" + }, + "media_kit_libs_video": { + "dependency": "direct main", + "description": { + "name": "media_kit_libs_video", + "sha256": "958cc55e7065d9d01f52a2842dab2a0812a92add18489f1006d864fb5e42a3ef", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.0.6" + }, + "media_kit_libs_windows_video": { + "dependency": "transitive", + "description": { + "name": "media_kit_libs_windows_video", + "sha256": "dff76da2778729ab650229e6b4ec6ec111eb5151431002cbd7ea304ff1f112ab", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.0.11" + }, + "media_kit_video": { + "dependency": "direct main", + "description": { + "name": "media_kit_video", + "sha256": "a656a9463298c1adc64c57f2d012874f7f2900f0c614d9545a3e7b8bb9e2137b", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.3.0" + }, + "media_store_plus": { + "dependency": "direct main", + "description": { + "name": "media_store_plus", + "sha256": "4b4971365e00a4ed9fde14abf40d7c27475b66b8bba9bf43478ae2ecb449df20", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.1.3" + }, + "meta": { + "dependency": "transitive", + "description": { + "name": "meta", + "sha256": "e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.16.0" + }, + "mime": { + "dependency": "direct main", + "description": { + "name": "mime", + "sha256": "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.0" + }, + "objective_c": { + "dependency": "transitive", + "description": { + "name": "objective_c", + "sha256": "9f034ba1eeca53ddb339bc8f4813cb07336a849cd735559b60cdc068ecce2dc7", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "7.1.0" + }, + "package_config": { + "dependency": "transitive", + "description": { + "name": "package_config", + "sha256": "f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.2.0" + }, + "package_info_plus": { + "dependency": "transitive", + "description": { + "name": "package_info_plus", + "sha256": "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "8.3.1" + }, + "package_info_plus_platform_interface": { + "dependency": "transitive", + "description": { + "name": "package_info_plus_platform_interface", + "sha256": "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.2.1" + }, + "path": { + "dependency": "transitive", + "description": { + "name": "path", + "sha256": "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.9.1" + }, + "path_parsing": { + "dependency": "transitive", + "description": { + "name": "path_parsing", + "sha256": "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.0" + }, + "path_provider": { + "dependency": "direct main", + "description": { + "name": "path_provider", + "sha256": "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.5" + }, + "path_provider_android": { + "dependency": "transitive", + "description": { + "name": "path_provider_android", + "sha256": "d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.2.17" + }, + "path_provider_foundation": { + "dependency": "transitive", + "description": { + "name": "path_provider_foundation", + "sha256": "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.4.1" + }, + "path_provider_linux": { + "dependency": "transitive", + "description": { + "name": "path_provider_linux", + "sha256": "f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.2.1" + }, + "path_provider_platform_interface": { + "dependency": "transitive", + "description": { + "name": "path_provider_platform_interface", + "sha256": "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.2" + }, + "path_provider_windows": { + "dependency": "transitive", + "description": { + "name": "path_provider_windows", + "sha256": "bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.3.0" + }, + "petitparser": { + "dependency": "transitive", + "description": { + "name": "petitparser", + "sha256": "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "6.1.0" + }, + "platform": { + "dependency": "transitive", + "description": { + "name": "platform", + "sha256": "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.1.6" + }, + "plugin_platform_interface": { + "dependency": "transitive", + "description": { + "name": "plugin_platform_interface", + "sha256": "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.8" + }, + "pointer_interceptor": { + "dependency": "transitive", + "description": { + "name": "pointer_interceptor", + "sha256": "57210410680379aea8b1b7ed6ae0c3ad349bfd56fe845b8ea934a53344b9d523", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.10.1+2" + }, + "pointer_interceptor_ios": { + "dependency": "transitive", + "description": { + "name": "pointer_interceptor_ios", + "sha256": "a6906772b3205b42c44614fcea28f818b1e5fdad73a4ca742a7bd49818d9c917", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.10.1" + }, + "pointer_interceptor_platform_interface": { + "dependency": "transitive", + "description": { + "name": "pointer_interceptor_platform_interface", + "sha256": "0597b0560e14354baeb23f8375cd612e8bd4841bf8306ecb71fcd0bb78552506", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.10.0+1" + }, + "pointer_interceptor_web": { + "dependency": "transitive", + "description": { + "name": "pointer_interceptor_web", + "sha256": "460b600e71de6fcea2b3d5f662c92293c049c4319e27f0829310e5a953b3ee2a", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.10.3" + }, + "pool": { + "dependency": "transitive", + "description": { + "name": "pool", + "sha256": "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.5.1" + }, + "posix": { + "dependency": "transitive", + "description": { + "name": "posix", + "sha256": "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "6.0.3" + }, + "process": { + "dependency": "transitive", + "description": { + "name": "process", + "sha256": "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "5.0.3" + }, + "pub_semver": { + "dependency": "transitive", + "description": { + "name": "pub_semver", + "sha256": "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.2.0" + }, + "qr": { + "dependency": "transitive", + "description": { + "name": "qr", + "sha256": "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.0.2" + }, + "quiver": { + "dependency": "transitive", + "description": { + "name": "quiver", + "sha256": "ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.2.2" + }, + "random_string": { + "dependency": "transitive", + "description": { + "name": "random_string", + "sha256": "03b52435aae8cbdd1056cf91bfc5bf845e9706724dd35ae2e99fa14a1ef79d02", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.3.1" + }, + "receive_sharing_intent": { + "dependency": "direct main", + "description": { + "name": "receive_sharing_intent", + "sha256": "ec76056e4d258ad708e76d85591d933678625318e411564dcb9059048ca3a593", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.8.1" + }, + "rxdart": { + "dependency": "transitive", + "description": { + "name": "rxdart", + "sha256": "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.28.0" + }, + "safe_local_storage": { + "dependency": "transitive", + "description": { + "name": "safe_local_storage", + "sha256": "e9a21b6fec7a8aa62cc2585ff4c1b127df42f3185adbd2aca66b47abe2e80236", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.1" + }, + "screen_brightness_android": { + "dependency": "transitive", + "description": { + "name": "screen_brightness_android", + "sha256": "fb5fa43cb89d0c9b8534556c427db1e97e46594ac5d66ebdcf16063b773d54ed", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.2" + }, + "screen_brightness_platform_interface": { + "dependency": "transitive", + "description": { + "name": "screen_brightness_platform_interface", + "sha256": "737bd47b57746bc4291cab1b8a5843ee881af499514881b0247ec77447ee769c", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.0" + }, + "sdp_transform": { + "dependency": "transitive", + "description": { + "name": "sdp_transform", + "sha256": "73e412a5279a5c2de74001535208e20fff88f225c9a4571af0f7146202755e45", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.3.2" + }, + "sentry": { + "dependency": "direct main", + "description": { + "name": "sentry", + "sha256": "d9f3dcf1ecdd600cf9ce134f622383adde5423ecfdaf0ca9b20fbc1c44849337", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "9.6.0" + }, + "share_plus": { + "dependency": "direct main", + "description": { + "name": "share_plus", + "sha256": "d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "11.1.0" + }, + "share_plus_platform_interface": { + "dependency": "transitive", + "description": { + "name": "share_plus_platform_interface", + "sha256": "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "6.1.0" + }, + "sky_engine": { + "dependency": "transitive", + "description": "flutter", + "source": "sdk", + "version": "0.0.0" + }, + "slugify": { + "dependency": "transitive", + "description": { + "name": "slugify", + "sha256": "b272501565cb28050cac2d96b7bf28a2d24c8dae359280361d124f3093d337c3", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.0" + }, + "source_map_stack_trace": { + "dependency": "transitive", + "description": { + "name": "source_map_stack_trace", + "sha256": "c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.2" + }, + "source_maps": { + "dependency": "transitive", + "description": { + "name": "source_maps", + "sha256": "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.10.13" + }, + "source_span": { + "dependency": "transitive", + "description": { + "name": "source_span", + "sha256": "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.10.1" + }, + "sprintf": { + "dependency": "transitive", + "description": { + "name": "sprintf", + "sha256": "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "7.0.0" + }, + "sqflite": { + "dependency": "direct main", + "description": { + "name": "sqflite", + "sha256": "e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.4.2" + }, + "sqflite_android": { + "dependency": "transitive", + "description": { + "name": "sqflite_android", + "sha256": "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.4.1" + }, + "sqflite_common": { + "dependency": "transitive", + "description": { + "name": "sqflite_common", + "sha256": "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.5.6" + }, + "sqflite_common_ffi": { + "dependency": "direct main", + "description": { + "name": "sqflite_common_ffi", + "sha256": "9faa2fedc5385ef238ce772589f7718c24cdddd27419b609bb9c6f703ea27988", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.3.6" + }, + "sqflite_darwin": { + "dependency": "transitive", + "description": { + "name": "sqflite_darwin", + "sha256": "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.4.2" + }, + "sqflite_platform_interface": { + "dependency": "transitive", + "description": { + "name": "sqflite_platform_interface", + "sha256": "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.4.0" + }, + "sqlcipher_flutter_libs": { + "dependency": "direct main", + "description": { + "name": "sqlcipher_flutter_libs", + "sha256": "dd1fcc74d5baf3c36ad53e2652b2d06c9f8747494a3ccde0076e88b159dfe622", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.6.8" + }, + "sqlite3": { + "dependency": "transitive", + "description": { + "name": "sqlite3", + "sha256": "f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.9.0" + }, + "stack_trace": { + "dependency": "transitive", + "description": { + "name": "stack_trace", + "sha256": "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.12.1" + }, + "stream_channel": { + "dependency": "transitive", + "description": { + "name": "stream_channel", + "sha256": "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.4" + }, + "stream_transform": { + "dependency": "transitive", + "description": { + "name": "stream_transform", + "sha256": "ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.1" + }, + "string_scanner": { + "dependency": "transitive", + "description": { + "name": "string_scanner", + "sha256": "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.4.1" + }, + "sync_http": { + "dependency": "transitive", + "description": { + "name": "sync_http", + "sha256": "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.3.1" + }, + "synchronized": { + "dependency": "transitive", + "description": { + "name": "synchronized", + "sha256": "c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.4.0" + }, + "term_glyph": { + "dependency": "transitive", + "description": { + "name": "term_glyph", + "sha256": "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.2.2" + }, + "test_api": { + "dependency": "transitive", + "description": { + "name": "test_api", + "sha256": "fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.7.4" + }, + "test_core": { + "dependency": "transitive", + "description": { + "name": "test_core", + "sha256": "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.6.8" + }, + "timezone": { + "dependency": "transitive", + "description": { + "name": "timezone", + "sha256": "dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.10.1" + }, + "tint": { + "dependency": "transitive", + "description": { + "name": "tint", + "sha256": "9652d9a589f4536d5e392cf790263d120474f15da3cf1bee7f1fdb31b4de5f46", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.1" + }, + "tuple": { + "dependency": "transitive", + "description": { + "name": "tuple", + "sha256": "a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.0.2" + }, + "typed_data": { + "dependency": "transitive", + "description": { + "name": "typed_data", + "sha256": "f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.4.0" + }, + "unifiedpush": { + "dependency": "direct main", + "description": { + "name": "unifiedpush", + "sha256": "1418375efb580af9640de4eaf4209cb6481f9a48792648ced3051f30e67d9568", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "6.0.2" + }, + "unifiedpush_android": { + "dependency": "transitive", + "description": { + "name": "unifiedpush_android", + "sha256": "2f25db8eb2fc3183bf2e43db89fff20b2587adc1c361e1d1e06b223a0d45b50a", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.1.1" + }, + "unifiedpush_platform_interface": { + "dependency": "transitive", + "description": { + "name": "unifiedpush_platform_interface", + "sha256": "bb49d2748211520e35e0374ab816faa8a2c635267e71909d334ad868d532eba5", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.0.1" + }, + "universal_platform": { + "dependency": "transitive", + "description": { + "name": "universal_platform", + "sha256": "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.0" + }, + "unorm_dart": { + "dependency": "direct main", + "description": { + "name": "unorm_dart", + "sha256": "5b35bff83fce4d76467641438f9e867dc9bcfdb8c1694854f230579d68cd8f4b", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.2.0" + }, + "uri_parser": { + "dependency": "transitive", + "description": { + "name": "uri_parser", + "sha256": "ff4d2c720aca3f4f7d5445e23b11b2d15ef8af5ddce5164643f38ff962dcb270", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.0.0" + }, + "url_launcher": { + "dependency": "direct main", + "description": { + "name": "url_launcher", + "sha256": "f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "6.3.2" + }, + "url_launcher_android": { + "dependency": "transitive", + "description": { + "name": "url_launcher_android", + "sha256": "0aedad096a85b49df2e4725fa32118f9fa580f3b14af7a2d2221896a02cd5656", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "6.3.17" + }, + "url_launcher_ios": { + "dependency": "transitive", + "description": { + "name": "url_launcher_ios", + "sha256": "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "6.3.3" + }, + "url_launcher_linux": { + "dependency": "transitive", + "description": { + "name": "url_launcher_linux", + "sha256": "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.2.1" + }, + "url_launcher_macos": { + "dependency": "transitive", + "description": { + "name": "url_launcher_macos", + "sha256": "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.2.2" + }, + "url_launcher_platform_interface": { + "dependency": "transitive", + "description": { + "name": "url_launcher_platform_interface", + "sha256": "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.3.2" + }, + "url_launcher_web": { + "dependency": "transitive", + "description": { + "name": "url_launcher_web", + "sha256": "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.4.1" + }, + "url_launcher_windows": { + "dependency": "transitive", + "description": { + "name": "url_launcher_windows", + "sha256": "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.1.4" + }, + "uuid": { + "dependency": "transitive", + "description": { + "name": "uuid", + "sha256": "a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "4.5.1" + }, + "vector_graphics": { + "dependency": "transitive", + "description": { + "name": "vector_graphics", + "sha256": "a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.19" + }, + "vector_graphics_codec": { + "dependency": "transitive", + "description": { + "name": "vector_graphics_codec", + "sha256": "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.13" + }, + "vector_graphics_compiler": { + "dependency": "transitive", + "description": { + "name": "vector_graphics_compiler", + "sha256": "557a315b7d2a6dbb0aaaff84d857967ce6bdc96a63dc6ee2a57ce5a6ee5d3331", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.17" + }, + "vector_math": { + "dependency": "transitive", + "description": { + "name": "vector_math", + "sha256": "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "2.1.4" + }, + "visibility_detector": { + "dependency": "direct main", + "description": { + "name": "visibility_detector", + "sha256": "dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.4.0+2" + }, + "vm_service": { + "dependency": "transitive", + "description": { + "name": "vm_service", + "sha256": "ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "15.0.0" + }, + "vodozemac": { + "dependency": "direct main", + "description": { + "name": "vodozemac", + "sha256": "dba14017e042748fb22d270e8ab1d3e46965b89788dd3857dba938ec07571968", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.2.0" + }, + "volume_controller": { + "dependency": "transitive", + "description": { + "name": "volume_controller", + "sha256": "d75039e69c0d90e7810bfd47e3eedf29ff8543ea7a10392792e81f9bded7edf5", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.4.0" + }, + "wakelock_plus": { + "dependency": "transitive", + "description": { + "name": "wakelock_plus", + "sha256": "a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.3.2" + }, + "wakelock_plus_platform_interface": { + "dependency": "transitive", + "description": { + "name": "wakelock_plus_platform_interface", + "sha256": "e10444072e50dbc4999d7316fd303f7ea53d31c824aa5eb05d7ccbdd98985207", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.2.3" + }, + "watcher": { + "dependency": "transitive", + "description": { + "name": "watcher", + "sha256": "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.2" + }, + "web": { + "dependency": "direct main", + "description": { + "name": "web", + "sha256": "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.1" + }, + "web_multiple_tab_detector": { + "dependency": "direct main", + "description": { + "name": "web_multiple_tab_detector", + "sha256": "a40d485720ea88b4e25311421d435906ba202ac33e35435403dc1c49c5ed7c4e", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "0.3.0" + }, + "web_socket": { + "dependency": "transitive", + "description": { + "name": "web_socket", + "sha256": "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.0.1" + }, + "webdriver": { + "dependency": "transitive", + "description": { + "name": "webdriver", + "sha256": "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.1.0" + }, + "webrtc_interface": { + "dependency": "transitive", + "description": { + "name": "webrtc_interface", + "sha256": "86fe3afc81a08481dfb25cf14a5a94e27062ecef25544783f352c914e0bbc1ca", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.2.2+hotfix.2" + }, + "win32": { + "dependency": "transitive", + "description": { + "name": "win32", + "sha256": "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "5.14.0" + }, + "xdg_directories": { + "dependency": "transitive", + "description": { + "name": "xdg_directories", + "sha256": "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "1.1.0" + }, + "xml": { + "dependency": "transitive", + "description": { + "name": "xml", + "sha256": "b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "6.5.0" + }, + "yaml": { + "dependency": "transitive", + "description": { + "name": "yaml", + "sha256": "b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce", + "url": "https://pub.dev" + }, + "source": "hosted", + "version": "3.1.3" + } + }, + "sdks": { + "dart": ">=3.8.0 <4.0.0", + "flutter": ">=3.29.0" + } +} diff --git a/modules/home-manager-modules/programs/default.nix b/modules/home-manager-modules/programs/default.nix index 79f3351..68e5c71 100644 --- a/modules/home-manager-modules/programs/default.nix +++ b/modules/home-manager-modules/programs/default.nix @@ -38,5 +38,6 @@ ./davinci-resolve.nix ./gdx-liftoff.nix ./tor-browser.nix + ./polycule.nix ]; } diff --git a/modules/home-manager-modules/programs/polycule.nix b/modules/home-manager-modules/programs/polycule.nix new file mode 100644 index 0000000..a7004bd --- /dev/null +++ b/modules/home-manager-modules/programs/polycule.nix @@ -0,0 +1,32 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + options.programs.polycule = { + enable = lib.mkEnableOption "enable polycule matrix client"; + package = lib.mkPackageOption pkgs "polycule" {}; + }; + + config = lib.mkIf config.programs.polycule.enable (lib.mkMerge [ + { + home.packages = [ + config.programs.polycule.package + ]; + } + ( + lib.mkIf osConfig.host.impermanence.enable { + home.persistence."/persist${config.home.homeDirectory}" = { + # TODO: check that these are actually the correct folders + # directories = [ + # "${config.xdg.configHome}/polycule" + # "${config.xdg.dataHome}/polycule" + # "${config.xdg.cacheHome}/polycule" + # ]; + }; + } + ) + ]); +} From d4615fc4354c0e9c824a10485d096123ce34b32c Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 2 Oct 2025 12:51:20 -0500 Subject: [PATCH 51/62] chore: updated README --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e94eb58..dc74557 100644 --- a/README.md +++ b/README.md @@ -74,4 +74,8 @@ nix multi user, multi system, configuration with `sops` secret management, `home - make radarr, sonarr, and bazarr accessible over vpn - create some sort of service that allows uploading files to jellyfin - auto sort files into where they should go with some combination of filebot cli and picard cli -- graphana accessible though tailscale \ No newline at end of file +- graphana accessible though tailscale +- fix polycule package +- fix panoramax package +- actual instance +- intergrade radarr, sonarr, and bazarr \ No newline at end of file From 21edda5fe6eb75680abf3f4d478b00c006e845b3 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 2 Oct 2025 12:55:49 -0500 Subject: [PATCH 52/62] feat: added auto aproval for nixos mcp server settings --- README.md | 3 ++- .../home-manager/leyla/packages/vscode/default.nix | 13 ++++++++++++- .../programs/vscode/claudeDev.nix | 9 +++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dc74557..d0cda10 100644 --- a/README.md +++ b/README.md @@ -78,4 +78,5 @@ nix multi user, multi system, configuration with `sops` secret management, `home - fix polycule package - fix panoramax package - actual instance -- intergrade radarr, sonarr, and bazarr \ No newline at end of file +- intergrade radarr, sonarr, and bazarr +- claude code MCP servers should bundle node with them so they work in all environments diff --git a/configurations/home-manager/leyla/packages/vscode/default.nix b/configurations/home-manager/leyla/packages/vscode/default.nix index 981156b..ba9e48a 100644 --- a/configurations/home-manager/leyla/packages/vscode/default.nix +++ b/configurations/home-manager/leyla/packages/vscode/default.nix @@ -71,7 +71,18 @@ in { claudeDev = lib.mkIf ai-tooling-enabled { enable = true; mcp = { - nixos.enable = true; + nixos = { + enable = true; + autoApprove = { + nixos_search = true; + nixos_info = true; + home_manager_search = true; + home_manager_info = true; + darwin_search = true; + darwin_info = true; + nixos_flakes_search = true; + }; + }; eslint = { enable = true; autoApprove = { diff --git a/modules/home-manager-modules/programs/vscode/claudeDev.nix b/modules/home-manager-modules/programs/vscode/claudeDev.nix index cebf614..ffeaff3 100644 --- a/modules/home-manager-modules/programs/vscode/claudeDev.nix +++ b/modules/home-manager-modules/programs/vscode/claudeDev.nix @@ -72,6 +72,15 @@ in { mcp = { nixos = { enable = lib.mkEnableOption "enable NixOS MCP server for Claude Dev"; + autoApprove = { + nixos_search = lib.mkEnableOption "should the nixos_search tool be auto approved for the nixos MCP server"; + nixos_info = lib.mkEnableOption "should the nixos_info tool be auto approved for the nixos MCP server"; + home_manager_search = lib.mkEnableOption "should the home_manager_search tool be auto approved for the nixos MCP server"; + home_manager_info = lib.mkEnableOption "should the home_manager_info tool be auto approved for the nixos MCP server"; + darwin_search = lib.mkEnableOption "should the darwin_search tool be auto approved for the nixos MCP server"; + darwin_info = lib.mkEnableOption "should the darwin_info tool be auto approved for the nixos MCP server"; + nixos_flakes_search = lib.mkEnableOption "should the nixos_flakes_search tool be auto approved for the nixos MCP server"; + }; }; eslint = { enable = lib.mkEnableOption "enable ESLint MCP server for Claude Dev"; From c9bb9380b510ec0c9f50cf631e04e6170e866499 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 2 Oct 2025 15:45:21 -0500 Subject: [PATCH 53/62] feat: fixed vpn on defiant --- .../nixos/defiant/configuration.nix | 108 ++++++++++++------ 1 file changed, 70 insertions(+), 38 deletions(-) diff --git a/configurations/nixos/defiant/configuration.nix b/configurations/nixos/defiant/configuration.nix index 401173e..b16036b 100644 --- a/configurations/nixos/defiant/configuration.nix +++ b/configurations/nixos/defiant/configuration.nix @@ -132,23 +132,24 @@ }; }; - # "20-wg0" = { - # netdevConfig = { - # Kind = "wireguard"; - # Name = "wg0"; - # }; - # wireguardConfig = { - # PrivateKeyFile = config.sops.secrets."vpn-keys/proton-wireguard/defiant-p2p".path; - # ListenPort = 51820; - # }; - # wireguardPeers = [ - # { - # PublicKey = "rRO6yJim++Ezz6scCLMaizI+taDjU1pzR2nfW6qKbW0="; - # Endpoint = "185.230.126.146:51820"; - # AllowedIPs = ["0.0.0.0/0"]; - # } - # ]; - # }; + "20-wg0" = { + netdevConfig = { + Kind = "wireguard"; + Name = "wg0"; + }; + wireguardConfig = { + PrivateKeyFile = config.sops.secrets."vpn-keys/proton-wireguard/defiant-p2p".path; + ListenPort = 51820; + }; + wireguardPeers = [ + { + PublicKey = "rRO6yJim++Ezz6scCLMaizI+taDjU1pzR2nfW6qKbW0="; + Endpoint = "185.230.126.146:51820"; + # Allow all traffic but use policy routing to prevent system-wide VPN + AllowedIPs = ["0.0.0.0/0"]; + } + ]; + }; }; networks = { "40-bond0" = { @@ -163,36 +164,67 @@ "192.168.1.10/32" ]; - gateway = ["192.168.1.1"]; + # Set lower priority for default gateway to allow WireGuard interface binding + routes = [ + { + Destination = "0.0.0.0/0"; + Gateway = "192.168.1.1"; + Metric = 100; + } + ]; dns = ["192.168.1.1"]; }; - # For some reason this isn't working. It looks like traffic goes out and comes back but doesn't get correctly routed back to the wg interface on the return trip - # debugging steps: - # try sending data on the interface `ping -I wg0 8.8.8.8` - # view all traffic on the interface `sudo tshark -i wg0` - # see what applications are listening to port 14666 (thats what we currently have qbittorent set up to use) `ss -tuln | grep 14666` - # "50-wg0" = { - # matchConfig.Name = "wg0"; - # networkConfig = { - # DHCP = "no"; - # }; - # address = [ - # "10.2.0.2/32" - # ]; - # # routes = [ - # # { - # # Destination = "10.2.0.2/32"; - # # Gateway = "10.2.0.1"; - # # } - # # ]; - # }; + "50-wg0" = { + matchConfig.Name = "wg0"; + networkConfig = { + DHCP = "no"; + }; + address = [ + "10.2.0.2/32" + ]; + # Configure routing for application binding + routingPolicyRules = [ + { + # Route traffic from VPN interface through VPN table + From = "10.2.0.2/32"; + Table = 200; + Priority = 100; + } + ]; + routes = [ + { + # Direct route to VPN gateway + Destination = "10.2.0.1/32"; + Scope = "link"; + } + { + # Route VPN subnet through VPN gateway in custom table + Destination = "10.2.0.0/16"; + Gateway = "10.2.0.1"; + Table = 200; + } + { + # Route all traffic through VPN gateway in custom table + Destination = "0.0.0.0/0"; + Gateway = "10.2.0.1"; + Table = 200; + } + ]; + }; }; }; # limit arc usage to 50gb because ollama doesn't play nice with zfs using up all of the memory boot.kernelParams = ["zfs.zfs_arc_max=53687091200"]; + # Enable policy routing and source routing for application-specific VPN binding + boot.kernel.sysctl = { + "net.ipv4.conf.all.rp_filter" = 2; + "net.ipv4.conf.default.rp_filter" = 2; + "net.ipv4.conf.wg0.rp_filter" = 2; + }; + services = { # temp enable desktop environment for setup # Enable the X11 windowing system. From 03149db7ea58095fba9712d23e8e4e7179e69643 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 2 Oct 2025 17:53:07 -0500 Subject: [PATCH 54/62] build: updated flake lock --- flake.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index b5607f6..080c221 100644 --- a/flake.lock +++ b/flake.lock @@ -46,11 +46,11 @@ }, "locked": { "dir": "pkgs/firefox-addons", - "lastModified": 1759291409, - "narHash": "sha256-eAzmD4ijeWCFy4YqArNmVu8901nLQLHr6dCv94yRrFk=", + "lastModified": 1759403080, + "narHash": "sha256-EteyL8KyG9R5xzqyOBzyag4n2cSemu61VFrl3opJSqE=", "owner": "rycee", "repo": "nur-expressions", - "rev": "f9c2e6b2eebdbe0e87236a63ea323c86da79b6c5", + "rev": "8af6dfcbcbf1115a4f5aeed77ff0db5d3c02caf0", "type": "gitlab" }, "original": { @@ -217,11 +217,11 @@ ] }, "locked": { - "lastModified": 1759284197, - "narHash": "sha256-NbaOzcxsUxNm+Dday5DlV6P9CzRAonY2DNcp056oWWc=", + "lastModified": 1759369908, + "narHash": "sha256-IIhaE6jAge64z+fIyi/8Vtu0JdTtapbp4CvwiuIkZ1E=", "owner": "nix-community", "repo": "nix-vscode-extensions", - "rev": "a87f796f1ed4b0a8babe9370791a66aac4864887", + "rev": "a66ad2141b1440a838ead278c6edfe8a4ce75e6c", "type": "github" }, "original": { @@ -264,11 +264,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1759036355, - "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "lastModified": 1759381078, + "narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=", "owner": "nixos", "repo": "nixpkgs", - "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", "type": "github" }, "original": { From 2c918478abf29afc9e4a64bf59679fbee4c250e3 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Thu, 2 Oct 2025 18:53:32 -0500 Subject: [PATCH 55/62] feat: enabled filebot-cleanup task --- configurations/nixos/defiant/configuration.nix | 15 ++++++++++++++- configurations/nixos/defiant/filebot.nix | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/configurations/nixos/defiant/configuration.nix b/configurations/nixos/defiant/configuration.nix index b16036b..9fbdee6 100644 --- a/configurations/nixos/defiant/configuration.nix +++ b/configurations/nixos/defiant/configuration.nix @@ -351,9 +351,22 @@ }; filebot-cleanup = { - enable = false; + enable = true; licenseFile = "/srv/jellyfin/filebot_license.psm"; }; + + sonarr = { + enable = false; + openFirewall = true; + }; + radarr = { + enable = false; + openFirewall = true; + }; + bazarr = { + enable = false; + openFirewall = true; + }; }; # disable computer sleeping diff --git a/configurations/nixos/defiant/filebot.nix b/configurations/nixos/defiant/filebot.nix index 77d81bd..c6153f9 100644 --- a/configurations/nixos/defiant/filebot.nix +++ b/configurations/nixos/defiant/filebot.nix @@ -52,8 +52,8 @@ in { ${optionalString (cfg.licenseFile != null) '' ${pkgs.filebot}/bin/filebot --license "${cfg.licenseFile}" ''} - ${pkgs.filebot}/bin/filebot -rename -r "/srv/jellyfin/media/Movies/" --output "${cfg.cleanupDirectory}/" --format "{jellyfin}" -non-strict --action move - ${pkgs.filebot}/bin/filebot -rename -r "/srv/jellyfin/media/Shows/" --output "${cfg.cleanupDirectory}/" --format "{jellyfin}" -non-strict --action move + ${pkgs.filebot}/bin/filebot -rename -r "/srv/jellyfin/media/Movies/" --output "${cfg.cleanupDirectory}/" --format "{jellyfin}" -non-strict --action duplicate + ${pkgs.filebot}/bin/filebot -rename -r "/srv/jellyfin/media/Shows/" --output "${cfg.cleanupDirectory}/" --format "{jellyfin}" -non-strict --action duplicate ''; StandardOutput = "journal"; StandardError = "journal"; From 7483c2c01c320a7c46a4add634cc90af2e8c9a9c Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Fri, 3 Oct 2025 13:59:12 -0500 Subject: [PATCH 56/62] feat: fixed polyclue package --- configurations/home-manager/leyla/packages/default.nix | 4 ++-- modules/common-modules/pkgs/polycule/default.nix | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/configurations/home-manager/leyla/packages/default.nix b/configurations/home-manager/leyla/packages/default.nix index a6da7f5..5bccad3 100644 --- a/configurations/home-manager/leyla/packages/default.nix +++ b/configurations/home-manager/leyla/packages/default.nix @@ -70,8 +70,8 @@ in { noisetorch.enable = true; tor-browser.enable = true; gdx-liftoff.enable = true; - # polycule package is currently broken - polycule.enable = false; + # polycule package is now working with Flutter 3.29 + polycule.enable = true; }) ]; } diff --git a/modules/common-modules/pkgs/polycule/default.nix b/modules/common-modules/pkgs/polycule/default.nix index d092897..28c51fc 100644 --- a/modules/common-modules/pkgs/polycule/default.nix +++ b/modules/common-modules/pkgs/polycule/default.nix @@ -1,6 +1,6 @@ { lib, - flutter329, + flutter332, fetchFromGitLab, pkg-config, wrapGAppsHook, @@ -44,7 +44,7 @@ libva, libvdpau, }: -flutter329.buildFlutterApplication rec { +flutter332.buildFlutterApplication rec { pname = "polycule"; version = "0.3.0"; From bc705098d65a4366dd2458492b55d8996f1437e9 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Fri, 3 Oct 2025 14:23:26 -0500 Subject: [PATCH 57/62] chore: removed completed task from README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d0cda10..c952fbf 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,6 @@ nix multi user, multi system, configuration with `sops` secret management, `home - create some sort of service that allows uploading files to jellyfin - auto sort files into where they should go with some combination of filebot cli and picard cli - graphana accessible though tailscale -- fix polycule package - fix panoramax package - actual instance - intergrade radarr, sonarr, and bazarr From 2935d43bcb34aca22a0221be1736e47bd2971dff Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Fri, 3 Oct 2025 21:10:20 -0500 Subject: [PATCH 58/62] feat: moved filebot cleanup to jellyfin persistence --- configurations/nixos/defiant/filebot.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configurations/nixos/defiant/filebot.nix b/configurations/nixos/defiant/filebot.nix index c6153f9..aaf247d 100644 --- a/configurations/nixos/defiant/filebot.nix +++ b/configurations/nixos/defiant/filebot.nix @@ -62,7 +62,7 @@ in { }; environment.persistence = lib.mkIf config.host.impermanence.enable { - "/persist/system/filebot_cleanup" = { + "/persist/system/jellyfin" = { enable = true; hideMounts = true; files = [ From 0730cc6594cdd9a3a66add42021de16c4ba9d220 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sat, 4 Oct 2025 11:15:53 -0500 Subject: [PATCH 59/62] feat: updated polycule package --- modules/common-modules/pkgs/polycule/default.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/common-modules/pkgs/polycule/default.nix b/modules/common-modules/pkgs/polycule/default.nix index 28c51fc..b463cc5 100644 --- a/modules/common-modules/pkgs/polycule/default.nix +++ b/modules/common-modules/pkgs/polycule/default.nix @@ -46,19 +46,19 @@ }: flutter332.buildFlutterApplication rec { pname = "polycule"; - version = "0.3.0"; + version = "0.3.4"; src = fetchFromGitLab { owner = "polycule_client"; repo = "polycule"; rev = "v${version}"; - hash = "sha256-kY1vJiDXh0rSCJNOAkO8JGiMR8kXwDHuc3T+S4MkOWY="; + hash = "sha256-RUu8DKuX2NUU5Ce5WLHtDaORkn7CSrgTj3KhM/z+yHc="; }; pubspecLock = lib.importJSON ./polycule-pubspec.lock.json; gitHashes = { - matrix = "sha256-e1HGC2yZyqqYB5YAGKmUkkdDbuSzhiUenJMKJgQYIi8="; + matrix = "sha256-w/QB5nYJ9Lh77TcYKEN/DnNQjWfp+9NX0dwQ9GOzWE8="; media_kit = "sha256-1sVX+aHFLFJBtrNZrR6tWkb80vFELW2N9EejyQKlBPg="; media_kit_libs_android_video = "sha256-N6QoktM8u9NYF8MAXLsxM9RlV8nICM4NbnmABHTRkZg="; }; From 884d11d0a36c5b05a29deddad32a1a9e503ba201 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sat, 4 Oct 2025 12:12:45 -0500 Subject: [PATCH 60/62] chore: updated host map in README --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c952fbf..acaa6e7 100644 --- a/README.md +++ b/README.md @@ -7,19 +7,19 @@ nix multi user, multi system, configuration with `sops` secret management, `home # Hosts ## Host Map -| Hostname | Device Description | Primary User | Role | -| :---------: | :------------------------: | :--------------: | :-------: | -| `twilight` | Desktop Computer | Leyla | Desktop | -| `horizon` | 13 inch Framework Laptop | Leyla | Laptop | -| `defiant` | NAS Server | Leyla | Server | -| `hesperium` | Mac | ????? | ??? | -| `emergent` | Desktop Computer | Eve | Desktop | -| `threshold` | Laptop | Eve | Laptop | -| `wolfram` | Steam Deck | House | Handheld | -| `ceder` | A5 Tablet (not using nix) | Leyla | Tablet | -| `skate` | A6 Tablet (not using nix) | Leyla | Tablet | -| `shale` | A6 Tablet (not using nix) | Eve | Tablet | -| `coven` | Pixel 8 (not using nix) | Leyla | Android | +| Hostname | Device Description | Primary User | Role | Provisioned | Using Nix | +| :---------: | :------------------------: | :--------------: | :-------: | :---------: | :-------: | +| `twilight` | Desktop Computer | Leyla | Desktop | ✅ | ✅ | +| `horizon` | 13 inch Framework Laptop | Leyla | Laptop | ✅ | ✅ | +| `defiant` | NAS Server | Leyla | Server | ✅ | ✅ | +| `hesperium` | Mac | ????? | Mac | ❌ | ❌ | +| `emergent` | Desktop Computer | Eve | Desktop | ✅ | ✅ | +| `threshold` | Laptop | Eve | Laptop | ❌ | ❌ | +| `wolfram` | Steam Deck | House | Handheld | ✅ | ❌ | +| `ceder` | A5 Tablet | Leyla | Tablet | ✅ | ❌ | +| `skate` | A6 Tablet | Leyla | Tablet | ❌ | ❌ | +| `shale` | A6 Tablet | Eve | Tablet | ✅ | ❌ | +| `coven` | Pixel 8 | Leyla | Android | ✅ | ❌ | # Tooling ## Rebuilding From 44922dfcd589116c996bf054e7951a020b06294e Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sun, 5 Oct 2025 14:58:41 -0500 Subject: [PATCH 61/62] feat: re enabled lix --- flake.lock | 90 ++++++++++++++++++++++++++++++++++++++++++++++-- flake.nix | 8 ++--- util/default.nix | 4 +-- 3 files changed, 94 insertions(+), 8 deletions(-) diff --git a/flake.lock b/flake.lock index 080c221..5be844f 100644 --- a/flake.lock +++ b/flake.lock @@ -111,6 +111,39 @@ "type": "github" } }, + "flake-utils_3": { + "inputs": { + "systems": "systems_3" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flakey-profile": { + "locked": { + "lastModified": 1712898590, + "narHash": "sha256-FhGIEU93VHAChKEXx905TSiPZKga69bWl1VB37FK//I=", + "owner": "lf-", + "repo": "flakey-profile", + "rev": "243c903fd8eadc0f63d205665a92d4df91d42d9d", + "type": "github" + }, + "original": { + "owner": "lf-", + "repo": "flakey-profile", + "type": "github" + } + }, "home-manager": { "inputs": { "nixpkgs": [ @@ -146,10 +179,47 @@ "type": "github" } }, + "lix": { + "flake": false, + "locked": { + "lastModified": 1759624822, + "narHash": "sha256-cf40qfsfpxJU/BnQ9PEj027LdPINNSsJqm+C6Ug93BA=", + "rev": "57333a0e600c5e096a609410a2f1059b97194b1e", + "type": "tarball", + "url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/57333a0e600c5e096a609410a2f1059b97194b1e.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://git.lix.systems/lix-project/lix/archive/main.tar.gz" + } + }, + "lix-module": { + "inputs": { + "flake-utils": "flake-utils", + "flakey-profile": "flakey-profile", + "lix": "lix", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1756511062, + "narHash": "sha256-IgD1JR7scSEwlK/YAbmrcTWpAYT30LPldCUHdzXkaMs=", + "ref": "refs/heads/main", + "rev": "3f09a5eb772e02d98bb8878ab687d5b721f00d16", + "revCount": 162, + "type": "git", + "url": "https://git.lix.systems/lix-project/nixos-module.git" + }, + "original": { + "type": "git", + "url": "https://git.lix.systems/lix-project/nixos-module.git" + } + }, "mcp-nixos": { "inputs": { "devshell": "devshell", - "flake-utils": "flake-utils", + "flake-utils": "flake-utils_2", "nixpkgs": [ "nixpkgs" ] @@ -211,7 +281,7 @@ }, "nix-vscode-extensions": { "inputs": { - "flake-utils": "flake-utils_2", + "flake-utils": "flake-utils_3", "nixpkgs": [ "nixpkgs" ] @@ -285,6 +355,7 @@ "flake-compat": "flake-compat", "home-manager": "home-manager", "impermanence": "impermanence", + "lix-module": "lix-module", "mcp-nixos": "mcp-nixos", "nix-darwin": "nix-darwin", "nix-syncthing": "nix-syncthing", @@ -360,6 +431,21 @@ "repo": "default", "type": "github" } + }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 151a54b..ddf92ce 100644 --- a/flake.nix +++ b/flake.nix @@ -5,10 +5,10 @@ # base packages nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - # lix-module = { - # url = "https://git.lix.systems/lix-project/nixos-module/archive/2.93.3-1.tar.gz"; - # inputs.nixpkgs.follows = "nixpkgs"; - # }; + lix-module = { + url = "git+https://git.lix.systems/lix-project/nixos-module.git"; + inputs.nixpkgs.follows = "nixpkgs"; + }; # secret encryption sops-nix = { diff --git a/util/default.nix b/util/default.nix index 5b61779..fb2f83d 100644 --- a/util/default.nix +++ b/util/default.nix @@ -10,7 +10,7 @@ nix-syncthing = inputs.nix-syncthing; disko = inputs.disko; impermanence = inputs.impermanence; - # lix-module = inputs.lix-module; + lix-module = inputs.lix-module; systems = [ "aarch64-darwin" @@ -83,7 +83,7 @@ in { impermanence.nixosModules.impermanence home-manager.nixosModules.home-manager disko.nixosModules.disko - # lix-module.nixosModules.default + lix-module.nixosModules.default ../modules/nixos-modules ../configurations/nixos/${host} ]; From 76d3c488db0c7468e7a9c47ebcfe9b6ac9cd0984 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Wed, 8 Oct 2025 13:05:08 -0500 Subject: [PATCH 62/62] feat: pinned mapilary version downloader feat: created user ivy --- configurations/home-manager/default.nix | 1 + configurations/home-manager/ivy/default.nix | 55 ++++++++++++++ configurations/home-manager/ivy/packages.nix | 73 +++++++++++++++++++ .../nixos/horizon/configuration.nix | 1 + flake.lock | 8 +- .../pkgs/mapillary-uploader.nix | 4 +- modules/nixos-modules/users.nix | 30 ++++++++ modules/system-modules/users.nix | 5 ++ nix-config-secrets | 2 +- 9 files changed, 172 insertions(+), 7 deletions(-) create mode 100644 configurations/home-manager/ivy/default.nix create mode 100644 configurations/home-manager/ivy/packages.nix diff --git a/configurations/home-manager/default.nix b/configurations/home-manager/default.nix index a7fa478..3f88481 100644 --- a/configurations/home-manager/default.nix +++ b/configurations/home-manager/default.nix @@ -8,5 +8,6 @@ in { leyla = lib.mkIf users.leyla.isNormalUser (import ./leyla); eve = lib.mkIf users.eve.isNormalUser (import ./eve); + ivy = lib.mkIf users.ivy.isNormalUser (import ./ivy); git = lib.mkIf (osConfig.services.forgejo.enable or false) (import ./git); } diff --git a/configurations/home-manager/ivy/default.nix b/configurations/home-manager/ivy/default.nix new file mode 100644 index 0000000..48a3cae --- /dev/null +++ b/configurations/home-manager/ivy/default.nix @@ -0,0 +1,55 @@ +{osConfig, ...}: let + userConfig = osConfig.host.users.ivy; +in { + imports = [ + ./packages.nix + ]; + + home = { + username = userConfig.name; + homeDirectory = osConfig.users.users.ivy.home; + + # This value determines the Home Manager release that your configuration is + # compatible with. This helps avoid breakage when a new Home Manager release + # introduces backwards incompatible changes. + # + # You should not change this value, even if you update Home Manager. If you do + # want to update the value, then make sure to first check the Home Manager + # release notes. + stateVersion = "23.11"; # Please read the comment before changing. + + # Home Manager is pretty good at managing dotfiles. The primary way to manage + # plain files is through 'home.file'. + file = { + # # Building this configuration will create a copy of 'dotfiles/screenrc' in + # # the Nix store. Activating the configuration will then make '~/.screenrc' a + # # symlink to the Nix store copy. + # ".screenrc".source = dotfiles/screenrc; + + # # You can also set the file content immediately. + # ".gradle/gradle.properties".text = '' + # org.gradle.console=verbose + # org.gradle.daemon.idletimeout=3600000 + # ''; + }; + + # Home Manager can also manage your environment variables through + # 'home.sessionVariables'. If you don't want to manage your shell through Home + # Manager then you have to manually source 'hm-session-vars.sh' located at + # either + # + # ~/.nix-profile/etc/profile.d/hm-session-vars.sh + # + # or + # + # ~/.local/state/nix/profiles/profile/etc/profile.d/hm-session-vars.sh + # + # or + # + # /etc/profiles/per-user/ivy/etc/profile.d/hm-session-vars.sh + # + sessionVariables = { + # EDITOR = "emacs"; + }; + }; +} diff --git a/configurations/home-manager/ivy/packages.nix b/configurations/home-manager/ivy/packages.nix new file mode 100644 index 0000000..3c2a3d9 --- /dev/null +++ b/configurations/home-manager/ivy/packages.nix @@ -0,0 +1,73 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: { + config = { + nixpkgs.config = { + allowUnfree = true; + }; + + # Programs that need to be installed with some extra configuration + programs = lib.mkMerge [ + { + # Let Home Manager install and manage itself. + home-manager.enable = true; + } + (lib.mkIf (config.user.isDesktopUser || config.user.isTerminalUser) { + # git = { + # enable = true; + # userName = "Ivy"; + # userEmail = "ivy@example.com"; # Update this with actual email + # extraConfig.init.defaultBranch = "main"; + # }; + + openssh = { + enable = true; + hostKeys = [ + { + type = "ed25519"; + path = "${config.home.username}_${osConfig.networking.hostName}_ed25519"; + } + ]; + }; + }) + (lib.mkIf config.user.isDesktopUser { + vscode = { + enable = true; + package = pkgs.vscodium; + mutableExtensionsDir = false; + + profiles.default = { + enableUpdateCheck = false; + enableExtensionUpdateCheck = false; + + extraExtensions = { + # Cline extension (Claude AI assistant) + claudeDev.enable = true; + # Auto Rename Tag + autoRenameTag.enable = true; + # Live Server + liveServer.enable = true; + }; + + extensions = let + extension-pkgs = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + in ( + with extension-pkgs.open-vsx; [ + streetsidesoftware.code-spell-checker + ] + ); + }; + }; + + firefox.enable = true; + discord.enable = true; + signal-desktop-bin.enable = true; + claude-code.enable = true; + }) + ]; + }; +} diff --git a/configurations/nixos/horizon/configuration.nix b/configurations/nixos/horizon/configuration.nix index 731c6b0..0e86fe7 100644 --- a/configurations/nixos/horizon/configuration.nix +++ b/configurations/nixos/horizon/configuration.nix @@ -32,6 +32,7 @@ isPrincipleUser = true; }; eve.isDesktopUser = true; + ivy.isDesktopUser = true; }; hardware = { diff --git a/flake.lock b/flake.lock index 5be844f..9309105 100644 --- a/flake.lock +++ b/flake.lock @@ -369,11 +369,11 @@ "secrets": { "flake": false, "locked": { - "lastModified": 1752531440, - "narHash": "sha256-04tQ3EUrtmZ7g6fVUkZC4AbAG+Z7lng79qU3jsiqWJY=", + "lastModified": 1759945215, + "narHash": "sha256-xmUzOuhJl6FtTjR5++OQvSoAnXe7/VA5QFCZDyFwBXo=", "ref": "refs/heads/main", - "rev": "f016767c13aa36dde91503f7a9f01bdd02468045", - "revCount": 20, + "rev": "444229a105445339fb028d15a8d866063c5f8141", + "revCount": 21, "type": "git", "url": "ssh://git@git.jan-leila.com/jan-leila/nix-config-secrets.git" }, diff --git a/modules/common-modules/pkgs/mapillary-uploader.nix b/modules/common-modules/pkgs/mapillary-uploader.nix index 3ab38f8..7ce24f2 100644 --- a/modules/common-modules/pkgs/mapillary-uploader.nix +++ b/modules/common-modules/pkgs/mapillary-uploader.nix @@ -4,10 +4,10 @@ appimageTools, }: let pname = "mapillary-uploader"; - version = "4.7.2"; # Based on the application output + version = "4.7.2"; src = fetchurl { - url = "https://tools.mapillary.com/uploader/download/linux"; + url = "http://tools.mapillary.com/uploader/download/linux/${version}"; name = "mapillary-uploader.AppImage"; sha256 = "sha256-Oyx7AIdA/2mwBaq7UzXOoyq/z2SU2sViMN40sY2RCQw="; }; diff --git a/modules/nixos-modules/users.nix b/modules/nixos-modules/users.nix index db7d4ab..137ae4b 100644 --- a/modules/nixos-modules/users.nix +++ b/modules/nixos-modules/users.nix @@ -15,6 +15,7 @@ uids = { leyla = 1000; eve = 1002; + ivy = 1004; jellyfin = 2000; forgejo = 2002; hass = 2004; @@ -33,6 +34,7 @@ gids = { leyla = 1000; eve = 1002; + ivy = 1004; users = 100; jellyfin_media = 2001; jellyfin = 2000; @@ -53,6 +55,7 @@ users = config.users.users; leyla = users.leyla.name; eve = users.eve.name; + ivy = users.ivy.name; in { config = lib.mkMerge [ { @@ -90,6 +93,10 @@ in { neededForUsers = true; sopsFile = "${inputs.secrets}/user-passwords.yaml"; }; + "passwords/ivy" = { + neededForUsers = true; + sopsFile = "${inputs.secrets}/user-passwords.yaml"; + }; }; }; @@ -123,6 +130,19 @@ in { group = config.users.users.eve.name; }; + ivy = { + uid = lib.mkForce uids.ivy; + name = lib.mkForce host.users.ivy.name; + description = "Ivy"; + extraGroups = + lib.optionals host.users.ivy.isNormalUser ["networkmanager"] + ++ (lib.lists.optionals host.users.ivy.isPrincipleUser ["wheel"]); + hashedPasswordFile = config.sops.secrets."passwords/ivy".path; + isNormalUser = host.users.ivy.isNormalUser; + isSystemUser = !host.users.ivy.isNormalUser; + group = config.users.users.ivy.name; + }; + jellyfin = { uid = lib.mkForce uids.jellyfin; isSystemUser = true; @@ -218,11 +238,19 @@ in { ]; }; + ivy = { + gid = lib.mkForce gids.ivy; + members = [ + ivy + ]; + }; + users = { gid = lib.mkForce gids.users; members = [ leyla eve + ivy ]; }; @@ -235,6 +263,7 @@ in { users.bazarr.name leyla eve + ivy ]; }; @@ -268,6 +297,7 @@ in { users.syncthing.name leyla eve + ivy ]; }; diff --git a/modules/system-modules/users.nix b/modules/system-modules/users.nix index cd9c900..dda9ed3 100644 --- a/modules/system-modules/users.nix +++ b/modules/system-modules/users.nix @@ -89,6 +89,11 @@ in { isDesktopUser = lib.mkDefault false; isTerminalUser = lib.mkDefault false; }; + ivy = { + isPrincipleUser = lib.mkDefault false; + isDesktopUser = lib.mkDefault false; + isTerminalUser = lib.mkDefault false; + }; }; assertions = diff --git a/nix-config-secrets b/nix-config-secrets index f016767..444229a 160000 --- a/nix-config-secrets +++ b/nix-config-secrets @@ -1 +1 @@ -Subproject commit f016767c13aa36dde91503f7a9f01bdd02468045 +Subproject commit 444229a105445339fb028d15a8d866063c5f8141