diff --git a/.gitignore b/.gitignore index ce2538f..2810727 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ result .direnv .vscode/* -!.vscode/settings.json \ No newline at end of file +!.vscode/settings.json +nixos.qcow2 diff --git a/.hooks/post-commit b/.hooks/post-commit index 56c439d..03a160d 100755 --- a/.hooks/post-commit +++ b/.hooks/post-commit @@ -3,4 +3,12 @@ echo "restoring stashed changes" -git stash pop -q +# Find the most recent pre-commit stash and restore it +recent_stash=$(git stash list | grep "pre-commit-stash-" | head -n 1 | cut -d: -f1) + +if [ -n "$recent_stash" ]; then + echo "Found recent pre-commit stash: $recent_stash" + git stash pop -q "$recent_stash" +else + echo "No pre-commit stash found to restore" +fi diff --git a/.hooks/post-merge b/.hooks/post-merge new file mode 100755 index 0000000..06fabc3 --- /dev/null +++ b/.hooks/post-merge @@ -0,0 +1,32 @@ +#!/usr/bin/env nix-shell +#! nix-shell -i bash ../shell.nix + +# Get current branch name +current_branch=$(git branch --show-current) + +# Only perform actions if we're on main branch and a merge just completed +if [ "$current_branch" = "main" ]; then + echo "Post-merge on main branch - running nix flake check" + + # Run nix flake check after merge into main + nix flake check + + if [ ! $? -eq 0 ]; then + echo "Warning: nix flake check failed after merge into main" + echo "Please fix the issues as soon as possible" + else + echo "nix flake check passed after merge" + fi + + # Check if there are any pre-commit stashes to restore + recent_stash=$(git stash list | grep "pre-commit-stash-" | head -n 1 | cut -d: -f1) + + if [ -n "$recent_stash" ]; then + echo "Post-merge: restoring pre-commit stash on main branch" + git stash pop -q "$recent_stash" + else + echo "Post-merge: no pre-commit stash to restore on main branch" + fi +else + echo "Post-merge: no action needed on branch '$current_branch'" +fi diff --git a/.hooks/pre-commit b/.hooks/pre-commit index f98c64f..74cbc64 100755 --- a/.hooks/pre-commit +++ b/.hooks/pre-commit @@ -1,14 +1,24 @@ #!/usr/bin/env nix-shell #! nix-shell -i bash ../shell.nix -echo "stashing all uncommitted changes" -git stash -q --keep-index +# Get current branch name +current_branch=$(git branch --show-current) -echo "checking flakes all compile" -nix flake check +echo "stashing all uncommitted changes with named stash (excluding hooks)" +git stash push -q --keep-index -m "pre-commit-stash-$(date +%s)" -- ':!.hooks/' -if [ ! $? -eq 0 ]; then - exit 1 +# Only run nix flake check if we're on main branch +if [ "$current_branch" = "main" ]; then + echo "On main branch - checking flakes all compile" + nix flake check + + if [ ! $? -eq 0 ]; then + echo "Error: nix flake check failed on main branch" + exit 1 + fi + echo "nix flake check passed" +else + echo "Not on main branch - skipping nix flake check" fi echo "running linter" @@ -19,4 +29,4 @@ RESULT=$? echo "adding lint changes to commit" git add -u -exit $RESULT \ No newline at end of file +exit $RESULT diff --git a/.hooks/pre-merge-commit b/.hooks/pre-merge-commit new file mode 100755 index 0000000..9b7b41d --- /dev/null +++ b/.hooks/pre-merge-commit @@ -0,0 +1,37 @@ +#!/usr/bin/env nix-shell +#! nix-shell -i bash ../shell.nix + +# Get the target branch (the branch being merged into) +target_branch="" + +# Check if we're in the middle of a merge +if [ -f .git/MERGE_HEAD ]; then + # We're in a merge, check if the current branch is main + current_branch=$(git branch --show-current) + if [ "$current_branch" = "main" ]; then + target_branch="main" + fi +fi + +# If we're merging into main, run nix flake check +if [ "$target_branch" = "main" ]; then + echo "Merging into main branch - running nix flake check..." + + echo "stashing all uncommitted changes with named stash (excluding hooks)" + git stash push -q --keep-index -m "pre-merge-stash-$(date +%s)" -- ':!.hooks/' + + echo "checking flakes all compile" + nix flake check + + if [ ! $? -eq 0 ]; then + echo "Error: nix flake check failed. Merge aborted." + echo "Please fix the issues and try merging again." + exit 1 + fi + + echo "nix flake check passed. Merge can proceed." +else + echo "Not merging into main branch, skipping nix flake check." +fi + +exit 0 diff --git a/.sops.yaml b/.sops.yaml index b8b0adf..a6e6f4f 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -13,3 +13,7 @@ creation_rules: key_groups: - age: - *leyla + - path_regex: secrets/application-keys.yaml$ + key_groups: + - age: + - *leyla \ No newline at end of file diff --git a/README.md b/README.md index bc31eca..15ea0a3 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 @@ -41,30 +41,94 @@ nix multi user, multi system, configuration with `sops` secret management, `home ## Research topics - Look into this for auto rotating sops keys `https://technotim.live/posts/rotate-sops-encryption-keys/` -- Look into this for flake templates https://nix.dev/manual/nix/2.22/command-ref/new-cli/nix3-flake-init +- Look into this for npins https://jade.fyi/blog/pinning-nixos-with-npins/ - https://nixos-and-flakes.thiscute.world/ +- proton mail now has an smtp server we could use that for our zfs and SMART test emails +- VR https://lvra.gitlab.io/docs/distros/nixos/ # Tasks: +## Documentation +- [ ] project layout +- [ ] users file structure +- [ ] reverse proxy design + - public service compatibility + - vpn based services compatibility +- [ ] the choice of impermanence +- [ ] storage module design + - base impermanence compatibility and structure reason + - what does local vs persist mean in pool names (do we need a second layer? ephemeral, local, and persist? local exist only on this machine and is not backed up, persist is backed up to other machines (I think we need to redo the sops and torrent/media folders?)) + - plans to possibly support btrfs in the future + - plans for home manager datasets + - plans for auto systemd service datasets +- [ ] plans to migrate to some kind of acl structure for user management +- [ ] plans to migrate from flakes to npins + +## Chores: +- [ ] test out crab hole service + ## Tech Debt -- monitor configuration in `~/.config/monitors.xml` should be sym linked to `/run/gdm/.config/monitors.xml` (https://www.reddit.com/r/NixOS/comments/u09cz9/home_manager_create_my_own_symlinks_automatically/) -- syncthing folder passwords -- nfs export should be backed by the same values for server and client -## New Features -- offline access for nfs mounts (overlay with rsync might be a good option here? https://www.spinics.net/lists/linux-unionfs/msg07105.html note about nfs4 and overlay fs) -- samba mounts -- figure out steam vr things? -- Open GL? -- rotate sops encryption keys periodically (and somehow sync between devices?) -- zfs email after scrubbing # TODO: test this -- wake on LAN for updates -- ISO target that contains authorized keys for nixos-anywhere https://github.com/diegofariasm/yggdrasil/blob/4acc43ebc7bcbf2e41376d14268e382007e94d78/hosts/bootstrap/default.nix -- zfs encryption FIDO2 2fa (look into shavee) -- Secure Boot - https://github.com/nix-community/lanzaboote -- SMART test with email results -- Create Tor guard/relay server -- remote distributed builds - https://nix.dev/tutorials/nixos/distributed-builds-setup.html -- migrate away from flakes and move to npins -- fix nfs -- fix home assistant -- create adguard server \ No newline at end of file +- [ ] monitor configuration in `~/.config/monitors.xml` should be sym linked to `/run/gdm/.config/monitors.xml` (https://www.reddit.com/r/NixOS/comments/u09cz9/home_manager_create_my_own_symlinks_automatically/) +- [ ] migrate away from flakes and move to npins +- [ ] `host.users` should be redone so that we just extend the base `users.users` object. Right now we cant quite do this because we have weird circular dependencies with disko/impermanence (not sure which one) and home manger enabling/disabling users per devices +- [ ] Home manager impermanence is preventing updates to the latest version of the module + +## Broken things +- [ ] figure out steam vr things? +- [ ] whisper was having issues +- [ ] auto loading of ssh agent keys that we auto generate per system + +## Data Integrity +- [ ] zfs email after scrubbing # TODO: test this +- [ ] SMART test with email results +- [ ] zfs encryption FIDO2 2fa (look into shavee) +- [ ] rotate sops encryption keys periodically (and somehow sync between devices?) +- [ ] Secure Boot - https://github.com/nix-community/lanzaboote +- [ ] auto turn off on power loss - nut +- [ ] every service needs to have its own data pool +- [ ] secondary server with data sync. Maybe a Pi with a usb hdd enclosure and use rtcwake to only turn on once a week to sync data over tailscale with connection initiated from pi's side. We could probably put this at LZ. Hoping for it to draw only like $1 of power a month. Initial sync should probably be done here before we move it over because that will take a while. Data should be encrypted so that devices doesn't have access to it. Project will prob cost like $1800 + +## Data Access +- [ ] nfs export should be backed by the same values for server and client +- [ ] samba mounts +- [ ] offline access for nfs mounts (overlay with rsync might be a good option here? https://www.spinics.net/lists/linux-unionfs/msg07105.html note about nfs4 and overlay fs) +- [ ] figure out why syncthing and jellyfins permissions don't propagate downwards +- [ ] make radarr, sonarr, and bazarr accessible over vpn with fully qualified names via reverse proxy +- [ ] move searx, home-assistant, actual, vikunja, jellyfin, paperless, and immich to only be accessible via vpn +- [ ] FreeIPA/SSSD/LDAP/Kerberos to manage uid and gid's + +## Services +- [ ] ntfy service for unified push +- [ ] signal socket server +- [ ] vikunja service for project management +- [ ] Penpot services (need to make this custom) +- [ ] minecraft server with old world file +- [ ] storj server +- [ ] XMR miner used to heat home based on smart thermostat +- [ ] Create Tor guard/relay server +- [ ] Create i2P node +- [ ] screeps server +- [ ] mastodon instance + +## DevOps +- [ ] 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 +- [ ] fix panoramax package +- [ ] claude code MCP servers should bundle node with them so they work in all environments + +## Observability +- [ ] graphana for dashboards +- [ ] prometheus and loki for metric and log collection + - [ ] zfs storage usage + - [ ] zfs drive health status + - [ ] service version lag + - [ ] network/cpu/ram utilization + - [ ] http latency + - [ ] postgres db load + - [ ] nginx queries +- [ ] ntfy.sh for push notifications +- [ ] kuma for uptime visualization + +## Packages +- [ ] Custom private fork of MultiMC \ No newline at end of file diff --git a/configurations/home-manager/eve/default.nix b/configurations/home-manager/eve/default.nix index 4e1d6fd..192c980 100644 --- a/configurations/home-manager/eve/default.nix +++ b/configurations/home-manager/eve/default.nix @@ -1,15 +1,10 @@ -{ - pkgs, - lib, - config, - osConfig, - ... -}: let +{osConfig, ...}: let userConfig = osConfig.host.users.eve; in { - nixpkgs.config = { - allowUnfree = true; - }; + imports = [ + ./packages.nix + ./gnomeconf.nix + ]; home = { username = userConfig.name; @@ -57,37 +52,5 @@ in { sessionVariables = { # EDITOR = "emacs"; }; - - packages = lib.lists.optionals userConfig.isDesktopUser ( - with pkgs; [ - firefox - bitwarden - discord - makemkv - signal-desktop-bin - ungoogled-chromium - ] - ); - }; - - programs = { - # Let Home Manager install and manage itself. - home-manager.enable = true; - - git = { - enable = true; - userName = "Eve Halfmann"; - userEmail = "evesnrobins@gmail.com"; - extraConfig.init.defaultBranch = "main"; - }; - - openssh = { - hostKeys = [ - { - type = "ed25519"; - path = "${config.home.username}_${osConfig.networking.hostName}_ed25519"; - } - ]; - }; }; } diff --git a/configurations/home-manager/eve/gnomeconf.nix b/configurations/home-manager/eve/gnomeconf.nix new file mode 100644 index 0000000..7cd3863 --- /dev/null +++ b/configurations/home-manager/eve/gnomeconf.nix @@ -0,0 +1,39 @@ +{ + osConfig, + lib, + ... +}: { + config = { + gnome = lib.mkMerge [ + { + colorScheme = "prefer-dark"; + accentColor = "slate"; + clockFormat = "24h"; + nightLight = { + enable = true; + automatic = false; + fromTime = 12.0; + toTime = 11.999999999999; + temperature = 2700; + }; + extraWindowControls = true; + extensions = { + dash-to-panel = { + enable = true; + }; + }; + } + + (lib.mkIf (osConfig.networking.hostName == "horizon") { + displayScaling = 125; + experimentalFeatures = { + scaleMonitorFramebuffer = true; + }; + }) + ]; + + dconf = { + enable = true; + }; + }; +} diff --git a/configurations/home-manager/eve/packages.nix b/configurations/home-manager/eve/packages.nix new file mode 100644 index 0000000..c87f786 --- /dev/null +++ b/configurations/home-manager/eve/packages.nix @@ -0,0 +1,90 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: let + userConfig = osConfig.host.users.eve; + hardware = osConfig.host.hardware; +in { + config = { + nixpkgs.config = { + allowUnfree = true; + }; + + # Packages that can be installed without any extra configuration + # See https://search.nixos.org/packages for all options + home.packages = lib.lists.optionals userConfig.isDesktopUser ( + with pkgs; [ + gnomeExtensions.dash-to-panel + claude-code + friture + ] + ); + + # Packages that need to be installed with some extra configuration + # See https://home-manager-options.extranix.com/ for all options + 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; + settings = { + user.name = "Eve"; + user.email = "evesnrobins@gmail.com"; + 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; + }; + + firefox.enable = true; + bitwarden.enable = true; + discord.enable = true; + makemkv.enable = true; + signal-desktop.enable = true; + steam.enable = true; + piper.enable = hardware.piperMouse.enable; + krita.enable = true; + ungoogled-chromium.enable = true; + + inkscape.enable = true; + obsidian.enable = true; + obs-studio.enable = true; + kdenlive.enable = true; + tor-browser.enable = true; + olympus.enable = true; + libreoffice.enable = true; + noita-entangled-worlds.enable = true; + + opencode.enable = osConfig.host.ai.enable; + + e621-downloader.enable = true; + + # Windows applications that we need to figure out how to install + guild-wars-2.enable = false; + vortex.enable = false; + dungeon-draft.enable = false; + vmware-workstation.enable = true; + }) + ]; + }; +} diff --git a/configurations/home-manager/git/default.nix b/configurations/home-manager/git/default.nix index 2276e7a..1ea29cc 100644 --- a/configurations/home-manager/git/default.nix +++ b/configurations/home-manager/git/default.nix @@ -1,4 +1,6 @@ {osConfig, ...}: { + impermanence.fallbackPersistence.enable = false; + home = { username = osConfig.users.users.git.name; homeDirectory = osConfig.users.users.git.home; diff --git a/configurations/home-manager/leyla/dconf.nix b/configurations/home-manager/leyla/dconf.nix index 5818641..9aa61f7 100644 --- a/configurations/home-manager/leyla/dconf.nix +++ b/configurations/home-manager/leyla/dconf.nix @@ -1,46 +1,43 @@ -{pkgs, ...}: { +{...}: { config = { + gnome = { + extraWindowControls = true; + colorScheme = "prefer-dark"; + clockFormat = "24h"; + nightLight = { + enable = true; + automatic = false; + fromTime = 12.0; + toTime = 11.999999999999; + temperature = 2700; + }; + extensions = { + dash-to-dock = { + enable = true; + options = { + "dock-position" = "LEFT"; + "intellihide-mode" = "ALL_WINDOWS"; + "show-trash" = false; + "require-pressure-to-show" = false; + "show-mounts" = false; + }; + }; + }; + hotkeys = { + "Open Terminal" = { + binding = "t"; + command = "kgx"; + }; + "Open Firefox" = { + binding = "f"; + command = "firefox"; + }; + }; + }; + dconf = { enable = true; settings = { - "org/gnome/desktop/interface".color-scheme = "prefer-dark"; - - "org/gnome/desktop/wm/preferences".button-layout = ":minimize,maximize,close"; - - "org/gnome/shell" = { - disable-user-extensions = false; # enables user extensions - enabled-extensions = [ - # Put UUIDs of extensions that you want to enable here. - # If the extension you want to enable is packaged in nixpkgs, - # you can easily get its UUID by accessing its extensionUuid - # field (look at the following example). - pkgs.gnomeExtensions.dash-to-dock.extensionUuid - - # Alternatively, you can manually pass UUID as a string. - # "dash-to-dock@micxgx.gmail.com" - ]; - }; - - "org/gnome/shell/extensions/dash-to-dock" = { - "dock-position" = "LEFT"; - "intellihide-mode" = "ALL_WINDOWS"; - "show-trash" = false; - "require-pressure-to-show" = false; - "show-mounts" = false; - }; - - "org/gnome/settings-daemon/plugins/media-keys" = { - custom-keybindings = [ - "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/" - ]; - }; - - "org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0" = { - binding = "t"; - command = "kgx"; - name = "Open Terminal"; - }; - "org/gnome/shell" = { favorite-apps = ["org.gnome.Nautilus.desktop" "firefox.desktop" "codium.desktop" "steam.desktop" "org.gnome.Console.desktop"]; # app-picker-layout = diff --git a/configurations/home-manager/leyla/default.nix b/configurations/home-manager/leyla/default.nix index 0c90ab1..20b04c7 100644 --- a/configurations/home-manager/leyla/default.nix +++ b/configurations/home-manager/leyla/default.nix @@ -1,16 +1,19 @@ { - osConfig, + pkgs, config, + osConfig, ... }: { imports = [ + ./packages ./i18n.nix - ./packages.nix ./impermanence.nix ./dconf.nix ]; config = { + impermanence.enable = osConfig.storage.impermanence.enable; + # Home Manager needs a bit of information about you and the paths it should # manage. home = { @@ -39,7 +42,7 @@ # org.gradle.console=verbose # org.gradle.daemon.idletimeout=3600000 # ''; - ".config/user-dirs.dirs" = { + "${config.xdg.configHome}/user-dirs.dirs" = { force = true; text = '' # This file is written by xdg-user-dirs-update @@ -83,69 +86,10 @@ }; }; - user = { - continue = { - enable = true; - docs = { - "Continue Docs" = { - startUrl = "https://docs.continue.dev"; - }; - "Nixpkgs" = { - startUrl = "https://ryantm.github.io/nixpkgs/#preface"; - }; - "Nix Manual" = { - startUrl = "https://nixos.org/manual/nixos/stable/"; - }; - "Home manager Manual" = { - startUrl = "https://nix-community.github.io/home-manager/"; - }; - "Nix Docs" = { - startUrl = "https://nix.dev/index.html"; - }; - "Linux Man Page" = { - startUrl = "https://linux.die.net/man/"; - }; - }; - }; - }; - - programs = { - # Let Home Manager install and manage itself. - home-manager.enable = true; - - # set up git defaults - git = { - enable = true; - userName = "Leyla Becker"; - userEmail = "git@jan-leila.com"; - extraConfig.init.defaultBranch = "main"; - }; - - # add direnv to auto load flakes for development - direnv = { - enable = true; - enableBashIntegration = true; - nix-direnv.enable = true; - config = { - global.hide_env_diff = true; - whitelist.exact = ["/home/leyla/documents/code/nix-config"]; - }; - }; - bash.enable = true; - - openssh = { - authorizedKeys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJHeItmt8TRW43uNcOC+eIurYC7Eunc0V3LGocQqLaYj leyla@horizon" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIILimFIW2exEH/Xo7LtXkqgE04qusvnPNpPWSCeNrFkP leyla@defiant" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKBiZkg1c2aaNHiieBX4cEziqvJVj9pcDfzUrKU/mO0I leyla@twilight" - ]; - hostKeys = [ - { - type = "ed25519"; - path = "${config.home.username}_${osConfig.networking.hostName}_ed25519"; - } - ]; - }; - }; + # TODO: move this into a fonts module + home.packages = with pkgs; [ + aileron + ]; + fonts.fontconfig.enable = true; }; } diff --git a/configurations/home-manager/leyla/impermanence.nix b/configurations/home-manager/leyla/impermanence.nix index 29936b5..8fbff41 100644 --- a/configurations/home-manager/leyla/impermanence.nix +++ b/configurations/home-manager/leyla/impermanence.nix @@ -1,24 +1,19 @@ { lib, - osConfig, + config, ... }: { - config = lib.mkIf osConfig.host.impermanence.enable { - home.persistence."/persist/home/leyla" = { + config = lib.mkIf (config.impermanence.enable) { + home.persistence."${config.impermanence.persistencePath}" = { directories = [ "desktop" "downloads" "documents" - { - directory = ".local/share/Steam"; - method = "symlink"; - } ]; files = [ ".bash_history" # keep shell history around - ".local/share/recently-used.xbel" # gnome recently viewed files + "${config.xdg.dataHome}/recently-used.xbel" # gnome recently viewed files ]; - allowOther = true; }; }; } diff --git a/configurations/home-manager/leyla/packages.nix b/configurations/home-manager/leyla/packages.nix deleted file mode 100644 index 13263ea..0000000 --- a/configurations/home-manager/leyla/packages.nix +++ /dev/null @@ -1,95 +0,0 @@ -{ - lib, - osConfig, - pkgs, - ... -}: let - userConfig = osConfig.host.users.leyla; - hardware = osConfig.host.hardware; -in { - imports = [ - ./vscode/default.nix - ./firefox.nix - ]; - - nixpkgs.config = { - allowUnfree = true; - }; - - home = { - packages = - lib.lists.optionals userConfig.isTerminalUser ( - with pkgs; [ - # command line tools - sox - yt-dlp - ffmpeg - imagemagick - ] - ) - ++ ( - lib.lists.optionals userConfig.isDesktopUser ( - (with pkgs; [ - # helvetica font - aileron - - gnomeExtensions.dash-to-dock - - # development tools - dbeaver-bin - bruno - proxmark3 - ]) - ++ ( - lib.lists.optionals hardware.directAccess.enable (with pkgs; [ - #foss platforms - signal-desktop-bin - bitwarden - ungoogled-chromium - libreoffice - inkscape - gimp - krita - freecad - # cura - # kicad-small - makemkv - onionshare - # rhythmbox - (lib.mkIf hardware.graphicsAcceleration.enable obs-studio) - # wireshark - # rpi-imager - # fritzing - mfoc - tor-browser - anki - pdfarranger - calibre - qbittorrent - picard - - # proprietary platforms - discord - obsidian - (lib.mkIf hardware.graphicsAcceleration.enable davinci-resolve) - - # development tools - # androidStudioPackages.canary - jetbrains.idea-community - qFlipper - - # system tools - protonvpn-gui - openvpn - noisetorch - - # hardware management tools - (lib.mkIf hardware.piperMouse.enable piper) - (lib.mkIf hardware.openRGB.enable openrgb) - (lib.mkIf hardware.viaKeyboard.enable via) - ]) - ) - ) - ); - }; -} diff --git a/configurations/home-manager/leyla/packages/default.nix b/configurations/home-manager/leyla/packages/default.nix new file mode 100644 index 0000000..5f64742 --- /dev/null +++ b/configurations/home-manager/leyla/packages/default.nix @@ -0,0 +1,98 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: let + hardware = osConfig.host.hardware; +in { + imports = [ + ./vscode + ./firefox + ./direnv.nix + ./openssh.nix + ./git.nix + ./makemkv.nix + ]; + + config = lib.mkMerge [ + { + programs = lib.mkMerge [ + { + # Let Home Manager install and manage itself. + home-manager.enable = true; + } + (lib.mkIf (config.user.isTerminalUser || config.user.isDesktopUser) { + bash.enable = true; + git.enable = true; + openssh.enable = true; + }) + (lib.mkIf config.user.isDesktopUser { + bitwarden.enable = true; + obs-studio.enable = hardware.graphicsAcceleration.enable; + qbittorrent.enable = true; + prostudiomasters.enable = true; + protonvpn-gui.enable = true; + 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; + opencode.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; + android-studio.enable = true; + makemkv.enable = true; + discord.enable = true; + signal-desktop.enable = true; + calibre.enable = true; + obsidian.enable = true; + jetbrains.idea-oss.enable = true; + vscode.enable = true; + firefox.enable = true; + steam.enable = true; + krita.enable = true; + ungoogled-chromium.enable = true; + libreoffice.enable = true; + mapillary-uploader.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; + noita-entangled-worlds.enable = true; + tor-browser.enable = true; + gdx-liftoff.enable = true; + proton-mail-pwa.enable = true; + proton-calendar-pwa.enable = true; + matrix-cyberia-pwa.enable = true; + kicad.enable = true; + }) + ]; + } + (lib.mkIf config.user.isTerminalUser { + home.packages = with pkgs; [ + # command line tools + sox + yt-dlp + ffmpeg + imagemagick + ]; + }) + (lib.mkIf config.user.isDesktopUser { + nixpkgs.config = { + allowUnfree = true; + }; + }) + ]; +} diff --git a/configurations/home-manager/leyla/packages/direnv.nix b/configurations/home-manager/leyla/packages/direnv.nix new file mode 100644 index 0000000..038c149 --- /dev/null +++ b/configurations/home-manager/leyla/packages/direnv.nix @@ -0,0 +1,22 @@ +{ + lib, + config, + osConfig, + ... +}: let + userConfig = osConfig.host.users.leyla; +in { + config = lib.mkIf userConfig.isDesktopUser { + programs = { + direnv = { + enable = true; + enableBashIntegration = true; + nix-direnv.enable = true; + config = { + global.hide_env_diff = true; + whitelist.exact = ["${config.home.homeDirectory}/documents/code/nix-config"]; + }; + }; + }; + }; +} diff --git a/configurations/home-manager/leyla/packages/firefox/bookmarks.nix b/configurations/home-manager/leyla/packages/firefox/bookmarks.nix new file mode 100644 index 0000000..bd172e7 --- /dev/null +++ b/configurations/home-manager/leyla/packages/firefox/bookmarks.nix @@ -0,0 +1,161 @@ +{...}: { + programs.firefox = { + profiles.leyla = { + bookmarks = { + force = true; + settings = [ + # Personal Services + { + name = "Media"; + url = "https://media.jan-leila.com/"; + keyword = ""; + tags = [""]; + } + { + name = "Photos"; + url = "https://photos.jan-leila.com"; + keyword = ""; + tags = [""]; + } + { + name = "Git"; + url = "https://git.jan-leila.com/"; + keyword = ""; + tags = [""]; + } + { + name = "Home Automation"; + url = "https://home.jan-leila.com/"; + keyword = ""; + tags = [""]; + } + { + name = "Search"; + url = "https://search.jan-leila.com/"; + keyword = ""; + tags = [""]; + } + { + name = "Budget"; + url = "https://budget.jan-leila.com/"; + keyword = ""; + tags = [""]; + } + { + name = "Documents"; + url = "https://documents.jan-leila.com/"; + keyword = ""; + tags = [""]; + } + + # Defiant Server Services + { + name = "QBittorrent"; + url = "http://defiant:8084"; + keyword = ""; + tags = ["defiant"]; + } + { + name = "Sonarr"; + url = "http://defiant:8989"; + keyword = ""; + tags = ["defiant"]; + } + { + name = "Radarr"; + url = "http://defiant:7878"; + keyword = ""; + tags = ["defiant"]; + } + { + name = "Bazarr"; + url = "http://defiant:6767"; + keyword = ""; + tags = ["defiant"]; + } + { + name = "Lidarr"; + url = "http://defiant:8686"; + keyword = ""; + tags = ["defiant"]; + } + { + name = "Jackett"; + url = "http://defiant:9117"; + keyword = ""; + tags = ["defiant"]; + } + { + name = "Crab-hole DNS"; + url = "http://defiant:8085"; + keyword = ""; + tags = ["defiant"]; + } + + # External Services + { + name = "Mail"; + url = "https://mail.protonmail.com"; + keyword = ""; + tags = [""]; + } + { + name = "Open Street Map"; + url = "https://www.openstreetmap.org/"; + keyword = ""; + tags = [""]; + } + { + name = "Password Manager"; + url = "https://vault.bitwarden.com/"; + keyword = ""; + tags = [""]; + } + { + name = "Mastodon"; + url = "https://mspsocial.net"; + keyword = ""; + tags = [""]; + } + { + name = "Linked In"; + url = "https://www.linkedin.com/"; + keyword = ""; + tags = [""]; + } + { + name = "Job Search"; + url = "https://www.jobsinnetwork.com/?state=cleaned_history&language%5B%5D=en&query=react&locations.countryCode%5B%5D=IT&locations.countryCode%5B%5D=DE&locations.countryCode%5B%5D=NL&experience%5B%5D=medior&experience%5B%5D=junior&page=1"; + keyword = ""; + tags = [""]; + } + { + name = "React Docs"; + url = "https://react.dev/"; + keyword = ""; + tags = [""]; + } + { + name = "Cyberia Matrix"; + url = "https://chat.cyberia.club"; + keyword = ""; + tags = [""]; + } + { + name = "Cyberia Git"; + url = "https://git.cyberia.club"; + keyword = ""; + tags = [""]; + } + # Template + # { + # name = ""; + # url = ""; + # keyword = ""; + # tags = [""]; + # } + ]; + }; + }; + }; +} diff --git a/configurations/home-manager/leyla/packages/firefox/default.nix b/configurations/home-manager/leyla/packages/firefox/default.nix new file mode 100644 index 0000000..4246c68 --- /dev/null +++ b/configurations/home-manager/leyla/packages/firefox/default.nix @@ -0,0 +1,18 @@ +{ + lib, + pkgs, + inputs, + ... +}: { + imports = [ + ./firefox.nix + ./bookmarks.nix + ./harden.nix + ]; + + config = { + programs.firefox = { + enable = true; + }; + }; +} diff --git a/configurations/home-manager/leyla/firefox.nix b/configurations/home-manager/leyla/packages/firefox/firefox.nix similarity index 52% rename from configurations/home-manager/leyla/firefox.nix rename to configurations/home-manager/leyla/packages/firefox/firefox.nix index 4f8c624..ef6d202 100644 --- a/configurations/home-manager/leyla/firefox.nix +++ b/configurations/home-manager/leyla/packages/firefox/firefox.nix @@ -5,7 +5,6 @@ ... }: { programs.firefox = { - enable = true; profiles.leyla = { settings = { "browser.search.defaultenginename" = "Searx"; @@ -32,7 +31,7 @@ ]; } ]; - icon = "''${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg"; + icon = "${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg"; definedAliases = ["@np"]; }; "NixOS Wiki" = { @@ -50,7 +49,7 @@ }; }; - extensions.packages = with inputs.firefox-addons.packages.${pkgs.system}; [ + extensions.packages = with inputs.firefox-addons.packages.${pkgs.stdenv.hostPlatform.system}; [ bitwarden terms-of-service-didnt-read multi-account-containers @@ -69,41 +68,13 @@ snowflake - deutsch-de-language-pack + pkgs.firefox-extensions.deutsch-de-language-pack dictionary-german - # ( - # buildFirefoxXpiAddon rec { - # pname = "italiano-it-language-pack"; - # version = "132.0.20241110.231641"; - # addonId = "langpack-it@firefox.mozilla.org"; - # url = "https://addons.mozilla.org/firefox/downloads/file/4392453/italiano_it_language_pack-${version}.xpi"; - # sha256 = ""; - # meta = with lib; - # { - # description = "Firefox Language Pack for Italiano (it) – Italian"; - # license = licenses.mpl20; - # mozPermissions = []; - # platforms = platforms.all; - # }; - # } - # ) - # ( - # buildFirefoxXpiAddon rec { - # pname = "dizionario-italiano"; - # version = "5.1"; - # addonId = "it-IT@dictionaries.addons.mozilla.org"; - # url = "https://addons.mozilla.org/firefox/downloads/file/1163874/dizionario_italiano-${version}.xpi"; - # sha256 = ""; - # meta = with lib; - # { - # description = "Add support for Italian to spellchecking"; - # license = licenses.gpl3; - # mozPermissions = []; - # platforms = platforms.all; - # }; - # } - # ) + tab-session-manager + + pkgs.firefox-extensions.italiano-it-language-pack + pkgs.firefox-extensions.dizionario-italiano ]; settings = { @@ -140,7 +111,6 @@ "placements" = { "widget-overflow-fixed-list" = []; "unified-extensions-area" = [ - "privacy_privacy_com-browser-action" # bitwarden "_446900e4-71c2-419f-a6a7-df9c091e268b_-browser-action" "ublock0_raymondhill_net-browser-action" @@ -215,127 +185,6 @@ "T9nJot5PurhJSy8n038xGA==" ] (_: 1); "identity.fxaccounts.enabled" = false; - - # Security - "privacy.trackingprotection.enabled" = true; - "dom.security.https_only_mode" = true; - - "extensions.formautofill.addresses.enabled" = false; - "extensions.formautofill.creditCards.enabled" = false; - "signon.rememberSignons" = false; - "privacy.sanitize.sanitizeOnShutdown" = true; - "privacy.clearOnShutdown_v2.cache" = true; - "privacy.clearOnShutdown_v2.cookiesAndStorage" = true; - "privacy.clearOnShutdown_v2.historyFormDataAndDownloads" = true; - "urlclassifier.trackingSkipURLs" = ""; - "urlclassifier.features.socialtracking.skipURLs" = ""; - "dom.security.https_only_mode_pbm" = true; - "dom.security.https_only_mode_error_page_user_suggestions" = true; - - # Disable telemetry - "app.shield.optoutstudies.enabled" = false; - "browser.discovery.enabled" = false; - "browser.newtabpage.activity-stream.feeds.telemetry" = false; - "browser.newtabpage.activity-stream.telemetry" = false; - "browser.ping-centre.telemetry" = false; - "datareporting.healthreport.service.enabled" = false; - "datareporting.healthreport.uploadEnabled" = false; - "datareporting.policy.dataSubmissionEnabled" = false; - "datareporting.sessions.current.clean" = true; - "devtools.onboarding.telemetry.logged" = false; - "toolkit.telemetry.archive.enabled" = false; - "toolkit.telemetry.bhrPing.enabled" = false; - "toolkit.telemetry.enabled" = false; - "toolkit.telemetry.firstShutdownPing.enabled" = false; - "toolkit.telemetry.hybridContent.enabled" = false; - "toolkit.telemetry.newProfilePing.enabled" = false; - "toolkit.telemetry.prompted" = 2; - "toolkit.telemetry.rejected" = true; - "toolkit.telemetry.reportingpolicy.firstRun" = false; - "toolkit.telemetry.server" = ""; - "toolkit.telemetry.shutdownPingSender.enabled" = false; - "toolkit.telemetry.unified" = false; - "toolkit.telemetry.unifiedIsOptIn" = false; - "toolkit.telemetry.updatePing.enabled" = false; - }; - - bookmarks = { - force = true; - settings = [ - { - name = "Media"; - url = "https://media.jan-leila.com/"; - keyword = ""; - tags = [""]; - } - { - name = "Photos"; - url = "https://photos.jan-leila.com"; - keyword = ""; - tags = [""]; - } - { - name = "Git"; - url = "https://git.jan-leila.com/"; - keyword = ""; - tags = [""]; - } - { - name = "Home Automation"; - url = "https://home.jan-leila.com/"; - keyword = ""; - tags = [""]; - } - { - name = "Mail"; - url = "https://mail.protonmail.com"; - keyword = ""; - tags = [""]; - } - { - name = "Open Street Map"; - url = "https://www.openstreetmap.org/"; - keyword = ""; - tags = [""]; - } - { - name = "Password Manager"; - url = "https://vault.bitwarden.com/"; - keyword = ""; - tags = [""]; - } - { - name = "Mastodon"; - url = "https://mspsocial.net"; - keyword = ""; - tags = [""]; - } - { - name = "Linked In"; - url = "https://www.linkedin.com/"; - keyword = ""; - tags = [""]; - } - { - name = "Job Search"; - url = "https://www.jobsinnetwork.com/?state=cleaned_history&language%5B%5D=en&query=react&locations.countryCode%5B%5D=IT&locations.countryCode%5B%5D=DE&locations.countryCode%5B%5D=NL&experience%5B%5D=medior&experience%5B%5D=junior&page=1"; - keyword = ""; - tags = [""]; - } - { - name = "React Docs"; - url = "https://react.dev/"; - keyword = ""; - tags = [""]; - } - # Template - # { - # name = ""; - # url = ""; - # keyword = ""; - # tags = [""]; - # } - ]; }; }; }; diff --git a/configurations/home-manager/leyla/packages/firefox/harden.nix b/configurations/home-manager/leyla/packages/firefox/harden.nix new file mode 100644 index 0000000..66310c2 --- /dev/null +++ b/configurations/home-manager/leyla/packages/firefox/harden.nix @@ -0,0 +1,50 @@ +{...}: { + programs.firefox = { + profiles.leyla = { + settings = { + # Security + "privacy.trackingprotection.enabled" = true; + "dom.security.https_only_mode" = true; + "dom.security.https_only_mode_pbm" = true; + "dom.security.https_only_mode_error_page_user_suggestions" = true; + + # Privacy & Data Protection + "extensions.formautofill.addresses.enabled" = false; + "extensions.formautofill.creditCards.enabled" = false; + "signon.rememberSignons" = false; + "privacy.sanitize.sanitizeOnShutdown" = true; + "privacy.clearOnShutdown_v2.cache" = true; + "privacy.clearOnShutdown_v2.cookiesAndStorage" = true; + "privacy.clearOnShutdown_v2.historyFormDataAndDownloads" = true; + "urlclassifier.trackingSkipURLs" = ""; + "urlclassifier.features.socialtracking.skipURLs" = ""; + + # Disable telemetry and data collection + "app.shield.optoutstudies.enabled" = false; + "browser.discovery.enabled" = false; + "browser.newtabpage.activity-stream.feeds.telemetry" = false; + "browser.newtabpage.activity-stream.telemetry" = false; + "browser.ping-centre.telemetry" = false; + "datareporting.healthreport.service.enabled" = false; + "datareporting.healthreport.uploadEnabled" = false; + "datareporting.policy.dataSubmissionEnabled" = false; + "datareporting.sessions.current.clean" = true; + "devtools.onboarding.telemetry.logged" = false; + "toolkit.telemetry.archive.enabled" = false; + "toolkit.telemetry.bhrPing.enabled" = false; + "toolkit.telemetry.enabled" = false; + "toolkit.telemetry.firstShutdownPing.enabled" = false; + "toolkit.telemetry.hybridContent.enabled" = false; + "toolkit.telemetry.newProfilePing.enabled" = false; + "toolkit.telemetry.prompted" = 2; + "toolkit.telemetry.rejected" = true; + "toolkit.telemetry.reportingpolicy.firstRun" = false; + "toolkit.telemetry.server" = ""; + "toolkit.telemetry.shutdownPingSender.enabled" = false; + "toolkit.telemetry.unified" = false; + "toolkit.telemetry.unifiedIsOptIn" = false; + "toolkit.telemetry.updatePing.enabled" = false; + }; + }; + }; +} diff --git a/configurations/home-manager/leyla/packages/git.nix b/configurations/home-manager/leyla/packages/git.nix new file mode 100644 index 0000000..499e37b --- /dev/null +++ b/configurations/home-manager/leyla/packages/git.nix @@ -0,0 +1,13 @@ +{...}: { + config = { + programs = { + git = { + settings = { + user.name = "Leyla Becker"; + user.email = "git@jan-leila.com"; + init.defaultBranch = "main"; + }; + }; + }; + }; +} diff --git a/configurations/home-manager/leyla/packages/makemkv.nix b/configurations/home-manager/leyla/packages/makemkv.nix new file mode 100644 index 0000000..ee71955 --- /dev/null +++ b/configurations/home-manager/leyla/packages/makemkv.nix @@ -0,0 +1,17 @@ +{ + config, + inputs, + ... +}: { + config = { + sops.secrets = { + "application-keys/makemkv" = { + sopsFile = "${inputs.secrets}/application-keys.yaml"; + }; + }; + programs.makemkv = { + appKeyFile = config.sops.placeholder."application-keys/makemkv"; + destinationDir = "/home/leyla/downloads/makemkv"; + }; + }; +} diff --git a/configurations/home-manager/leyla/packages/openssh.nix b/configurations/home-manager/leyla/packages/openssh.nix new file mode 100644 index 0000000..91aec11 --- /dev/null +++ b/configurations/home-manager/leyla/packages/openssh.nix @@ -0,0 +1,23 @@ +{ + config, + osConfig, + ... +}: { + config = { + programs = { + openssh = { + authorizedKeys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJHeItmt8TRW43uNcOC+eIurYC7Eunc0V3LGocQqLaYj leyla@horizon" + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIILimFIW2exEH/Xo7LtXkqgE04qusvnPNpPWSCeNrFkP leyla@defiant" + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKBiZkg1c2aaNHiieBX4cEziqvJVj9pcDfzUrKU/mO0I leyla@twilight" + ]; + hostKeys = [ + { + type = "ed25519"; + path = "${config.home.username}_${osConfig.networking.hostName}_ed25519"; + } + ]; + }; + }; + }; +} diff --git a/configurations/home-manager/leyla/packages/vscode/default.nix b/configurations/home-manager/leyla/packages/vscode/default.nix new file mode 100644 index 0000000..e0b2d98 --- /dev/null +++ b/configurations/home-manager/leyla/packages/vscode/default.nix @@ -0,0 +1,142 @@ +{ + lib, + pkgs, + config, + osConfig, + ... +}: let + nix-development-enabled = osConfig.host.nix-development.enable; + ai-tooling-enabled = osConfig.host.ai.enable; +in { + imports = [ + ./user-words.nix + ]; + + config = lib.mkIf config.user.isDesktopUser { + programs = { + bash.shellAliases = { + code = "codium"; + }; + + vscode = { + package = pkgs.vscodium; + + mutableExtensionsDir = false; + + profiles.default = { + enableUpdateCheck = false; + enableExtensionUpdateCheck = false; + + userSettings = lib.mkMerge [ + { + "javascript.updateImportsOnFileMove.enabled" = "always"; + "editor.tabSize" = 2; + "editor.insertSpaces" = false; + # "terminal.integrated.fontFamily" = "'Droid Sans Mono', 'monospace', monospace"; + } + ]; + + extraExtensions = { + # vs code feel + oneDark.enable = true; + atomKeybindings.enable = true; + openRemoteSsh.enable = true; + # openDyslexicFont.enable = false; + + # html development + autoRenameTag.enable = true; + liveServer.enable = true; + + # js development + es7ReactJsSnippets.enable = true; + tauriVscode.enable = true; + vscodeEslint.enable = true; + vscodeJest.enable = true; + vitest.enable = true; + vscodeStandard.enable = true; + vscodeStylelint.enable = true; + + nearley.enable = true; + + # graphql + graphql.enable = true; + + # astro development + vscodeMdx.enable = true; + astroVscode.enable = true; + + # nix development + alejandra.enable = nix-development-enabled; + nixIde.enable = nix-development-enabled; + + # go development + go.enable = true; + + # rust development + rustAnalyzer.enable = true; + + # arduino development + platformIO.enable = false; + + # claude development + claudeDev = lib.mkIf ai-tooling-enabled { + enable = false; + mcp = { + 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 = { + lint-files = true; + }; + }; + vitest = { + enable = true; + autoApprove = { + list_tests = true; + run_tests = true; + analyze_coverage = true; + set_project_root = true; + }; + }; + sleep = { + enable = true; + timeout = 18000; # 5 hours to match claude codes timeout + autoApprove = { + sleep = true; + }; + }; + }; + }; + + # misc extensions + evenBetterToml.enable = true; + direnv.enable = config.programs.direnv.enable; + conventionalCommits.enable = true; + }; + + extensions = let + extension-pkgs = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + in ( + with extension-pkgs.open-vsx; [ + # vs code feel extensions + streetsidesoftware.code-spell-checker + streetsidesoftware.code-spell-checker-german + streetsidesoftware.code-spell-checker-italian + ] + ); + }; + }; + }; + }; +} diff --git a/configurations/home-manager/leyla/packages/vscode/user-words.nix b/configurations/home-manager/leyla/packages/vscode/user-words.nix new file mode 100644 index 0000000..112269e --- /dev/null +++ b/configurations/home-manager/leyla/packages/vscode/user-words.nix @@ -0,0 +1,127 @@ +{ + pkgs, + lib, + ... +}: { + config.programs.vscode.profiles.default.userSettings = { + "cSpell.userWords" = [ + "leyla" + "Cyberia" + ]; + + "cSpell.languageSettings" = [ + { + "languageId" = "nix"; + "locale" = "*"; + "dictionaries" = [ + "applications" + "ai-words" + "nix-words" + + # We need to include all other dictionaries in the nix language settings because they exist in this file + # TODO: see if there is a way to make this only apply for this file + "js-words" + ]; + } + { + "languageId" = "javascript,typescript,js,ts"; + "locale" = "*"; + "dictionaries" = [ + "js-words" + ]; + } + ]; + + "cSpell.customDictionaries" = { + applications = { + name = "applications"; + description = "application names"; + path = pkgs.writeText "applications.txt" (lib.strings.concatLines [ + "ollama" + "syncthing" + "immich" + "sonos" + "makemkv" + "hass" + "qbittorent" + "prostudiomasters" + "protonmail" + "pulseaudio" + ]); + }; + + ai-words = { + name = "ai-words"; + description = "common words used for ai development"; + path = pkgs.writeText "ai-words.txt" (lib.strings.concatLines [ + "ollama" + "deepseek" + "qwen" + ]); + }; + + nix-words = { + name = "nix-words"; + description = "words used in nix configurations"; + path = pkgs.writeText "nix-words.txt" (lib.strings.concatLines [ + "pname" + "direnv" + "tmpfiles" + "Networkd" + "networkmanager" + "dialout" + "adbusers" + "authkey" + "netdevs" + "atomix" + "geary" + "gedit" + "hitori" + "iagno" + "alsa" + "timezoned" + "pipewire" + "rtkit" + "disko" + "ashift" + "autotrim" + "canmount" + "mountpoint" + "xattr" + "acltype" + "relatime" + "keyformat" + "keylocation" + "vdevs" + + # codium extensions + "akamud" + "onedark" + "jeanp" + "dsznajder" + "dbaeumer" + "orta" + "tauri" + "unifiedjs" + "tamasfe" + "pinage" + "jnoortheen" + "kamadorueda" + "karyfoundation" + "nearley" + + # nix.optimise is spelled wrong + "optimise" + ]); + }; + + js-words = { + name = "js-words"; + description = "words used in js development"; + path = pkgs.writeText "js-words.txt" (lib.strings.concatLines [ + "webdav" + ]); + }; + }; + }; +} diff --git a/configurations/home-manager/leyla/vscode/default.nix b/configurations/home-manager/leyla/vscode/default.nix deleted file mode 100644 index 2f3c455..0000000 --- a/configurations/home-manager/leyla/vscode/default.nix +++ /dev/null @@ -1,118 +0,0 @@ -{ - lib, - pkgs, - inputs, - config, - osConfig, - ... -}: let - nix-development-enabled = osConfig.host.nix-development.enable; - ai-tooling-enabled = config.user.continue.enable && osConfig.host.ai.enable; -in { - nixpkgs = { - overlays = [ - inputs.nix-vscode-extensions.overlays.default - ]; - }; - - programs = { - bash.shellAliases = { - code = "codium"; - }; - - vscode = let - extensions = inputs.nix-vscode-extensions.extensions.${pkgs.system}; - open-vsx = extensions.open-vsx; - vscode-marketplace = extensions.vscode-marketplace; - in { - enable = true; - - package = pkgs.vscodium; - - mutableExtensionsDir = false; - - profiles.default = { - enableUpdateCheck = false; - enableExtensionUpdateCheck = false; - - userSettings = lib.mkMerge [ - { - "workbench.colorTheme" = "Atom One Dark"; - "cSpell.userWords" = import ./user-words.nix; - "javascript.updateImportsOnFileMove.enabled" = "always"; - "editor.tabSize" = 2; - "editor.insertSpaces" = false; - } - (lib.mkIf nix-development-enabled { - "nix.enableLanguageServer" = true; - "nix.serverPath" = "nil"; - "[nix]" = { - "editor.defaultFormatter" = "kamadorueda.alejandra"; - "editor.formatOnPaste" = true; - "editor.formatOnSave" = true; - "editor.formatOnType" = true; - }; - "alejandra.program" = "alejandra"; - "nixpkgs" = { - "expr" = "import {}"; - }; - }) - (lib.mkIf ai-tooling-enabled { - "continue.telemetryEnabled" = false; - }) - ]; - - extensions = ( - with open-vsx; - [ - # vs code feel extensions - ms-vscode.atom-keybindings - akamud.vscode-theme-onedark - streetsidesoftware.code-spell-checker - streetsidesoftware.code-spell-checker-german - streetsidesoftware.code-spell-checker-italian - jeanp413.open-remote-ssh - - # html extensions - formulahendry.auto-rename-tag - ms-vscode.live-server - - # js extensions - dsznajder.es7-react-js-snippets - dbaeumer.vscode-eslint - standard.vscode-standard - firsttris.vscode-jest-runner - stylelint.vscode-stylelint - tauri-apps.tauri-vscode - - # go extensions - golang.go - - # astro blog extensions - astro-build.astro-vscode - unifiedjs.vscode-mdx - - # misc extensions - tamasfe.even-better-toml - ] - ++ (lib.lists.optionals nix-development-enabled [ - # nix extensions - pinage404.nix-extension-pack - jnoortheen.nix-ide - kamadorueda.alejandra - ]) - ++ ( - with vscode-marketplace; - [ - # js extensions - karyfoundation.nearley - ] - ++ (lib.lists.optionals ai-tooling-enabled [ - continue.continue - ]) - ) - ); - }; - }; - }; -} diff --git a/configurations/home-manager/leyla/vscode/user-words.nix b/configurations/home-manager/leyla/vscode/user-words.nix deleted file mode 100644 index b581118..0000000 --- a/configurations/home-manager/leyla/vscode/user-words.nix +++ /dev/null @@ -1,6 +0,0 @@ -[ - "leyla" - "webdav" - "ollama" - "optimise" -] diff --git a/configurations/nixos/defiant/configuration.nix b/configurations/nixos/defiant/configuration.nix index fef7a56..40adbd5 100644 --- a/configurations/nixos/defiant/configuration.nix +++ b/configurations/nixos/defiant/configuration.nix @@ -17,6 +17,12 @@ "services/zfs_smtp_token" = { sopsFile = "${inputs.secrets}/defiant-services.yaml"; }; + "services/paperless_password" = { + sopsFile = "${inputs.secrets}/defiant-services.yaml"; + mode = "0700"; + owner = "paperless"; + group = "paperless"; + }; }; host = { @@ -27,43 +33,6 @@ isPrincipleUser = true; }; }; - impermanence.enable = true; - storage = { - enable = true; - encryption = true; - notifications = { - enable = true; - host = "smtp.protonmail.ch"; - port = 587; - to = "leyla@jan-leila.com"; - user = "leyla@jan-leila.com"; - tokenFile = config.sops.secrets."services/zfs_smtp_token".path; - }; - pool = { - vdevs = [ - [ - "ata-ST18000NE000-3G6101_ZVTCXVEB" - "ata-ST18000NE000-3G6101_ZVTCXWSC" - "ata-ST18000NE000-3G6101_ZVTD10EH" - "ata-ST18000NT001-3NF101_ZVTE0S3Q" - "ata-ST18000NT001-3NF101_ZVTEF27J" - "ata-ST18000NE000-3G6101_ZVTJ7359" - ] - # TODO: this needs to be configured manually - [ - "ata-ST4000NE001-2MA101_WS2275P3" - "ata-ST4000NE001-2MA101_WS227B9F" - "ata-ST4000NE001-2MA101_WS227CEW" - "ata-ST4000NE001-2MA101_WS227CYN" - "ata-ST4000NE001-2MA101_WS23TBWV" - "ata-ST4000NE001-2MA101_WS23TC5F" - ] - ]; - cache = [ - "nvme-Samsung_SSD_990_PRO_4TB_S7KGNU0X907881F" - ]; - }; - }; network_storage = { enable = true; directories = [ @@ -95,36 +64,58 @@ directories = ["leyla_documents" "eve_documents" "users_documents" "media"]; }; }; - reverse_proxy = { + }; + + storage = { + zfs = { enable = true; - enableACME = true; - hostname = "jan-leila.com"; - }; - postgres = { - extraUsers = { - leyla = { - isAdmin = true; + notifications = { + enable = true; + host = "smtp.protonmail.ch"; + port = 587; + to = "leyla@jan-leila.com"; + user = "noreply@jan-leila.com"; + tokenFile = config.sops.secrets."services/zfs_smtp_token".path; + }; + pool = { + encryption = { + enable = true; }; + vdevs = [ + [ + "ata-ST18000NE000-3G6101_ZVTCXVEB" + "ata-ST18000NE000-3G6101_ZVTCXWSC" + "ata-ST18000NE000-3G6101_ZVTD10EH" + "ata-ST18000NT001-3NF101_ZVTE0S3Q" + "ata-ST18000NT001-3NF101_ZVTEF27J" + "ata-ST18000NE000-3G6101_ZVTJ7359" + ] + [ + "ata-ST4000NE001-2MA101_WS2275P3" + "ata-ST4000NE001-2MA101_WS227B9F" + "ata-ST4000NE001-2MA101_WS227CEW" + "ata-ST4000NE001-2MA101_WS227CYN" + "ata-ST4000NE001-2MA101_WS23TBWV" + "ata-ST4000NE001-2MA101_WS23TC5F" + ] + ]; + # We are having to boot off of the nvm cache drive because I cant figure out how to boot via the HBA + cache = [ + { + device = "nvme-Samsung_SSD_990_PRO_4TB_S7KGNU0X907881F"; + boot = true; + } + ]; }; }; - # home-assistant = { - # enable = false; - # subdomain = "home"; - # }; - adguardhome = { - enable = false; + impermanence = { + enable = true; }; }; systemd.network = { enable = true; - # config = { - # routeTables = { - # p2p = 1; - # }; - # }; - netdevs = { "10-bond0" = { netdevConfig = { @@ -137,26 +128,25 @@ }; }; - # "15-p2p0" = { - # netdevConfig = { - # Kind = "wireguard"; - # Name = "p2p0"; - # MTUBytes = "1280"; - # }; - # wireguardConfig = { - # PrivateKeyFile = config.sops.secrets."vpn-keys/proton-wireguard/defiant-p2p".path; - # ListenPort = 51820; - # # RouteTable = "p2p"; - # }; - # wireguardPeers = [ - # { - # PublicKey = "rRO6yJim++Ezz6scCLMaizI+taDjU1pzR2nfW6qKbW0="; - # Endpoint = "185.230.126.146:51820"; - # AllowedIPs = ["0.0.0.0/0"]; - # RouteTable = "off"; - # } - # ]; - # }; + "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"]; + PersistentKeepalive = 25; + } + ]; + }; }; networks = { "40-bond0" = { @@ -171,49 +161,106 @@ "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"]; }; - # "45-p2p0" = { - # matchConfig.Name = "p2p0"; - # address = [ - # "10.2.0.2/32" - # ]; - # routes = [ - # { - # Destination = "0.0.0.0/0"; - # } - # ]; - # linkConfig.RequiredForOnline = false; - # }; + "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 = { - # TODO: move zfs scrubbing into module - zfs = { - autoScrub.enable = true; - autoSnapshot.enable = true; + # PostgreSQL database server + postgresql = { + enable = true; + adminUsers = ["leyla"]; + impermanence.enable = false; }; - # temp enable desktop enviroment for setup + # temp enable desktop environment for setup # Enable the X11 windowing system. - xserver = { - enable = true; + xserver.enable = true; - # Enable the GNOME Desktop Environment. - displayManager = { - gdm.enable = true; - }; - desktopManager = { - gnome.enable = true; + # Enable the GNOME Desktop Environment. + displayManager = { + gdm.enable = true; + }; + desktopManager = { + gnome.enable = true; + }; + + # Enable new reverse proxy system + reverseProxy = { + enable = true; + openFirewall = true; + impermanence.enable = false; + acme = { + enable = true; + email = "jan-leila@protonmail.com"; }; }; ollama = { enable = true; exposePort = true; + impermanence.enable = false; + + environmentVariables = { + OLLAMA_KEEP_ALIVE = "24h"; + }; loadModels = [ # conversation models @@ -231,6 +278,10 @@ # agent models "qwen3:8b" "qwen3:32b" + "qwen3:235b-a22b" + + "qwen3-coder:30b" + "qwen3-coder:30b-a3b-fp16" # embedding models "nomic-embed-text:latest" @@ -240,6 +291,7 @@ enable = true; authKeyFile = config.sops.secrets."vpn-keys/tailscale-authkey/defiant".path; useRoutingFeatures = "server"; + impermanence.enable = false; extraUpFlags = [ "--advertise-exit-node" "--advertise-routes=192.168.0.0/24" @@ -252,42 +304,126 @@ ]; }; - syncthing.enable = true; + syncthing = { + enable = true; + impermanence.enable = false; + }; - fail2ban.enable = true; + fail2ban = { + enable = true; + impermanence.enable = false; + }; jellyfin = { enable = true; - subdomain = "media"; - extraSubdomains = ["jellyfin"]; + domain = "media.jan-leila.com"; + extraDomains = ["jellyfin.jan-leila.com"]; + impermanence.enable = false; }; immich = { enable = true; - subdomain = "photos"; + domain = "photos.jan-leila.com"; + impermanence.enable = false; }; forgejo = { enable = true; - subdomain = "git"; + reverseProxy.domain = "git.jan-leila.com"; + impermanence.enable = false; }; searx = { enable = true; - subdomain = "search"; + domain = "search.jan-leila.com"; }; - virt-home-assistant = { + actual = { enable = false; - networkBridge = "bond0"; - hostDevice = "0x10c4:0xea60"; + domain = "budget.jan-leila.com"; + impermanence.enable = false; + }; + + home-assistant = { + enable = true; + domain = "home.jan-leila.com"; + openFirewall = true; + postgres.enable = true; + impermanence.enable = false; + + extensions = { + sonos.enable = true; + jellyfin.enable = true; + wyoming.enable = false; # Temporarily disabled due to dependency conflict in wyoming-piper + }; + }; + + paperless = { + enable = true; + domain = "documents.jan-leila.com"; + passwordFile = config.sops.secrets."services/paperless_password".path; + impermanence.enable = false; + }; + + panoramax = { + enable = false; + openFirewall = true; + impermanence.enable = false; + }; + + crab-hole = { + enable = true; + port = 8085; + openFirewall = true; + show_doc = true; + impermanence.enable = false; + downstreams = { + host = { + enable = true; + openFirewall = true; + }; + }; + upstreams.cloudFlare.enable = true; + blocklists.ad_malware.enable = true; }; qbittorrent = { enable = true; mediaDir = "/srv/qbittorent"; openFirewall = true; - webPort = 8084; + webuiPort = 8084; + impermanence.enable = false; + }; + + sonarr = { + enable = true; + openFirewall = true; + impermanence.enable = false; + }; + radarr = { + enable = true; + openFirewall = true; + impermanence.enable = false; + }; + bazarr = { + enable = true; + openFirewall = true; + impermanence.enable = false; + }; + lidarr = { + enable = true; + openFirewall = true; + impermanence.enable = false; + }; + jackett = { + enable = true; + openFirewall = true; + impermanence.enable = false; + }; + flaresolverr = { + enable = true; + openFirewall = true; + impermanence.enable = false; }; }; @@ -298,7 +434,7 @@ hibernate.enable = false; hybrid-sleep.enable = false; }; - services.xserver.displayManager.gdm.autoSuspend = false; + services.displayManager.gdm.autoSuspend = false; # This value determines the NixOS release from which the default # settings for stateful data, like file locations and database versions diff --git a/configurations/nixos/defiant/default.nix b/configurations/nixos/defiant/default.nix index fe850af..dd2383f 100644 --- a/configurations/nixos/defiant/default.nix +++ b/configurations/nixos/defiant/default.nix @@ -3,5 +3,8 @@ imports = [ ./hardware-configuration.nix ./configuration.nix + ./packages.nix + ./legacy-storage.nix + ./legacy-impermanence.nix ]; } diff --git a/configurations/nixos/defiant/legacy-impermanence.nix b/configurations/nixos/defiant/legacy-impermanence.nix new file mode 100644 index 0000000..4cfe18b --- /dev/null +++ b/configurations/nixos/defiant/legacy-impermanence.nix @@ -0,0 +1,296 @@ +# Legacy impermanence module for defiant +# See legacy-storage.nix for the full incremental migration plan. +# +# This file is consumed in two phases: +# +# Phase 3 (after generateBase is enabled): +# Remove the SYSTEM-LEVEL entries marked [PHASE 3] below. These will be +# handled automatically by storage.nix, ssh.nix, and the impermanence module: +# - var-lib-private-permissions activation script +# - /etc/machine-id +# - SSH host keys +# - /var/lib/nixos +# - /var/lib/systemd/coredump +# - /persist/system/var/log persistence block +# +# Phase 4 (migrate services one at a time, any order): +# For each service: +# 1. Remove the service's section marked [PHASE 4] from this file +# 2. Remove `impermanence.enable = false` for that service in configuration.nix +# For jellyfin/qbittorrent, also remove the separate media persistence blocks. +# +# Phase 5: Delete this file once empty. +{ + config, + lib, + ... +}: { + config = lib.mkIf config.storage.impermanence.enable { + # [PHASE 3] Remove this activation script after enabling generateBase + system.activationScripts = { + "var-lib-private-permissions" = { + deps = ["specialfs"]; + text = '' + mkdir -p /persist/system/root/var/lib/private + chmod 0700 /persist/system/root/var/lib/private + ''; + }; + }; + + environment.persistence."/persist/system/root" = { + enable = true; + hideMounts = true; + # [PHASE 3] Remove this files block after enabling generateBase + files = lib.mkMerge [ + ["/etc/machine-id"] + # SSH host keys + (lib.mkIf config.services.openssh.enable ( + lib.lists.flatten ( + builtins.map (hostKey: [ + hostKey.path + "${hostKey.path}.pub" + ]) + config.services.openssh.hostKeys + ) + )) + ]; + directories = lib.mkMerge [ + # [PHASE 3] Remove these system directories after enabling generateBase + [ + "/var/lib/nixos" + "/var/lib/systemd/coredump" + ] + + # [PHASE 4] PostgreSQL + (lib.mkIf config.services.postgresql.enable [ + { + directory = "/var/lib/postgresql/16"; + user = "postgres"; + group = "postgres"; + } + ]) + + # [PHASE 4] Reverse Proxy (ACME) + (lib.mkIf config.services.reverseProxy.enable [ + { + directory = "/var/lib/acme"; + user = "acme"; + group = "acme"; + } + ]) + + # [PHASE 4] Ollama + (lib.mkIf config.services.ollama.enable [ + { + directory = "/var/lib/private/ollama"; + user = config.services.ollama.user; + group = config.services.ollama.group; + mode = "0700"; + } + ]) + + # [PHASE 4] Tailscale + (lib.mkIf config.services.tailscale.enable [ + { + directory = "/var/lib/tailscale"; + user = "root"; + group = "root"; + } + ]) + + # [PHASE 4] Syncthing + (lib.mkIf config.services.syncthing.enable [ + { + directory = "/mnt/sync"; + user = "syncthing"; + group = "syncthing"; + } + { + directory = "/etc/syncthing"; + user = "syncthing"; + group = "syncthing"; + } + ]) + + # [PHASE 4] Fail2ban + (lib.mkIf config.services.fail2ban.enable [ + { + directory = "/var/lib/fail2ban"; + user = "fail2ban"; + group = "fail2ban"; + } + ]) + + # [PHASE 4] Jellyfin (data/cache only - media is on separate dataset) + (lib.mkIf config.services.jellyfin.enable [ + { + directory = "/var/lib/jellyfin"; + user = "jellyfin"; + group = "jellyfin"; + } + { + directory = "/var/cache/jellyfin"; + user = "jellyfin"; + group = "jellyfin"; + } + ]) + + # [PHASE 4] Immich + (lib.mkIf config.services.immich.enable [ + { + directory = "/var/lib/immich"; + user = "immich"; + group = "immich"; + } + ]) + + # [PHASE 4] Forgejo + (lib.mkIf config.services.forgejo.enable [ + { + directory = "/var/lib/forgejo"; + user = "forgejo"; + group = "forgejo"; + } + ]) + + # [PHASE 4] Actual + (lib.mkIf config.services.actual.enable [ + { + directory = "/var/lib/private/actual"; + user = "actual"; + group = "actual"; + } + ]) + + # [PHASE 4] Home Assistant + (lib.mkIf config.services.home-assistant.enable [ + { + directory = "/var/lib/hass"; + user = "hass"; + group = "hass"; + } + ]) + + # [PHASE 4] Paperless + (lib.mkIf config.services.paperless.enable [ + { + directory = "/var/lib/paperless"; + user = "paperless"; + group = "paperless"; + } + ]) + + # [PHASE 4] Crab-hole + (lib.mkIf config.services.crab-hole.enable [ + { + directory = "/var/lib/private/crab-hole"; + user = "crab-hole"; + group = "crab-hole"; + } + ]) + + # [PHASE 4] qBittorrent (config only - media is on separate dataset) + (lib.mkIf config.services.qbittorrent.enable [ + { + directory = "/var/lib/qBittorrent/"; + user = "qbittorrent"; + group = "qbittorrent"; + } + ]) + + # [PHASE 4] Sonarr + (lib.mkIf config.services.sonarr.enable [ + { + directory = "/var/lib/sonarr/.config/NzbDrone"; + user = "sonarr"; + group = "sonarr"; + } + ]) + + # [PHASE 4] Radarr + (lib.mkIf config.services.radarr.enable [ + { + directory = "/var/lib/radarr/.config/Radarr"; + user = "radarr"; + group = "radarr"; + } + ]) + + # [PHASE 4] Bazarr + (lib.mkIf config.services.bazarr.enable [ + { + directory = "/var/lib/bazarr"; + user = "bazarr"; + group = "bazarr"; + } + ]) + + # [PHASE 4] Lidarr + (lib.mkIf config.services.lidarr.enable [ + { + directory = "/var/lib/lidarr/.config/Lidarr"; + user = "lidarr"; + group = "lidarr"; + } + ]) + + # [PHASE 4] Jackett + (lib.mkIf config.services.jackett.enable [ + { + directory = "/var/lib/jackett/.config/Jackett"; + user = "jackett"; + group = "jackett"; + } + ]) + + # [PHASE 4] FlareSolverr + (lib.mkIf config.services.flaresolverr.enable [ + { + directory = "/var/lib/flaresolverr"; + user = "flaresolverr"; + group = "flaresolverr"; + } + ]) + ]; + }; + + # [PHASE 4 - LAST] Jellyfin media on separate dataset + # Requires Phase 2 media dataset merge before migrating (several days of data copy) + environment.persistence."/persist/system/jellyfin" = lib.mkIf config.services.jellyfin.enable { + enable = true; + hideMounts = true; + directories = [ + { + directory = config.services.jellyfin.media_directory; + user = "jellyfin"; + group = "jellyfin_media"; + mode = "1770"; + } + ]; + }; + + # [PHASE 4 - LAST] qBittorrent media on separate dataset + # Requires Phase 2 media dataset merge before migrating (several days of data copy) + environment.persistence."/persist/system/qbittorrent" = lib.mkIf config.services.qbittorrent.enable { + enable = true; + hideMounts = true; + directories = [ + { + directory = config.services.qbittorrent.mediaDir; + user = "qbittorrent"; + group = "qbittorrent"; + mode = "1775"; + } + ]; + }; + + # [PHASE 3] /var/log persistence - handled by storage.nix after generateBase + environment.persistence."/persist/system/var/log" = { + enable = true; + hideMounts = true; + directories = [ + "/var/log" + ]; + }; + }; +} diff --git a/configurations/nixos/defiant/legacy-storage.nix b/configurations/nixos/defiant/legacy-storage.nix new file mode 100644 index 0000000..9ab79a6 --- /dev/null +++ b/configurations/nixos/defiant/legacy-storage.nix @@ -0,0 +1,218 @@ +# Legacy storage configuration for defiant +# This file manually defines ZFS datasets matching the existing on-disk layout +# to allow incremental migration to the new storage module (generateBase = true). +# +# ============================================================================ +# INCREMENTAL MIGRATION PLAN +# ============================================================================ +# +# Current disk usage (for reference): +# rpool/local/system/nix ~26G (renamed in place, no copy) +# rpool/local/system/sops ~328K (renamed in place, no copy) +# rpool/persist/system/jellyfin ~32T (renamed in place, no copy) +# rpool/persist/system/qbittorrent ~6.5T (copied into media dataset, ~6.5T temp) +# rpool free space ~30T +# +# Phase 1: Migrate base datasets on disk (boot from live USB or rescue) +# All operations in this phase are instant renames -- no data is copied. +# +# Unlock the pool: +# zfs load-key -a +# +# Step 1a: Move nix and sops out of local/ (they go to persist/local/) +# The -p flag auto-creates the parent datasets. +# +# zfs rename -p rpool/local/system/nix rpool/persist/local/nix +# zfs rename -p rpool/local/system/sops rpool/persist/local/system/sops +# +# Step 1b: Rename local/ -> ephemeral/ (takes remaining children with it) +# zfs rename rpool/local rpool/ephemeral +# # This moves: local/system/root -> ephemeral/system/root +# # local/home/leyla -> ephemeral/home/leyla +# +# Step 1c: Recreate blank snapshots on ephemeral datasets +# zfs destroy rpool/ephemeral/system/root@blank +# zfs snapshot rpool/ephemeral/system/root@blank +# zfs destroy rpool/ephemeral/home/leyla@blank +# zfs snapshot rpool/ephemeral/home/leyla@blank +# +# Step 1d: Move persist/ children under persist/replicate/ +# zfs create -o canmount=off rpool/persist/replicate +# zfs create -o canmount=off rpool/persist/replicate/system +# zfs rename rpool/persist/system/root rpool/persist/replicate/system/root +# zfs rename rpool/persist/system/var rpool/persist/replicate/system/var +# zfs rename rpool/persist/home/leyla rpool/persist/replicate/home +# # Clean up the now-empty home parent +# zfs destroy rpool/persist/home +# # NOTE: Do NOT destroy rpool/persist/system -- it still contains +# # persist/system/jellyfin and persist/system/qbittorrent which are +# # migrated in Phase 2. +# +# Verify the new layout: +# zfs list -r rpool -o name,used,mountpoint +# +# Phase 2: Merge media into a single dataset (do this last) +# Strategy: Rename the jellyfin dataset to become the shared media dataset +# (zero copy, instant), then copy qbittorrent data into it (~6.5T copy). +# This avoids duplicating the 32T jellyfin dataset. +# +# Step 2a: Rename jellyfin dataset to the shared media name +# zfs rename rpool/persist/system/jellyfin rpool/persist/replicate/system/media +# +# Step 2b: Copy qbittorrent data into the media dataset +# This copies ~6.5T and may take several hours/days depending on disk speed. +# The qbittorrent data is not critical to back up so no snapshot needed. +# +# systemctl stop qbittorrent +# rsync -avPHAX /persist/system/qbittorrent/ /persist/replicate/system/media/ +# +# Step 2c: Verify the data and clean up +# ls -la /persist/replicate/system/media/ +# zfs destroy rpool/persist/system/qbittorrent +# # persist/system should now be empty, clean it up: +# zfs destroy rpool/persist/system +# +# Phase 3: Enable generateBase +# In the nix config: +# - Delete this file (legacy-storage.nix) and remove its import from default.nix +# - Remove [PHASE 3] entries from legacy-impermanence.nix: +# - var-lib-private-permissions activation script +# - /etc/machine-id, SSH host keys (files block) +# - /var/lib/nixos, /var/lib/systemd/coredump (directories) +# - /persist/system/var/log persistence block +# These are now handled automatically by storage.nix and ssh.nix. +# Rebuild and verify: +# sudo nixos-rebuild switch --flake .#defiant +# # Verify mounts: findmnt -t fuse.bindfs,fuse +# # Verify persist: ls /persist/replicate/system/root/var/lib/nixos +# # Verify boot: reboot and confirm system comes up cleanly +# +# Phase 4: Migrate services (one at a time, any order) +# For each service (except jellyfin/qbittorrent): +# 1. Remove the service's [PHASE 4] section from legacy-impermanence.nix +# 2. Remove `impermanence.enable = false` for that service in configuration.nix +# 3. Rebuild: sudo nixos-rebuild switch --flake .#defiant +# 4. Verify: systemctl status , check the service's data is intact +# No data migration is needed -- the data already lives on the renamed +# dataset at the new path. +# +# Migrate jellyfin and qbittorrent LAST (after Phase 2 media merge): +# 1. Remove [PHASE 4 - LAST] jellyfin entries from legacy-impermanence.nix +# 2. Remove [PHASE 4 - LAST] qbittorrent entries from legacy-impermanence.nix +# 3. Remove `impermanence.enable = false` for both in configuration.nix +# 4. Rebuild: sudo nixos-rebuild switch --flake .#defiant +# 5. Verify: systemctl status jellyfin qbittorrent +# +# Phase 5: Cleanup +# Once all services are migrated and legacy-impermanence.nix is empty: +# - Delete legacy-impermanence.nix and remove its import from default.nix +# - Rebuild: sudo nixos-rebuild switch --flake .#defiant +# +# ============================================================================ +# +# Current on-disk dataset layout: +# rpool/local/ - ephemeral parent +# rpool/local/home/leyla - ephemeral user home (rolled back on boot) +# rpool/local/system/nix - nix store +# rpool/local/system/root - root filesystem (rolled back on boot) +# rpool/local/system/sops - sops age key +# rpool/persist/ - persistent parent +# rpool/persist/home/leyla - persistent user home +# rpool/persist/system/jellyfin - jellyfin media +# rpool/persist/system/qbittorrent - qbittorrent media +# rpool/persist/system/root - persistent root data +# rpool/persist/system/var/log - log persistence +{lib, ...}: { + # Disable automatic base dataset generation so we can define them manually + storage.generateBase = false; + + # Manually define ZFS datasets matching main's structure + storage.zfs.datasets = { + # Ephemeral datasets (local/) + "local" = { + type = "zfs_fs"; + mount = null; + }; + "local/home/leyla" = { + type = "zfs_fs"; + mount = "/home/leyla"; + snapshot = { + blankSnapshot = true; + }; + }; + "local/system/nix" = { + type = "zfs_fs"; + mount = "/nix"; + atime = "off"; + relatime = "off"; + snapshot = { + autoSnapshot = false; + }; + }; + "local/system/root" = { + type = "zfs_fs"; + mount = "/"; + snapshot = { + blankSnapshot = true; + }; + }; + "local/system/sops" = { + type = "zfs_fs"; + mount = "/var/lib/sops-nix"; + }; + + # Persistent datasets (persist/) + "persist" = { + type = "zfs_fs"; + mount = null; + }; + "persist/home/leyla" = { + type = "zfs_fs"; + mount = "/persist/home/leyla"; + snapshot = { + autoSnapshot = true; + }; + }; + "persist/system/jellyfin" = { + type = "zfs_fs"; + mount = "/persist/system/jellyfin"; + atime = "off"; + relatime = "off"; + }; + "persist/system/qbittorrent" = { + type = "zfs_fs"; + mount = "/persist/system/qbittorrent"; + atime = "off"; + relatime = "off"; + }; + "persist/system/root" = { + type = "zfs_fs"; + mount = "/persist/system/root"; + snapshot = { + autoSnapshot = true; + }; + }; + "persist/system/var/log" = { + type = "zfs_fs"; + mount = "/persist/system/var/log"; + }; + }; + + # Boot commands to rollback ephemeral root and user homes on boot + boot.initrd.postResumeCommands = lib.mkAfter '' + zfs rollback -r rpool/local/system/root@blank + zfs rollback -r rpool/local/home/leyla@blank + ''; + + # FileSystems needed for boot + fileSystems = { + "/".neededForBoot = true; + "/persist/system/root".neededForBoot = true; + "/persist/system/var/log".neededForBoot = true; + "/persist/system/jellyfin".neededForBoot = true; + "/persist/system/qbittorrent".neededForBoot = true; + "/var/lib/sops-nix".neededForBoot = true; + "/persist/home/leyla".neededForBoot = true; + "/home/leyla".neededForBoot = true; + }; +} 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 + ]; +} diff --git a/configurations/nixos/emergent/configuration.nix b/configurations/nixos/emergent/configuration.nix index a880ef5..35ef445 100644 --- a/configurations/nixos/emergent/configuration.nix +++ b/configurations/nixos/emergent/configuration.nix @@ -2,12 +2,12 @@ # your system. Help is available in the configuration.nix(5) man page, on # https://search.nixos.org/options and in the NixOS manual (`nixos-help`). { - config, lib, pkgs, ... }: { imports = [ + ./nvidia-drivers.nix ]; # Use the systemd-boot EFI boot loader. @@ -36,12 +36,19 @@ # Enable the X11 windowing system. services.xserver.enable = true; + # Enable wacom touchscreen device + services.xserver.wacom.enable = true; + + # installed opentabletdriver + # hardware.opentabletdriver.enable = true; + hardware.keyboard.qmk.enable = true; # Enable the GNOME Desktop Environment. - services.xserver.displayManager.gdm.enable = true; - services.xserver.desktopManager.gnome.enable = true; + services.displayManager.gdm.enable = true; + services.desktopManager.gnome.enable = true; host = { + ai.enable = true; users = { eve = { isDesktopUser = true; @@ -49,8 +56,40 @@ isPrincipleUser = true; }; }; + hardware = { + piperMouse.enable = true; + }; }; + storage = { + zfs = { + enable = true; + pool = { + mode = "stripe"; + vdevs = [ + [ + { + device = "wwn-0x5000039fd0cf05eb"; + boot = true; + } + ] + ]; + cache = []; + }; + }; + }; + + virtualisation.libvirtd.enable = true; + + users.users.eve = { + extraGroups = ["libvirtd"]; + }; + + services.tailscale.enable = true; + # We were having weird build errors so this is disabled right now + # error: The option `devices.emergent.folders.eve_records.path' was accessed but has no value defined. Try setting the option + services.syncthing.enable = false; + # Configure keymap in X11 # services.xserver.xkb.layout = "us"; # services.xserver.xkb.options = "eurosign:e,caps:escape"; @@ -80,12 +119,19 @@ # programs.firefox.enable = true; - # List packages installed in system profile. - # You can use https://search.nixos.org/ to find more packages (and options). - # environment.systemPackages = with pkgs; [ - # vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default. - # wget - # ]; + nixpkgs.config.allowUnfree = true; + + # Packages that can be installed without any extra configuration + # See https://search.nixos.org/packages for all options + environment.systemPackages = with pkgs; [ + wget + gnome-boxes + libvirt + ]; + + # Packages that need to be installed with some extra configuration + # See https://search.nixos.org/options for all options + programs = {}; # Some programs need SUID wrappers, can be configured further or are # started in user sessions. diff --git a/configurations/nixos/emergent/default.nix b/configurations/nixos/emergent/default.nix index 3455825..3acaeda 100644 --- a/configurations/nixos/emergent/default.nix +++ b/configurations/nixos/emergent/default.nix @@ -3,6 +3,6 @@ imports = [ ./configuration.nix ./hardware-configuration.nix - ./disco-configuration.nix + ./legacy-storage.nix ]; } diff --git a/configurations/nixos/emergent/disco-configuration.nix b/configurations/nixos/emergent/disco-configuration.nix deleted file mode 100644 index ec002b2..0000000 --- a/configurations/nixos/emergent/disco-configuration.nix +++ /dev/null @@ -1,57 +0,0 @@ -{...}: { - disko.devices = { - disk = { - disk1 = { - type = "disk"; - device = "/dev/disk/by-id/wwn-0x5000039fd0cf05eb"; - content = { - type = "gpt"; - partitions = { - ESP = { - size = "64M"; - type = "EF00"; - content = { - type = "filesystem"; - format = "vfat"; - mountpoint = "/boot"; - mountOptions = ["umask=0077"]; - }; - }; - zfs = { - size = "100%"; - content = { - type = "zfs"; - pool = "zroot"; - }; - }; - }; - }; - }; - }; - zpool = { - zroot = { - type = "zpool"; - mode = ""; - options.cachefile = "none"; - rootFsOptions = { - compression = "zstd"; - "com.sun:auto-snapshot" = "true"; - }; - mountpoint = "/"; - postCreateHook = "zfs list -t snapshot -H -o name | grep -E '^zroot@blank$' || zfs snapshot zroot@blank"; - - datasets = { - "system/nix" = { - type = "zfs_fs"; - mountpoint = "/nix"; - options = { - atime = "off"; - relatime = "off"; - canmount = "on"; - }; - }; - }; - }; - }; - }; -} diff --git a/configurations/nixos/emergent/hardware-configuration.nix b/configurations/nixos/emergent/hardware-configuration.nix index 4e13149..b077f9c 100644 --- a/configurations/nixos/emergent/hardware-configuration.nix +++ b/configurations/nixos/emergent/hardware-configuration.nix @@ -12,7 +12,7 @@ (modulesPath + "/installer/scan/not-detected.nix") ]; - boot.initrd.availableKernelModules = ["xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod"]; + boot.initrd.availableKernelModules = ["xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod" "wacom" "kvm" "kvm_amd"]; boot.initrd.kernelModules = []; boot.kernelModules = []; boot.extraModulePackages = []; diff --git a/configurations/nixos/emergent/legacy-storage.nix b/configurations/nixos/emergent/legacy-storage.nix new file mode 100644 index 0000000..2b24729 --- /dev/null +++ b/configurations/nixos/emergent/legacy-storage.nix @@ -0,0 +1,51 @@ +# Legacy storage configuration for emergent +# This file manually defines ZFS datasets matching the existing on-disk layout +# to allow incremental migration to the new storage module (generateBase = true). +# +# Current on-disk dataset layout: +# rpool/local/ - parent (canmount=off) +# rpool/local/system/nix - nix store +# rpool/local/system/root - root filesystem +# +# Migration plan: +# Phase 1: Rename datasets on disk (boot from live USB) +# zfs rename -p rpool/local/system/nix rpool/persist/local/nix +# zfs rename rpool/local rpool/persist/local +# # This moves: local/system/root -> persist/local/root (need to rename after) +# # Actually, since local/system/root needs to become persist/local/root: +# zfs rename rpool/persist/local/system/root rpool/persist/local/root +# zfs destroy rpool/persist/local/system # now empty +# # Recreate blank snapshot: +# zfs destroy rpool/persist/local/root@blank +# zfs snapshot rpool/persist/local/root@blank +# +# Phase 2: Delete this file, remove its import from default.nix, rebuild. +{...}: { + # Disable automatic base dataset generation so we can define them manually + storage.generateBase = false; + + # Manually define ZFS datasets matching the existing on-disk layout + storage.zfs.datasets = { + "local" = { + type = "zfs_fs"; + mount = null; + }; + "local/system/nix" = { + type = "zfs_fs"; + mount = "/nix"; + atime = "off"; + relatime = "off"; + snapshot = { + autoSnapshot = false; + }; + }; + "local/system/root" = { + type = "zfs_fs"; + mount = "/"; + snapshot = { + blankSnapshot = true; + autoSnapshot = true; + }; + }; + }; +} diff --git a/configurations/nixos/emergent/nvidia-drivers.nix b/configurations/nixos/emergent/nvidia-drivers.nix new file mode 100644 index 0000000..05b7205 --- /dev/null +++ b/configurations/nixos/emergent/nvidia-drivers.nix @@ -0,0 +1,46 @@ +{config, ...}: { + # Enable OpenGL + hardware.graphics = { + enable = true; + }; + + # Load nvidia driver for Xorg and Wayland + services = { + xserver = { + # Load nvidia driver for Xorg and Wayland + videoDrivers = ["nvidia"]; + }; + # Use X instead of wayland + displayManager.gdm.wayland = true; + }; + + hardware.nvidia = { + # Modesetting is required. + modesetting.enable = true; + + # Nvidia power management. Experimental, and can cause sleep/suspend to fail. + # Enable this if you have graphical corruption issues or application crashes after waking + # up from sleep. This fixes it by saving the entire VRAM memory to /tmp/ instead + # of just the bare essentials. + powerManagement.enable = true; + + # Fine-grained power management. Turns off GPU when not in use. + # Experimental and only works on modern Nvidia GPUs (Turing or newer). + powerManagement.finegrained = false; + + # Use the NVidia open source kernel module (not to be confused with the + # independent third-party "nouveau" open source driver). + # Support is limited to the Turing and later architectures. Full list of + # supported GPUs is at: + # https://github.com/NVIDIA/open-gpu-kernel-modules#compatible-gpus + # Only available from driver 515.43.04+ + open = true; + + # Enable the Nvidia settings menu, + # accessible via `nvidia-settings`. + nvidiaSettings = true; + + # Optionally, you may need to select the appropriate driver version for your specific GPU. + package = config.boot.kernelPackages.nvidiaPackages.stable; + }; +} diff --git a/configurations/nixos/horizon/configuration.nix b/configurations/nixos/horizon/configuration.nix index 7e2ab8a..b81a895 100644 --- a/configurations/nixos/horizon/configuration.nix +++ b/configurations/nixos/horizon/configuration.nix @@ -1,7 +1,8 @@ { + lib, + pkgs, config, inputs, - pkgs, ... }: { imports = [ @@ -10,6 +11,19 @@ nixpkgs.config.allowUnfree = true; + boot = { + initrd = { + availableKernelModules = ["usb_storage" "sd_mod"]; + }; + kernelModules = ["sg"]; + + # Bootloader. + loader = { + systemd-boot.enable = true; + efi.canTouchEfiVariables = true; + }; + }; + host = { users = { leyla = { @@ -28,38 +42,65 @@ enable = true; models = { "Llama 3.1 8B" = { - model = "lamma3.1:8b"; + model = "llama3.1:8b"; roles = ["chat" "edit" "apply"]; - apiBase = "http://twilight:11434"; + apiBase = "http://defiant:11434"; + }; + "Deepseek Coder:6.7B" = { + model = "deepseek-coder:6.7b"; + roles = ["chat" "edit" "apply"]; + apiBase = "http://defiant:11434"; + }; + "Deepseek Coder:33B" = { + model = "deepseek-coder:33b"; + roles = ["chat" "edit" "apply"]; + apiBase = "http://defiant:11434"; + }; + + "Deepseek r1:8B" = { + model = "deepseek-r1:8b"; + roles = ["chat"]; + apiBase = "http://defiant:11434"; + }; + + "Deepseek r1:32B" = { + model = "deepseek-r1:32b"; + roles = ["chat"]; + apiBase = "http://defiant:11434"; }; "qwen2.5-coder:1.5b-base" = { model = "qwen2.5-coder:1.5b-base"; roles = ["autocomplete"]; - apiBase = "http://twilight:11434"; + apiBase = "http://defiant:11434"; }; "nomic-embed-text:latest" = { model = "nomic-embed-text:latest"; roles = ["embed"]; - apiBase = "http://twilight:11434"; + apiBase = "http://defiant:11434"; }; }; }; }; - environment.systemPackages = with pkgs; [ - webtoon-dl - prostudiomasters - ]; + virtualisation.docker.enable = true; - programs = { - adb.enable = true; - steam = { - enable = true; - remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play - dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server - }; + environment.systemPackages = with pkgs; [ + cachefilesd + webtoon-dl + android-tools + ]; + services.cachefilesd.enable = true; + + networking = { + networkmanager.enable = true; + hostName = "horizon"; # Define your hostname. + }; + powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; + + hardware = { + graphics.enable = true; }; sops.secrets = { @@ -73,6 +114,10 @@ fprintd = { enable = true; }; + # firmware update tool + fwupd = { + enable = true; + }; tailscale = { enable = true; authKeyFile = config.sops.secrets."vpn-keys/tailscale-authkey/horizon".path; @@ -80,8 +125,18 @@ }; syncthing.enable = true; + + ollama = { + enable = true; + loadModels = [ + "llama3.1:8b" + ]; + }; }; + # Enable network-online.target for better network dependency handling + systemd.services.NetworkManager-wait-online.enable = true; + # Enable touchpad support (enabled default in most desktopManager). # services.xserver.libinput.enable = true; diff --git a/configurations/nixos/horizon/default.nix b/configurations/nixos/horizon/default.nix index 1263215..b916d82 100644 --- a/configurations/nixos/horizon/default.nix +++ b/configurations/nixos/horizon/default.nix @@ -3,5 +3,6 @@ imports = [ ./configuration.nix ./hardware-configuration.nix + # ./network-mount.nix ]; } diff --git a/configurations/nixos/horizon/hardware-configuration.nix b/configurations/nixos/horizon/hardware-configuration.nix index e88d8dc..cec4914 100644 --- a/configurations/nixos/horizon/hardware-configuration.nix +++ b/configurations/nixos/horizon/hardware-configuration.nix @@ -4,7 +4,6 @@ { config, lib, - pkgs, modulesPath, ... }: { @@ -12,22 +11,10 @@ (modulesPath + "/installer/scan/not-detected.nix") ]; - boot = { - initrd = { - availableKernelModules = ["xhci_pci" "thunderbolt" "nvme" "usb_storage" "sd_mod"]; - kernelModules = []; - }; - kernelModules = ["kvm-intel" "sg"]; - extraModulePackages = []; - - # Bootloader. - loader = { - systemd-boot.enable = true; - efi.canTouchEfiVariables = true; - }; - - supportedFilesystems = ["nfs"]; - }; + boot.initrd.availableKernelModules = ["xhci_pci" "thunderbolt" "nvme"]; + boot.initrd.kernelModules = []; + boot.kernelModules = ["kvm-intel"]; + boot.extraModulePackages = []; fileSystems = { "/" = { @@ -39,98 +26,20 @@ device = "/dev/disk/by-uuid/E138-65B5"; fsType = "vfat"; }; - - "/mnt/leyla_documents" = { - device = "defiant:/export/leyla_documents"; - fsType = "nfs"; - options = [ - "vers=4" - "x-systemd.automount" - "noauto" - "user" - "noatime" - "nofail" - "x-systemd.idle-timeout=600" - "fsc" - "timeo=600" - "retrans=2" - ]; - }; - - "/mnt/eve_documents" = { - device = "defiant:/export/eve_documents"; - fsType = "nfs"; - options = [ - "vers=4" - "x-systemd.automount" - "noauto" - "user" - "nofail" - "x-systemd.idle-timeout=600" - "fsc" - "timeo=600" - "retrans=2" - ]; - }; - - "/mnt/users_documents" = { - device = "defiant:/export/users_documents"; - fsType = "nfs"; - options = [ - "vers=4" - "x-systemd.automount" - "noauto" - "user" - "nofail" - "x-systemd.idle-timeout=600" - "fsc" - "timeo=600" - "retrans=2" - ]; - }; - - "/mnt/media" = { - device = "defiant:/export/media"; - fsType = "nfs"; - options = [ - "vers=4" - "x-systemd.automount" - "noauto" - "user" - "noatime" - "nofail" - "x-systemd.idle-timeout=600" - "noatime" - "nodiratime" - "relatime" - "fsc" - "timeo=600" - "retrans=2" - ]; - }; }; - environment.systemPackages = with pkgs; [ - cachefilesd - ]; - - services.cachefilesd.enable = true; - swapDevices = [ {device = "/dev/disk/by-uuid/be98e952-a072-4c3a-8c12-69500b5a2fff";} ]; - networking = { - networkmanager.enable = true; - useDHCP = lib.mkDefault true; - hostName = "horizon"; # Define your hostname. - }; + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking + # (the default) this is the recommended approach. When using systemd-networkd it's + # still possible to use this option, but it's recommended to use it in conjunction + # with explicit per-interface declarations with `networking.interfaces..useDHCP`. + networking.useDHCP = lib.mkDefault true; + # networking.interfaces.tailscale0.useDHCP = lib.mkDefault true; + # networking.interfaces.wlp170s0.useDHCP = lib.mkDefault true; nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; - - hardware = { - graphics.enable = true; - cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; - }; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; } diff --git a/configurations/nixos/horizon/network-mount.nix b/configurations/nixos/horizon/network-mount.nix new file mode 100644 index 0000000..fde16f5 --- /dev/null +++ b/configurations/nixos/horizon/network-mount.nix @@ -0,0 +1,76 @@ +{...}: { + boot.supportedFilesystems = ["nfs"]; + + fileSystems = { + "/mnt/leyla_documents" = { + device = "defiant:/exports/leyla_documents"; + fsType = "nfs"; + options = [ + "x-systemd.automount" + "noauto" + "noatime" + "nofail" + "soft" + "intr" # Allow interruption of NFS calls + "timeo=30" # 3 second timeout (30 deciseconds) + "retrans=2" # Only 2 retries before giving up + "x-systemd.idle-timeout=300" # 5 minute idle timeout for mobile + "x-systemd.device-timeout=15" # 15 second device timeout + "bg" # Background mount - don't block boot + "fsc" # Enable caching + "_netdev" # Network device - wait for network + "x-systemd.requires=network-online.target" # Require network to be online + "x-systemd.after=network-online.target" # Start after network is online + "x-systemd.mount-timeout=30" # 30 second mount timeout + ]; + }; + + "/mnt/users_documents" = { + device = "defiant:/exports/users_documents"; + fsType = "nfs"; + options = [ + "x-systemd.automount" + "noauto" + "nofail" + "soft" + "intr" + "timeo=30" + "retrans=2" + "x-systemd.idle-timeout=300" + "x-systemd.device-timeout=15" + "bg" + "fsc" + "_netdev" + "x-systemd.requires=network-online.target" + "x-systemd.after=network-online.target" + "x-systemd.mount-timeout=30" + ]; + }; + + "/mnt/media" = { + device = "defiant:/exports/media"; + fsType = "nfs"; + options = [ + "x-systemd.automount" + "noauto" + "noatime" + "nofail" + "soft" + "intr" + "timeo=30" + "retrans=2" + "x-systemd.idle-timeout=300" + "x-systemd.device-timeout=15" + "bg" + # Mobile-optimized read settings + "rsize=8192" # Smaller read size for mobile + "wsize=8192" # Smaller write size for mobile + "fsc" + "_netdev" + "x-systemd.requires=network-online.target" + "x-systemd.after=network-online.target" + "x-systemd.mount-timeout=30" + ]; + }; + }; +} diff --git a/configurations/nixos/twilight/configuration.nix b/configurations/nixos/twilight/configuration.nix index e9032d8..d02af0a 100644 --- a/configurations/nixos/twilight/configuration.nix +++ b/configurations/nixos/twilight/configuration.nix @@ -1,14 +1,19 @@ { inputs, config, + pkgs, ... }: { - imports = [ - ./monitors.nix - ]; - nixpkgs.config.allowUnfree = true; + boot.initrd.availableKernelModules = ["usb_storage"]; + boot.kernelModules = ["sg"]; + + boot.loader = { + systemd-boot.enable = true; + efi.canTouchEfiVariables = true; + }; + sops.secrets = { "vpn-keys/tailscale-authkey/twilight" = { sopsFile = "${inputs.secrets}/vpn-keys.yaml"; @@ -121,13 +126,20 @@ syncthing.enable = true; }; - programs.steam = { - enable = true; - remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play - dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server - }; + + # Enable network-online.target for better network dependency handling + systemd.services.NetworkManager-wait-online.enable = true; + + environment.systemPackages = with pkgs; [ + cachefilesd + ]; hardware.steam-hardware.enable = true; # Provides udev rules for controller, HTC vive, and Valve Index + networking = { + networkmanager.enable = true; + hostName = "twilight"; # Define your hostname. + }; + # enabled virtualisation for docker # virtualisation.docker.enable = true; diff --git a/configurations/nixos/twilight/default.nix b/configurations/nixos/twilight/default.nix index edfb3f6..aa841f8 100644 --- a/configurations/nixos/twilight/default.nix +++ b/configurations/nixos/twilight/default.nix @@ -3,5 +3,7 @@ imports = [ ./configuration.nix ./hardware-configuration.nix + ./nvidia-drivers.nix + # ./network-mount.nix ]; } diff --git a/configurations/nixos/twilight/hardware-configuration.nix b/configurations/nixos/twilight/hardware-configuration.nix index 1cba7de..1288343 100644 --- a/configurations/nixos/twilight/hardware-configuration.nix +++ b/configurations/nixos/twilight/hardware-configuration.nix @@ -4,7 +4,6 @@ { config, lib, - pkgs, modulesPath, ... }: { @@ -12,147 +11,32 @@ (modulesPath + "/installer/scan/not-detected.nix") ]; - boot = { - initrd = { - availableKernelModules = ["nvme" "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod"]; - kernelModules = []; - }; - kernelModules = ["kvm-amd" "sg"]; - extraModulePackages = []; - - # Bootloader. - loader = { - systemd-boot.enable = true; - efi.canTouchEfiVariables = true; - }; - - supportedFilesystems = ["nfs"]; - }; - - services.xserver = { - # Load nvidia driver for Xorg and Wayland - videoDrivers = ["nvidia"]; - - # Use X instead of wayland for gaming reasons - displayManager.gdm.wayland = false; - }; + boot.initrd.availableKernelModules = ["nvme" "xhci_pci" "ahci" "usbhid" "sd_mod"]; + boot.initrd.kernelModules = []; + boot.kernelModules = ["kvm-amd"]; + boot.extraModulePackages = []; fileSystems = { "/" = { - device = "/dev/disk/by-uuid/8be49c65-2b57-48f1-b74d-244d26061adb"; + device = "/dev/disk/by-id/nvme-Samsung_SSD_980_500GB_S64ENJ0RA06463Z-part2"; fsType = "ext4"; }; "/boot" = { - device = "/dev/disk/by-uuid/3006-3867"; + device = "/dev/disk/by-id/nvme-Samsung_SSD_980_500GB_S64ENJ0RA06463Z-part1"; fsType = "vfat"; options = ["fmask=0022" "dmask=0022"]; }; - - "/mnt/leyla_documents" = { - device = "defiant:/exports/leyla_documents"; - fsType = "nfs"; - options = [ - "x-systemd.automount" - "noauto" - "user" - "noatime" - "nofail" - "soft" - "x-systemd.idle-timeout=600" - "fsc" - ]; - }; - - "/mnt/users_documents" = { - device = "defiant:/exports/users_documents"; - fsType = "nfs"; - options = [ - "x-systemd.automount" - "noauto" - "user" - "nofail" - "soft" - "x-systemd.idle-timeout=600" - "fsc" - ]; - }; - - "/mnt/media" = { - device = "defiant:/exports/media"; - fsType = "nfs"; - options = [ - "x-systemd.automount" - "noauto" - "user" - "noatime" - "nofail" - "soft" - "x-systemd.idle-timeout=600" - "noatime" - "nodiratime" - "relatime" - "rsize=32768" - "wsize=32768" - "fsc" - ]; - }; }; - environment.systemPackages = with pkgs; [ - cachefilesd - ]; - swapDevices = []; - networking = { - networkmanager.enable = true; - # Enables DHCP on each ethernet and wireless interface. In case of scripted networking - # (the default) this is the recommended approach. When using systemd-networkd it's - # still possible to use this option, but it's recommended to use it in conjunction - # with explicit per-interface declarations with `networking.interfaces..useDHCP`. - useDHCP = lib.mkDefault true; - hostName = "twilight"; # Define your hostname. - }; + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking + # (the default) this is the recommended approach. When using systemd-networkd it's + # still possible to use this option, but it's recommended to use it in conjunction + # with explicit per-interface declarations with `networking.interfaces..useDHCP`. + networking.useDHCP = lib.mkDefault true; nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - - hardware = { - # Enable OpenGL - graphics.enable = true; - - # install graphics drivers - nvidia = { - # Modesetting is required. - modesetting.enable = true; - - # Nvidia power management. Experimental, and can cause sleep/suspend to fail. - # Enable this if you have graphical corruption issues or application crashes after waking - # up from sleep. This fixes it by saving the entire VRAM memory to /tmp/ instead - # of just the bare essentials. - powerManagement.enable = false; - - # Fine-grained power management. Turns off GPU when not in use. - # Experimental and only works on modern Nvidia GPUs (Turing or newer). - powerManagement.finegrained = false; - - # Use the NVidia open source kernel module (not to be confused with the - # independent third-party "nouveau" open source driver). - # Support is limited to the Turing and later architectures. Full list of - # supported GPUs is at: - # https://github.com/NVIDIA/open-gpu-kernel-modules#compatible-gpus - # Only available from driver 515.43.04+ - # Currently alpha-quality/buggy, so false is currently the recommended setting. - open = false; - - # Enable the Nvidia settings menu, - # accessible via `nvidia-settings`. - nvidiaSettings = true; - - # Optionally, you may need to select the appropriate driver version for your specific GPU. - package = config.boot.kernelPackages.nvidiaPackages.production; - }; - - cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; - }; + hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; } diff --git a/configurations/nixos/twilight/monitors.nix b/configurations/nixos/twilight/monitors.nix deleted file mode 100644 index 1308f50..0000000 --- a/configurations/nixos/twilight/monitors.nix +++ /dev/null @@ -1,199 +0,0 @@ -{pkgs, ...}: { - systemd.tmpfiles.rules = [ - "L+ /run/gdm/.config/monitors.xml - - - - ${pkgs.writeText "gdm-monitors.xml" '' - - - - 0 - 156 - 1 - - - DP-4 - DEL - DELL U2719D - 8RGXNS2 - - - 2560 - 1440 - 59.951 - - - - - 2560 - 324 - 1 - yes - - - DP-2 - GSM - LG ULTRAGEAR - 0x00068c96 - - - 1920 - 1080 - 240.001 - - - - - 4480 - 0 - 1 - - left - no - - - - HDMI-0 - HWP - HP w2207 - CND7332S88 - - - 1600 - 1000 - 59.999 - - - - - - - 0 - 0 - 1 - yes - - - DP-1 - DEL - DELL U2719D - 8RGXNS2 - - - 2560 - 1440 - 59.951 - - - - - 4480 - 226 - 1 - - left - no - - - - HDMI-1 - HWP - HP w2207 - CND7332S88 - - - 1680 - 1050 - 59.954 - - - - - 2560 - 226 - 1 - - - DP-2 - GSM - LG ULTRAGEAR - 0x00068c96 - - - 1920 - 1080 - 240.001 - - - - - - - 2560 - 228 - 1 - yes - - - DP-2 - GSM - LG ULTRAGEAR - 0x00068c96 - - - 1920 - 1080 - 240.001 - - - - - 4480 - 69 - 1 - - left - no - - - - HDMI-1 - HWP - HP w2207 - CND7332S88 - - - 1680 - 1050 - 59.954 - - - - - 0 - 0 - 1 - - - DP-3 - DEL - DELL U2719D - 8RGXNS2 - - - 2560 - 1440 - 59.951 - - - - - - None-1 - unknown - unknown - unknown - - - - - ''}" - ]; -} diff --git a/configurations/nixos/twilight/network-mount.nix b/configurations/nixos/twilight/network-mount.nix new file mode 100644 index 0000000..9f84b04 --- /dev/null +++ b/configurations/nixos/twilight/network-mount.nix @@ -0,0 +1,72 @@ +{...}: { + boot.supportedFilesystems = ["nfs"]; + + fileSystems = { + "/mnt/leyla_documents" = { + device = "defiant:/exports/leyla_documents"; + fsType = "nfs"; + options = [ + "x-systemd.automount" + "noauto" + "noatime" + "nofail" + "soft" + "intr" # Allow interruption of NFS calls + "timeo=50" # 5 second timeout (50 deciseconds) - longer than mobile + "retrans=3" # 3 retries for desktop + "x-systemd.idle-timeout=600" # 10 minute idle timeout for desktop + "x-systemd.device-timeout=30" # 30 second device timeout + "bg" # Background mount - don't block boot + "fsc" # Enable caching + "_netdev" # Network device - wait for network + "x-systemd.requires=network-online.target" # Require network to be online + "x-systemd.after=network-online.target" # Start after network is online + ]; + }; + + "/mnt/users_documents" = { + device = "defiant:/exports/users_documents"; + fsType = "nfs"; + options = [ + "x-systemd.automount" + "noauto" + "nofail" + "soft" + "intr" + "timeo=50" + "retrans=3" + "x-systemd.idle-timeout=600" + "bg" + "fsc" + "_netdev" + "x-systemd.requires=network-online.target" + "x-systemd.after=network-online.target" + ]; + }; + + "/mnt/media" = { + device = "defiant:/exports/media"; + fsType = "nfs"; + options = [ + "x-systemd.automount" + "noauto" + "noatime" + "nofail" + "soft" + "intr" + "timeo=50" + "retrans=3" + "x-systemd.idle-timeout=600" + "x-systemd.device-timeout=30" + "bg" + # Desktop-optimized read settings + "rsize=32768" # Larger read size for desktop + "wsize=32768" # Larger write size for desktop + "fsc" + "_netdev" + "x-systemd.requires=network-online.target" + "x-systemd.after=network-online.target" + ]; + }; + }; +} diff --git a/configurations/nixos/twilight/nvidia-drivers.nix b/configurations/nixos/twilight/nvidia-drivers.nix new file mode 100644 index 0000000..2842d0a --- /dev/null +++ b/configurations/nixos/twilight/nvidia-drivers.nix @@ -0,0 +1,48 @@ +{config, ...}: { + services = { + xserver = { + # Load nvidia driver for Xorg and Wayland + videoDrivers = ["nvidia"]; + }; + # Temporarily enable wayland to fix boot issue + # TODO: Investigate proper X11 session generation for gaming + displayManager.gdm.wayland = true; + }; + + hardware = { + # Enable OpenGL + graphics.enable = true; + + # install graphics drivers + nvidia = { + # Modesetting is required. + modesetting.enable = true; + + # Nvidia power management. Experimental, and can cause sleep/suspend to fail. + # Enable this if you have graphical corruption issues or application crashes after waking + # up from sleep. This fixes it by saving the entire VRAM memory to /tmp/ instead + # of just the bare essentials. + powerManagement.enable = true; + + # Fine-grained power management. Turns off GPU when not in use. + # Experimental and only works on modern Nvidia GPUs (Turing or newer). + powerManagement.finegrained = false; + + # Use the NVidia open source kernel module (not to be confused with the + # independent third-party "nouveau" open source driver). + # Support is limited to the Turing and later architectures. Full list of + # supported GPUs is at: + # https://github.com/NVIDIA/open-gpu-kernel-modules#compatible-gpus + # Only available from driver 515.43.04+ + # Currently alpha-quality/buggy, so false is currently the recommended setting. + open = true; + + # Enable the Nvidia settings menu, + # accessible via `nvidia-settings`. + nvidiaSettings = true; + + # Optionally, you may need to select the appropriate driver version for your specific GPU. + package = config.boot.kernelPackages.nvidiaPackages.production; + }; + }; +} diff --git a/flake.lock b/flake.lock index fa3ad83..14c8561 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1748225455, - "narHash": "sha256-AzlJCKaM4wbEyEpV3I/PUq5mHnib2ryEy32c+qfj6xk=", + "lastModified": 1772867152, + "narHash": "sha256-RIFgZ4O6Eg+5ysZ8Tqb3YvcqiRaNy440GEY22ltjRrs=", "owner": "nix-community", "repo": "disko", - "rev": "a894f2811e1ee8d10c50560551e50d6ab3c392ba", + "rev": "eaafb89b56e948661d618eefd4757d9ea8d77514", "type": "github" }, "original": { @@ -28,11 +28,11 @@ }, "locked": { "dir": "pkgs/firefox-addons", - "lastModified": 1748405006, - "narHash": "sha256-pmt0SFjACJJAI8g8QU5arg2c9BXNZG9/okVwRSDJkG8=", + "lastModified": 1772856163, + "narHash": "sha256-xD+d1+FVhKJ+oFYMTWOdVSBoXS4yeMyVZyDjMXqWEJE=", "owner": "rycee", "repo": "nur-expressions", - "rev": "f9801a86d6603260940890c36650275090d1dceb", + "rev": "d358a550c7beac5f04fbc5a786e14af079606689", "type": "gitlab" }, "original": { @@ -44,11 +44,11 @@ }, "flake-compat": { "locked": { - "lastModified": 1747046372, - "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", "owner": "edolstra", "repo": "flake-compat", - "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { @@ -57,6 +57,24 @@ "type": "github" } }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1767609335, + "narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "250481aafeb741edfe23d29195671c19b36b6dca", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -75,6 +93,21 @@ "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": [ @@ -82,11 +115,11 @@ ] }, "locked": { - "lastModified": 1748455938, - "narHash": "sha256-mQ/iNzPra2WtDQ+x2r5IadcWNr0m3uHvLMzJkXKAG/8=", + "lastModified": 1772845525, + "narHash": "sha256-Dp5Ir2u4jJDGCgeMRviHvEQDe+U37hMxp6RSNOoMMPc=", "owner": "nix-community", "repo": "home-manager", - "rev": "02077149e2921014511dac2729ae6dadb4ec50e2", + "rev": "27b93804fbef1544cb07718d3f0a451f4c4cd6c0", "type": "github" }, "original": { @@ -96,12 +129,20 @@ } }, "impermanence": { + "inputs": { + "home-manager": [ + "home-manager" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, "locked": { - "lastModified": 1737831083, - "narHash": "sha256-LJggUHbpyeDvNagTUrdhe/pRVp4pnS6wVKALS782gRI=", + "lastModified": 1769548169, + "narHash": "sha256-03+JxvzmfwRu+5JafM0DLbxgHttOQZkUtDWBmeUkN8Y=", "owner": "nix-community", "repo": "impermanence", - "rev": "4b3e914cdf97a5b536a889e939fb2fd2b043a170", + "rev": "7b1d382faf603b6d264f58627330f9faa5cba149", "type": "github" }, "original": { @@ -110,6 +151,62 @@ "type": "github" } }, + "lix": { + "flake": false, + "locked": { + "lastModified": 1761937274, + "narHash": "sha256-KlELhsSq3XbemrGyQhmGurFu7m8wOEBw+8M04L7hn7A=", + "rev": "91867941fa73afea7869b7c71ede82e5ef8927da", + "type": "tarball", + "url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/91867941fa73afea7869b7c71ede82e5ef8927da.tar.gz?rev=91867941fa73afea7869b7c71ede82e5ef8927da" + }, + "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": 1767364176, + "narHash": "sha256-l6YdEBYQxXjD8ujqvc0tKdwWc3K8UQOi+E4Y3DKQ318=", + "ref": "refs/heads/main", + "rev": "1688100bba140492658d597f6b307c327f35c780", + "revCount": 179, + "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": { + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1772769318, + "narHash": "sha256-RAyOW5JMXRhiREqxFPOzw80fVsYVBnOPFgBSjnJ6gbY=", + "owner": "utensils", + "repo": "mcp-nixos", + "rev": "60c1efbba0de1268b42f1144c904e6c8a9627dde", + "type": "github" + }, + "original": { + "owner": "utensils", + "repo": "mcp-nixos", + "type": "github" + } + }, "nix-darwin": { "inputs": { "nixpkgs": [ @@ -117,11 +214,11 @@ ] }, "locked": { - "lastModified": 1748352827, - "narHash": "sha256-sNUUP6qxGkK9hXgJ+p362dtWLgnIWwOCmiq72LAWtYo=", + "lastModified": 1772379624, + "narHash": "sha256-NG9LLTWlz4YiaTAiRGChbrzbVxBfX+Auq4Ab/SWmk4A=", "owner": "LnL7", "repo": "nix-darwin", - "rev": "44a7d0e687a87b73facfe94fba78d323a6686a90", + "rev": "52d061516108769656a8bd9c6e811c677ec5b462", "type": "github" }, "original": { @@ -153,17 +250,16 @@ }, "nix-vscode-extensions": { "inputs": { - "flake-utils": "flake-utils", "nixpkgs": [ "nixpkgs" ] }, "locked": { - "lastModified": 1748397853, - "narHash": "sha256-tudGoP5caIJ5TzkV6wnsmUk7Spx21oWMKpkmPbjRNZc=", + "lastModified": 1772850876, + "narHash": "sha256-Ga19zlfMpakCY4GMwBSOljNLOF0nEYrYBXv0hP/d4rw=", "owner": "nix-community", "repo": "nix-vscode-extensions", - "rev": "ac4fc8eb9a1ee5eeb3c0a30f57652e4c5428d3a5", + "rev": "22f084d4c280dfc8a9d764f7b85af38e5d69c3dc", "type": "github" }, "original": { @@ -174,11 +270,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1747900541, - "narHash": "sha256-dn64Pg9xLETjblwZs9Euu/SsjW80pd6lr5qSiyLY1pg=", + "lastModified": 1771969195, + "narHash": "sha256-qwcDBtrRvJbrrnv1lf/pREQi8t2hWZxVAyeMo7/E9sw=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "11f2d9ea49c3e964315215d6baa73a8d42672f06", + "rev": "41c6b421bdc301b2624486e11905c9af7b8ec68e", "type": "github" }, "original": { @@ -190,11 +286,42 @@ }, "nixpkgs": { "locked": { - "lastModified": 1748370509, - "narHash": "sha256-QlL8slIgc16W5UaI3w7xHQEP+Qmv/6vSNTpoZrrSlbk=", + "lastModified": 1767640445, + "narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1765674936, + "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1772773019, + "narHash": "sha256-E1bxHxNKfDoQUuvriG71+f+s/NT0qWkImXsYZNFFfCs=", "owner": "nixos", "repo": "nixpkgs", - "rev": "4faa5f5321320e49a78ae7848582f684d64783e9", + "rev": "aca4d95fce4914b3892661bcb80b8087293536c6", "type": "github" }, "original": { @@ -204,6 +331,43 @@ "type": "github" } }, + "nixpkgs_3": { + "locked": { + "lastModified": 1759070547, + "narHash": "sha256-JVZl8NaVRYb0+381nl7LvPE+A774/dRpif01FKLrYFQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "647e5c14cbd5067f44ac86b74f014962df460840", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "noita-entangled-worlds": { + "inputs": { + "nixpkgs": "nixpkgs_3", + "rust-overlay": "rust-overlay", + "systems": "systems_2" + }, + "locked": { + "lastModified": 1771445312, + "narHash": "sha256-8uOcu+ZurGx0LmGFCf87Zbj4ikhVPQtP+PuBscEBCv0=", + "owner": "IntQuant", + "repo": "noita_entangled_worlds", + "rev": "4a842f29d0e5fb8dc6df73d87f7bb8d2a16f0fc8", + "type": "github" + }, + "original": { + "owner": "IntQuant", + "ref": "master", + "repo": "noita_entangled_worlds", + "type": "github" + } + }, "root": { "inputs": { "disko": "disko", @@ -211,23 +375,47 @@ "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", "nix-vscode-extensions": "nix-vscode-extensions", "nixos-hardware": "nixos-hardware", - "nixpkgs": "nixpkgs", + "nixpkgs": "nixpkgs_2", + "noita-entangled-worlds": "noita-entangled-worlds", "secrets": "secrets", "sops-nix": "sops-nix" } }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "noita-entangled-worlds", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1759199574, + "narHash": "sha256-w24RYly3VSVKp98rVfCI1nFYfQ0VoWmShtKPCbXgK6A=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "381776b12d0d125edd7c1930c2041a1471e586c0", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, "secrets": { "flake": false, "locked": { - "lastModified": 1743538790, - "narHash": "sha256-QXmvyxfAhpifxAWcYTvuGfzv9I+9gHw0bq4WYtGEB9A=", + "lastModified": 1768867162, + "narHash": "sha256-NiW2gUcdhnUbYQw476HzgBz+uVjyLnz151hzCQbWBX8=", "ref": "refs/heads/main", - "rev": "3d63dff77f8eda1667e3586169642cf256c4aa34", - "revCount": 17, + "rev": "22be81505a49cd205e9b5c91f51af69c0b885ed3", + "revCount": 23, "type": "git", "url": "ssh://git@git.jan-leila.com/jan-leila/nix-config-secrets.git" }, @@ -243,11 +431,11 @@ ] }, "locked": { - "lastModified": 1747603214, - "narHash": "sha256-lAblXm0VwifYCJ/ILPXJwlz0qNY07DDYdLD+9H+Wc8o=", + "lastModified": 1772495394, + "narHash": "sha256-hmIvE/slLKEFKNEJz27IZ8BKlAaZDcjIHmkZ7GCEjfw=", "owner": "Mic92", "repo": "sops-nix", - "rev": "8d215e1c981be3aa37e47aeabd4e61bb069548fd", + "rev": "1d9b98a29a45abe9c4d3174bd36de9f28755e3ff", "type": "github" }, "original": { @@ -270,6 +458,22 @@ "repo": "default", "type": "github" } + }, + "systems_2": { + "flake": false, + "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 496456a..df5f6e9 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/stable.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 = { @@ -37,6 +37,8 @@ # delete your darlings impermanence = { url = "github:nix-community/impermanence"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.home-manager.follows = "home-manager"; }; nix-darwin = { @@ -71,6 +73,18 @@ flake-compat = { url = "github:edolstra/flake-compat"; }; + + # MCP NixOS server for Claude Dev + mcp-nixos = { + url = "github:utensils/mcp-nixos"; + # Not following nixpkgs because aws-sam-translator doesn't support Python 3.14 yet + }; + + # Noita Entangled Worlds package + # Not following our nixpkgs so it can use its own rust-overlay configuration + noita-entangled-worlds = { + url = "github:IntQuant/noita_entangled_worlds/master"; + }; }; outputs = { @@ -85,15 +99,9 @@ util = import ./util {inherit inputs;}; forEachPkgs = util.forEachPkgs; - mkNixosInstaller = util.mkNixosInstaller; mkNixosSystem = util.mkNixosSystem; mkDarwinSystem = util.mkDarwinSystem; mkHome = util.mkHome; - syncthingConfiguration = util.syncthingConfiguration; - - installerSystems = { - basic = mkNixosInstaller "basic" []; - }; nixosSystems = { horizon = mkNixosSystem "horizon"; @@ -131,11 +139,11 @@ systemsHomes // homeSystems; in { - formatter = forEachPkgs (pkgs: pkgs.alejandra); + formatter = forEachPkgs (system: pkgs: pkgs.alejandra); # templates = import ./templates; - devShells = forEachPkgs (pkgs: { + devShells = forEachPkgs (system: pkgs: { default = pkgs.mkShell { packages = with pkgs; [ # for version controlling this repo @@ -150,6 +158,10 @@ nixos-anywhere # for updating disko configurations disko + # for viewing dconf entries + dconf-editor + # for MCP NixOS server support in development + inputs.mcp-nixos.packages.${system}.default ]; SOPS_AGE_KEY_DIRECTORY = import ./const/sops_age_key_directory.nix; @@ -160,14 +172,10 @@ }; }); - installerConfigurations = installerSystems; - nixosConfigurations = nixosSystems; darwinConfigurations = darwinSystems; homeConfigurations = homeConfigurations; - - syncthingConfiguration = syncthingConfiguration; }; } diff --git a/modules/common-modules/overlays/default.nix b/modules/common-modules/overlays/default.nix index 08085f5..3def9e9 100644 --- a/modules/common-modules/overlays/default.nix +++ b/modules/common-modules/overlays/default.nix @@ -1,3 +1,10 @@ # this folder is for derivation overlays -{...}: { +{inputs, ...}: { + nixpkgs.overlays = [ + inputs.nix-vscode-extensions.overlays.default + # Add noita_entangled_worlds from upstream flake to pkgs + (final: prev: { + noita_entangled_worlds = inputs.noita-entangled-worlds.packages.${prev.stdenv.hostPlatform.system}.noita-proxy; + }) + ]; } diff --git a/modules/common-modules/pkgs/cline/cline-package-lock.json b/modules/common-modules/pkgs/cline/cline-package-lock.json new file mode 100644 index 0000000..b8d0d6e --- /dev/null +++ b/modules/common-modules/pkgs/cline/cline-package-lock.json @@ -0,0 +1,4102 @@ +{ + "name": "cline", + "version": "2.4.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cline", + "version": "2.4.2", + "cpu": [ + "x64", + "arm64" + ], + "license": "Apache-2.0", + "os": [ + "darwin", + "linux", + "win32" + ], + "dependencies": { + "@agentclientprotocol/sdk": "^0.13.1", + "aws4fetch": "^1.0.20", + "chalk": "^5.3.0", + "commander": "^12.1.0", + "ink": "npm:@jrichman/ink@6.4.7", + "ink-picture": "^1.3.3", + "ink-spinner": "^5.0.0", + "nanoid": "^5.1.6", + "ora": "^8.0.1", + "pino": "^10.0.0", + "pino-roll": "^4.0.0", + "prompts": "^2.4.2", + "react": "^19.2.3" + }, + "bin": { + "cline": "dist/cli.mjs" + }, + "devDependencies": { + "@types/node": "20.x", + "@types/prompts": "^2.4.9", + "@types/react": "^19.2.9", + "dotenv": "^16.4.5", + "esbuild": "^0.25.0", + "ink-testing-library": "^4.0.0", + "rimraf": "^6.0.1", + "typescript": "^5.4.5", + "vitest": "^4.0.17" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@agentclientprotocol/sdk": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.13.1.tgz", + "integrity": "sha512-6byvu+F/xc96GBkdAx4hq6/tB3vT63DSBO4i3gYCz8nuyZMerVFna2Gkhm8EHNpZX0J9DjUxzZCW+rnHXUg0FA==", + "license": "Apache-2.0", + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz", + "integrity": "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.58.0.tgz", + "integrity": "sha512-mr0tmS/4FoVk1cnaeN244A/wjvGDNItZKR8hRhnmCzygyRXYtKF5jVDSIILR1U97CTzAYmbgIj/Dukg62ggG5w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.58.0.tgz", + "integrity": "sha512-+s++dbp+/RTte62mQD9wLSbiMTV+xr/PeRJEc/sFZFSBRlHPNPVaf5FXlzAL77Mr8FtSfQqCN+I598M8U41ccQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.58.0.tgz", + "integrity": "sha512-MFWBwTcYs0jZbINQBXHfSrpSQJq3IUOakcKPzfeSznONop14Pxuqa0Kg19GD0rNBMPQI2tFtu3UzapZpH0Uc1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.58.0.tgz", + "integrity": "sha512-yiKJY7pj9c9JwzuKYLFaDZw5gma3fI9bkPEIyofvVfsPqjCWPglSHdpdwXpKGvDeYDms3Qal8qGMEHZ1M/4Udg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.58.0.tgz", + "integrity": "sha512-x97kCoBh5MOevpn/CNK9W1x8BEzO238541BGWBc315uOlN0AD/ifZ1msg+ZQB05Ux+VF6EcYqpiagfLJ8U3LvQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.58.0.tgz", + "integrity": "sha512-Aa8jPoZ6IQAG2eIrcXPpjRcMjROMFxCt1UYPZZtCxRV68WkuSigYtQ/7Zwrcr2IvtNJo7T2JfDXyMLxq5L4Jlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.58.0.tgz", + "integrity": "sha512-Ob8YgT5kD/lSIYW2Rcngs5kNB/44Q2RzBSPz9brf2WEtcGR7/f/E9HeHn1wYaAwKBni+bdXEwgHvUd0x12lQSA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.58.0.tgz", + "integrity": "sha512-K+RI5oP1ceqoadvNt1FecL17Qtw/n9BgRSzxif3rTL2QlIu88ccvY+Y9nnHe/cmT5zbH9+bpiJuG1mGHRVwF4Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.58.0.tgz", + "integrity": "sha512-T+17JAsCKUjmbopcKepJjHWHXSjeW7O5PL7lEFaeQmiVyw4kkc5/lyYKzrv6ElWRX/MrEWfPiJWqbTvfIvjM1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.58.0.tgz", + "integrity": "sha512-cCePktb9+6R9itIJdeCFF9txPU7pQeEHB5AbHu/MKsfH/k70ZtOeq1k4YAtBv9Z7mmKI5/wOLYjQ+B9QdxR6LA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.58.0.tgz", + "integrity": "sha512-iekUaLkfliAsDl4/xSdoCJ1gnnIXvoNz85C8U8+ZxknM5pBStfZjeXgB8lXobDQvvPRCN8FPmmuTtH+z95HTmg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.58.0.tgz", + "integrity": "sha512-68ofRgJNl/jYJbxFjCKE7IwhbfxOl1muPN4KbIqAIe32lm22KmU7E8OPvyy68HTNkI2iV/c8y2kSPSm2mW/Q9Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.58.0.tgz", + "integrity": "sha512-dpz8vT0i+JqUKuSNPCP5SYyIV2Lh0sNL1+FhM7eLC457d5B9/BC3kDPp5BBftMmTNsBarcPcoz5UGSsnCiw4XQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.58.0.tgz", + "integrity": "sha512-4gdkkf9UJ7tafnweBCR/mk4jf3Jfl0cKX9Np80t5i78kjIH0ZdezUv/JDI2VtruE5lunfACqftJ8dIMGN4oHew==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.58.0.tgz", + "integrity": "sha512-YFS4vPnOkDTD/JriUeeZurFYoJhPf9GQQEF/v4lltp3mVcBmnsAdjEWhr2cjUCZzZNzxCG0HZOvJU44UGHSdzw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.58.0.tgz", + "integrity": "sha512-x2xgZlFne+QVNKV8b4wwaCS8pwq3y14zedZ5DqLzjdRITvreBk//4Knbcvm7+lWmms9V9qFp60MtUd0/t/PXPw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.58.0.tgz", + "integrity": "sha512-jIhrujyn4UnWF8S+DHSkAkDEO3hLX0cjzxJZPLF80xFyzyUIYgSMRcYQ3+uqEoyDD2beGq7Dj7edi8OnJcS/hg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.58.0.tgz", + "integrity": "sha512-+410Srdoh78MKSJxTQ+hZ/Mx+ajd6RjjPwBPNd0R3J9FtL6ZA0GqiiyNjCO9In0IzZkCNrpGymSfn+kgyPQocg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.58.0.tgz", + "integrity": "sha512-ZjMyby5SICi227y1MTR3VYBpFTdZs823Rs/hpakufleBoufoOIB6jtm9FEoxn/cgO7l6PM2rCEl5Kre5vX0QrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.58.0.tgz", + "integrity": "sha512-ds4iwfYkSQ0k1nb8LTcyXw//ToHOnNTJtceySpL3fa7tc/AsE+UpUFphW126A6fKBGJD5dhRvg8zw1rvoGFxmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.58.0.tgz", + "integrity": "sha512-fd/zpJniln4ICdPkjWFhZYeY/bpnaN9pGa6ko+5WD38I0tTqk9lXMgXZg09MNdhpARngmxiCg0B0XUamNw/5BQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.58.0.tgz", + "integrity": "sha512-YpG8dUOip7DCz3nr/JUfPbIUo+2d/dy++5bFzgi4ugOGBIox+qMbbqt/JoORwvI/C9Kn2tz6+Bieoqd5+B1CjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.58.0.tgz", + "integrity": "sha512-b9DI8jpFQVh4hIXFr0/+N/TzLdpBIoPzjt0Rt4xJbW3mzguV3mduR9cNgiuFcuL/TeORejJhCWiAXe3E/6PxWA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.58.0.tgz", + "integrity": "sha512-CSrVpmoRJFN06LL9xhkitkwUcTZtIotYAF5p6XOR2zW0Zz5mzb3IPpcoPhB02frzMHFNo1reQ9xSF5fFm3hUsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.58.0.tgz", + "integrity": "sha512-QFsBgQNTnh5K0t/sBsjJLq24YVqEIVkGpfN2VHsnN90soZyhaiA9UUHufcctVNL4ypJY0wrwad0wslx2KJQ1/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prompts": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.9.tgz", + "integrity": "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "kleur": "^3.0.3" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/app-path/-/app-path-4.0.0.tgz", + "integrity": "sha512-mgBO9PZJ3MpbKbwFTljTi36ZKBvG5X/fkVR1F85ANsVcVllEb+C0LGNdJfGUm84GpC4xxgN6HFkmkMU8VEO4mA==", + "license": "MIT", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/aws4fetch": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/aws4fetch/-/aws4fetch-1.0.20.tgz", + "integrity": "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", + "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-toolkit": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.44.0.tgz", + "integrity": "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink": { + "name": "@jrichman/ink", + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.7.tgz", + "integrity": "sha512-QHyxhNF5VonF5cRmdAJD/UPucB9nRx3FozWMjQrDGfBxfAL9lpyu72/MlFPgloS1TMTGsOt7YN6dTPPA6mh0Aw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.2.1", + "ansi-escapes": "^7.0.0", + "ansi-styles": "^6.2.1", + "auto-bind": "^5.0.1", + "chalk": "^5.6.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^4.0.0", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.39.10", + "indent-string": "^5.0.0", + "is-in-ci": "^2.0.0", + "mnemonist": "^0.40.3", + "patch-console": "^2.0.0", + "react-reconciler": "^0.32.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^7.1.0", + "stack-utils": "^2.0.6", + "string-width": "^8.1.0", + "type-fest": "^4.27.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@types/react": ">=19.0.0", + "react": ">=19.0.0", + "react-devtools-core": "^6.1.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ink-picture": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/ink-picture/-/ink-picture-1.3.3.tgz", + "integrity": "sha512-kFDZaiqnvbM2cU46ptIHFeh4RJkfsZfQUh657JmbOmD/swANdCkUvcb8c3Qv6270qFWKwRehf04fghMwtHwEGw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.6.0", + "is-unicode-supported": "^2.1.0", + "iterm2-version": "^5.0.0", + "node-fetch": "^3.3.2", + "sharp": "^0.34.3", + "sixel": "^0.16.0", + "supports-color": "^10.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "ink": ">=5", + "react": ">=18" + } + }, + "node_modules/ink-spinner": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ink-spinner/-/ink-spinner-5.0.0.tgz", + "integrity": "sha512-EYEasbEjkqLGyPOUc8hBJZNuC5GvXGMLu0w5gdTNskPc7Izc5vO3tdQEYnzvshucyGCBXc86ig0ujXPMWaQCdA==", + "license": "MIT", + "dependencies": { + "cli-spinners": "^2.7.0" + }, + "engines": { + "node": ">=14.16" + }, + "peerDependencies": { + "ink": ">=4.0.0", + "react": ">=18.0.0" + } + }, + "node_modules/ink-testing-library": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ink-testing-library/-/ink-testing-library-4.0.0.tgz", + "integrity": "sha512-yF92kj3pmBvk7oKbSq5vEALO//o7Z9Ck/OaLNlkzXNeYdwfpxMQkSowGTFUCS5MSu9bWfSZMewGpp7bFc66D7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": ">=18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-in-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", + "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/iterm2-version": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/iterm2-version/-/iterm2-version-5.0.0.tgz", + "integrity": "sha512-WdLXcMYvN3SXT6vEtuW78vnZs4pVWm2nBnb4VKjOPPXmdlR1xTHmBgqKacOzAe4RXOiY/V+0u/0zsU3LoGQoBg==", + "license": "MIT", + "dependencies": { + "app-path": "^4.0.0", + "plist": "^3.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mnemonist": { + "version": "0.40.3", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.40.3.tgz", + "integrity": "sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ==", + "license": "MIT", + "dependencies": { + "obliterator": "^2.0.4" + } + }, + "node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/obliterator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz", + "integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==", + "license": "MIT" + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ora/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-roll": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pino-roll/-/pino-roll-4.0.0.tgz", + "integrity": "sha512-axI1aQaIxXdw1F4OFFli1EDxIrdYNGLowkw/ZoZogX8oCSLHUghzwVVXUS8U+xD/Savwa5IXpiXmsSGKFX/7Sg==", + "license": "MIT", + "dependencies": { + "date-fns": "^4.1.0", + "sonic-boom": "^4.0.1" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.32.0.tgz", + "integrity": "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rimraf": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", + "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.3", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.58.0.tgz", + "integrity": "sha512-wbT0mBmWbIvvq8NeEYWWvevvxnOyhKChir47S66WCxw1SXqhw7ssIYejnQEVt7XYQpsj2y8F9PM+Cr3SNEa0gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.58.0", + "@rollup/rollup-android-arm64": "4.58.0", + "@rollup/rollup-darwin-arm64": "4.58.0", + "@rollup/rollup-darwin-x64": "4.58.0", + "@rollup/rollup-freebsd-arm64": "4.58.0", + "@rollup/rollup-freebsd-x64": "4.58.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.58.0", + "@rollup/rollup-linux-arm-musleabihf": "4.58.0", + "@rollup/rollup-linux-arm64-gnu": "4.58.0", + "@rollup/rollup-linux-arm64-musl": "4.58.0", + "@rollup/rollup-linux-loong64-gnu": "4.58.0", + "@rollup/rollup-linux-loong64-musl": "4.58.0", + "@rollup/rollup-linux-ppc64-gnu": "4.58.0", + "@rollup/rollup-linux-ppc64-musl": "4.58.0", + "@rollup/rollup-linux-riscv64-gnu": "4.58.0", + "@rollup/rollup-linux-riscv64-musl": "4.58.0", + "@rollup/rollup-linux-s390x-gnu": "4.58.0", + "@rollup/rollup-linux-x64-gnu": "4.58.0", + "@rollup/rollup-linux-x64-musl": "4.58.0", + "@rollup/rollup-openbsd-x64": "4.58.0", + "@rollup/rollup-openharmony-arm64": "4.58.0", + "@rollup/rollup-win32-arm64-msvc": "4.58.0", + "@rollup/rollup-win32-ia32-msvc": "4.58.0", + "@rollup/rollup-win32-x64-gnu": "4.58.0", + "@rollup/rollup-win32-x64-msvc": "4.58.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/sixel": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/sixel/-/sixel-0.16.0.tgz", + "integrity": "sha512-xicu6Y6Cyhmv5rjyHxq2r5RnKerlL/nyZEGjOU5bLCshXkZryc9JFJThTCKPOAtWXCfeWquEKFVFfMPcTD25PA==", + "license": "MIT" + }, + "node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", + "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/thread-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", + "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/modules/common-modules/pkgs/cline/default.nix b/modules/common-modules/pkgs/cline/default.nix new file mode 100644 index 0000000..05dbf48 --- /dev/null +++ b/modules/common-modules/pkgs/cline/default.nix @@ -0,0 +1,53 @@ +{ + lib, + buildNpmPackage, + fetchurl, + ripgrep, + makeWrapper, + jq, + ... +}: +buildNpmPackage rec { + pname = "cline"; + version = "2.4.2"; + + src = fetchurl { + url = "https://registry.npmjs.org/cline/-/cline-${version}.tgz"; + hash = "sha256-2utOBC0vhoj5fR+cG+Vdo3N6+i/pNW1E4mESF/dZS/c="; + }; + + sourceRoot = "package"; + + postPatch = '' + cp ${./cline-package-lock.json} package-lock.json + + # Remove @vscode/ripgrep from package.json since it tries to download + # a binary from GitHub during install, which fails in the nix sandbox. + # We provide ripgrep from nixpkgs instead via PATH wrapping. + # Also remove the man field since the man page is not included in the npm tarball. + ${jq}/bin/jq 'del(.dependencies["@vscode/ripgrep"]) | del(.man)' package.json > package.json.tmp + mv package.json.tmp package.json + ''; + + npmDepsHash = "sha256-oHo60ghR7A4SUT0cLmIe7glPDYBK3twJ0F71RKVrxQc="; + + dontNpmBuild = true; + + # Skip post-install scripts to be safe + npmFlags = ["--ignore-scripts"]; + + nativeBuildInputs = [makeWrapper jq]; + + # Provide ripgrep from nixpkgs since @vscode/ripgrep was removed + postInstall = '' + wrapProgram $out/bin/cline \ + --prefix PATH : ${lib.makeBinPath [ripgrep]} + ''; + + meta = with lib; { + description = "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more"; + homepage = "https://cline.bot"; + license = licenses.asl20; + mainProgram = "cline"; + }; +} diff --git a/modules/common-modules/pkgs/codium-extensions/ai-code.nix b/modules/common-modules/pkgs/codium-extensions/ai-code.nix new file mode 100644 index 0000000..9c9efe3 --- /dev/null +++ b/modules/common-modules/pkgs/codium-extensions/ai-code.nix @@ -0,0 +1,42 @@ +{ + buildNpmPackage, + vscode-utils, + pkgs, + ... +}: let + version = "0.0.1"; + pname = "ai-code"; + publisher = "jan-leila"; + vsix = buildNpmPackage { + inherit version pname; + + src = builtins.fetchGit { + url = "ssh://git@git.jan-leila.com/jan-leila/ai-code.git"; + rev = "d48e01713021dbb30de0ebbee2cfaf99e4e9b5a6"; + }; + + npmDepsHash = "sha256-kjMyEnT3dz0yH5Ydh+aGoFDocKpBYGRmfnwbEdvvgpY="; + + nativeBuildInputs = with pkgs; [ + vsce + ]; + + buildPhase = '' + ${pkgs.vsce}/bin/vsce package -o ${pname}.zip + ''; + + installPhase = '' + mkdir -p $out + mv ${pname}.zip $out/${pname}.zip + ''; + }; +in + vscode-utils.buildVscodeExtension { + inherit pname version; + + src = "${vsix}/${pname}.zip"; + + vscodeExtUniqueId = "${publisher}.${pname}"; + vscodeExtPublisher = publisher; + vscodeExtName = pname; + } diff --git a/modules/common-modules/pkgs/codium-extensions/default.nix b/modules/common-modules/pkgs/codium-extensions/default.nix new file mode 100644 index 0000000..a60e8a0 --- /dev/null +++ b/modules/common-modules/pkgs/codium-extensions/default.nix @@ -0,0 +1,3 @@ +{pkgs, ...}: { + ai-code = pkgs.callPackage ./ai-code.nix {}; +} diff --git a/modules/common-modules/pkgs/default.nix b/modules/common-modules/pkgs/default.nix index 3e4456b..c1e5e80 100644 --- a/modules/common-modules/pkgs/default.nix +++ b/modules/common-modules/pkgs/default.nix @@ -1,4 +1,54 @@ -# this folder is for custom derivations -{...}: { - # package = pkgs.callPackage ./package.nix {}; +{ + pkgs, + inputs, + ... +}: { + imports = [ + ./python + ]; + + nixpkgs.overlays = [ + (final: prev: { + webtoon-dl = + pkgs.callPackage + ./webtoon-dl.nix + {}; + }) + (final: prev: { + prostudiomasters = + pkgs.callPackage + ./prostudiomasters.nix + {}; + }) + (final: prev: { + gdx-liftoff = pkgs.callPackage ./gdx-liftoff.nix {}; + }) + (final: prev: { + codium-extensions = pkgs.callPackage ./codium-extensions {}; + }) + (final: prev: { + firefox-extensions = pkgs.callPackage ./firefox-extensions { + inherit inputs; + }; + }) + (final: prev: { + mapillary-uploader = pkgs.callPackage ./mapillary-uploader.nix {}; + }) + (final: prev: { + panoramax = pkgs.python3.pkgs.callPackage ./panoramax.nix {}; + }) + (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 {}; + }) + (final: prev: { + cline = pkgs.callPackage ./cline/default.nix {}; + }) + (final: prev: { + e621-downloader = pkgs.callPackage ./e621-downloader.nix {}; + }) + ]; } diff --git a/modules/common-modules/pkgs/e621-downloader.nix b/modules/common-modules/pkgs/e621-downloader.nix new file mode 100644 index 0000000..3e7c546 --- /dev/null +++ b/modules/common-modules/pkgs/e621-downloader.nix @@ -0,0 +1,36 @@ +{ + lib, + rustPlatform, + fetchFromGitHub, + pkg-config, + openssl, + ... +}: +rustPlatform.buildRustPackage rec { + pname = "e621-downloader"; + version = "1.7.2"; + + src = fetchFromGitHub { + owner = "McSib"; + repo = "e621_downloader"; + rev = version; + hash = "sha256-4z+PrCv8Mlp0VOJ5Akv1TXrJir1Ws/+45a6VCZGuCtk="; + }; + + cargoHash = "sha256-/yqNYjP7BuFQWilL2Ty+E5rd8qXj30twteptHx7cLRo="; + + nativeBuildInputs = [ + pkg-config + ]; + + buildInputs = [ + openssl + ]; + + meta = with lib; { + description = "E621 and E926 downloader made in Rust"; + homepage = "https://github.com/McSib/e621_downloader"; + license = licenses.asl20; + mainProgram = "e621_downloader"; + }; +} diff --git a/modules/common-modules/pkgs/firefox-extensions/default.nix b/modules/common-modules/pkgs/firefox-extensions/default.nix new file mode 100644 index 0000000..922dfc7 --- /dev/null +++ b/modules/common-modules/pkgs/firefox-extensions/default.nix @@ -0,0 +1,17 @@ +{ + pkgs, + inputs, + ... +}: let + inherit (inputs.firefox-addons.lib.${pkgs.stdenv.hostPlatform.system}) buildFirefoxXpiAddon; +in { + italiano-it-language-pack = pkgs.callPackage ./italiano-it-language-pack.nix { + inherit buildFirefoxXpiAddon; + }; + dizionario-italiano = pkgs.callPackage ./dizionario-italiano.nix { + inherit buildFirefoxXpiAddon; + }; + deutsch-de-language-pack = pkgs.callPackage ./deutsch-de-language-pack.nix { + inherit buildFirefoxXpiAddon; + }; +} diff --git a/modules/common-modules/pkgs/firefox-extensions/deutsch-de-language-pack.nix b/modules/common-modules/pkgs/firefox-extensions/deutsch-de-language-pack.nix new file mode 100644 index 0000000..b769bfd --- /dev/null +++ b/modules/common-modules/pkgs/firefox-extensions/deutsch-de-language-pack.nix @@ -0,0 +1,18 @@ +{ + lib, + buildFirefoxXpiAddon, + ... +}: +buildFirefoxXpiAddon rec { + pname = "deutsch-de-language-pack"; + version = "145.0.20251106.194447"; + addonId = "langpack-de@firefox.mozilla.org"; + url = "https://addons.mozilla.org/firefox/downloads/file/4614311/deutsch_de_language_pack-${version}.xpi"; + sha256 = "aaaa95c29984fb3802a5e7edb6b7e5020c391d81f389b8a8133c163959ea4299"; + meta = with lib; { + description = "Firefox Language Pack for Deutsch (de) – German"; + license = licenses.mpl20; + mozPermissions = []; + platforms = platforms.all; + }; +} diff --git a/modules/common-modules/pkgs/firefox-extensions/dizionario-italiano.nix b/modules/common-modules/pkgs/firefox-extensions/dizionario-italiano.nix new file mode 100644 index 0000000..4bfca14 --- /dev/null +++ b/modules/common-modules/pkgs/firefox-extensions/dizionario-italiano.nix @@ -0,0 +1,18 @@ +{ + lib, + buildFirefoxXpiAddon, + ... +}: +buildFirefoxXpiAddon rec { + pname = "dizionario-italiano"; + version = "5.1"; + addonId = "it-IT@dictionaries.addons.mozilla.org"; + url = "https://addons.mozilla.org/firefox/downloads/file/3693497/dizionario_italiano-${version}.xpi"; + sha256 = "90b173ffdde34a77108152a5ff51879767b1dd84e0aa0dfb7b2bab94cd2e7f53"; + meta = with lib; { + description = "Add support for Italian to spellchecking"; + license = licenses.gpl3; + mozPermissions = []; + platforms = platforms.all; + }; +} diff --git a/modules/common-modules/pkgs/firefox-extensions/italiano-it-language-pack.nix b/modules/common-modules/pkgs/firefox-extensions/italiano-it-language-pack.nix new file mode 100644 index 0000000..35f4243 --- /dev/null +++ b/modules/common-modules/pkgs/firefox-extensions/italiano-it-language-pack.nix @@ -0,0 +1,18 @@ +{ + lib, + buildFirefoxXpiAddon, + ... +}: +buildFirefoxXpiAddon rec { + pname = "italiano-it-language-pack"; + version = "145.0.20251106.194447"; + addonId = "langpack-it@firefox.mozilla.org"; + url = "https://addons.mozilla.org/firefox/downloads/file/4614309/italiano_it_language_pack-${version}.xpi"; + sha256 = "1eb271cedbf326543e222ba1b9a1da62fceef9d3c523ac02a098df296f155038"; + meta = with lib; { + description = "Firefox Language Pack for Italiano (it) – Italian"; + license = licenses.mpl20; + mozPermissions = []; + platforms = platforms.all; + }; +} diff --git a/modules/common-modules/pkgs/gdx-liftoff.nix b/modules/common-modules/pkgs/gdx-liftoff.nix new file mode 100644 index 0000000..da7d51f --- /dev/null +++ b/modules/common-modules/pkgs/gdx-liftoff.nix @@ -0,0 +1,48 @@ +{ + stdenv, + fetchurl, + makeWrapper, + jdk, + lib, + libGL, + libx11, + libxcursor, + libxext, + libxrandr, + libxxf86vm, + ... +}: +stdenv.mkDerivation rec { + pname = "gdx-liftoff"; + version = "1.13.5.1"; + + src = fetchurl { + url = "https://github.com/libgdx/gdx-liftoff/releases/download/v${version}/gdx-liftoff-${version}.jar"; + hash = "sha256-9vCXGNGwI/P4VmcdIzTv2GPAX8bZb7nkfopaRAf6yMA="; + }; + + dontUnpack = true; + + nativeBuildInputs = [makeWrapper]; + + runtimeDependencies = lib.makeLibraryPath [ + # glfw + libGL + libx11 + libxcursor + libxext + libxrandr + libxxf86vm + ]; + + installPhase = '' + runHook preInstall + + install -Dm644 $src $out/lib/gdx-liftoff-${version}.jar + + makeWrapper ${lib.getExe jdk} $out/bin/gdx-liftoff-${version} \ + --append-flags "-jar $out/lib/gdx-liftoff-${version}.jar"\ + ${lib.optionalString stdenv.hostPlatform.isLinux "--prefix LD_LIBRARY_PATH : ${runtimeDependencies}"} + runHook postInstall + ''; +} 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/mapillary-uploader.nix b/modules/common-modules/pkgs/mapillary-uploader.nix new file mode 100644 index 0000000..acff772 --- /dev/null +++ b/modules/common-modules/pkgs/mapillary-uploader.nix @@ -0,0 +1,39 @@ +{ + lib, + fetchurl, + appimageTools, +}: let + pname = "mapillary-uploader"; + version = "4.7.2"; + + src = fetchurl { + url = "http://tools.mapillary.com/uploader/download/linux/${version}"; + name = "mapillary-uploader.AppImage"; + sha256 = "sha256-hpWdfeuhYylO+SFD3BsKI0s/xtObCDd5OcuJ6i/aEuI="; + }; + + 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 + + # 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]; + }; + } diff --git a/modules/common-modules/pkgs/panoramax.nix b/modules/common-modules/pkgs/panoramax.nix new file mode 100644 index 0000000..75b5e0e --- /dev/null +++ b/modules/common-modules/pkgs/panoramax.nix @@ -0,0 +1,105 @@ +{ + lib, + fetchFromGitLab, + buildPythonPackage, + flit-core, + flask, + pillow, + requests, + python-dotenv, + 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"; + 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 + 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 + 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/common-modules/pkgs/prostudiomasters.nix b/modules/common-modules/pkgs/prostudiomasters.nix new file mode 100644 index 0000000..1a3ad01 --- /dev/null +++ b/modules/common-modules/pkgs/prostudiomasters.nix @@ -0,0 +1,33 @@ +{ + fetchurl, + appimageTools, + writeShellScript, +}: let + pname = "prostudiomasters"; + version = "2.5.6"; + src = fetchurl { + url = "https://download.prostudiomasters.com/linux/ProStudioMasters-${version}.AppImage"; + hash = "sha256-7owOwdcucFfl+JsVj+Seau2KOz0J4P/ep7WrBSNSmbs="; + }; + + # 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 + ''; + }) 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/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; + }; +} diff --git a/modules/nixos-modules/packages/webtoon-dl.nix b/modules/common-modules/pkgs/webtoon-dl.nix similarity index 100% rename from modules/nixos-modules/packages/webtoon-dl.nix rename to modules/common-modules/pkgs/webtoon-dl.nix diff --git a/modules/home-manager-modules/continue.nix b/modules/home-manager-modules/continue.nix deleted file mode 100644 index 327ee44..0000000 --- a/modules/home-manager-modules/continue.nix +++ /dev/null @@ -1,75 +0,0 @@ -{ - lib, - pkgs, - config, - osConfig, - ... -}: let - ai-tooling-enabled = config.user.continue.enable && osConfig.host.ai.enable; -in { - options = { - user.continue = { - enable = lib.mkEnableOption "should continue be enabled on this machine"; - docs = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { - options = { - name = lib.mkOption { - type = lib.types.str; - default = name; - }; - startUrl = lib.mkOption { - type = lib.types.str; - }; - }; - })); - }; - context = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { - options = { - provider = lib.mkOption { - type = lib.types.str; - default = name; - }; - }; - })); - default = { - "code" = {}; - "docs" = {}; - "diff" = {}; - "terminal" = {}; - "problems" = {}; - "folder" = {}; - "codebase" = {}; - }; - }; - }; - }; - - config = - lib.mkIf ai-tooling-enabled - (lib.mkMerge [ - { - home = { - file = { - ".continue/config.yaml".source = (pkgs.formats.yaml {}).generate "continue-config" { - name = "Assistant"; - version = "1.0.0"; - schema = "v1"; - models = lib.attrsets.attrValues osConfig.host.ai.models; - context = lib.attrsets.attrValues config.user.continue.context; - docs = lib.attrsets.attrValues config.user.continue.docs; - }; - }; - }; - } - (lib.mkIf osConfig.host.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { - directories = [ - ".continue/index" - ".continue/sessions" - ]; - allowOther = true; - }; - }) - ]); -} diff --git a/modules/home-manager-modules/default.nix b/modules/home-manager-modules/default.nix index ee47fb5..29d3414 100644 --- a/modules/home-manager-modules/default.nix +++ b/modules/home-manager-modules/default.nix @@ -1,9 +1,13 @@ # this folder container modules that are for home manager only {...}: { imports = [ + ./sops.nix + ./user.nix ./flipperzero.nix ./i18n.nix + ./impermanence.nix ./openssh.nix - ./continue.nix + ./gnome.nix + ./programs ]; } diff --git a/modules/home-manager-modules/gnome.nix b/modules/home-manager-modules/gnome.nix new file mode 100644 index 0000000..ab56189 --- /dev/null +++ b/modules/home-manager-modules/gnome.nix @@ -0,0 +1,203 @@ +{ + lib, + config, + pkgs, + ... +}: let + enabledExtensions = + [] + ++ lib.optional config.gnome.extensions.dash-to-dock.enable pkgs.gnomeExtensions.dash-to-dock + ++ lib.optional config.gnome.extensions.dash-to-panel.enable pkgs.gnomeExtensions.dash-to-panel; + + extensions = config.gnome.extraExtensions ++ enabledExtensions; +in { + options.gnome = { + extraWindowControls = lib.mkEnableOption "Should we add back in the minimize and maximize window controls?"; + clockFormat = lib.mkOption { + type = lib.types.enum [ + "12h" + "24h" + ]; + default = "24h"; + }; + colorScheme = lib.mkOption { + type = lib.types.enum [ + "default" + "prefer-dark" + "prefer-light" + ]; + default = "default"; + }; + accentColor = lib.mkOption { + type = lib.types.enum [ + "blue" + "teal" + "green" + "yellow" + "orange" + "red" + "pink" + "purple" + "slate" + ]; + default = "blue"; + }; + extraExtensions = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = []; + description = "The set of extensions to install and enable in the user environment."; + }; + hotkeys = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { + options = { + key = lib.mkOption { + type = lib.types.strMatching "[a-zA-Z0-9-]+"; + default = builtins.replaceStrings [" " "/" "_"] ["-" "-" "-"] name; + }; + name = lib.mkOption { + type = lib.types.str; + default = name; + }; + binding = lib.mkOption { + type = lib.types.str; + }; + command = lib.mkOption { + type = lib.types.str; + }; + }; + })); + default = {}; + }; + displayScaling = lib.mkOption { + type = lib.types.nullOr (lib.types.enum [100 125 150 175 200]); + default = null; + description = "Display scaling percentage for GNOME"; + }; + experimentalFeatures = lib.mkOption { + type = lib.types.submodule { + options = { + scaleMonitorFramebuffer = lib.mkEnableOption "scale-monitor-framebuffer experimental feature"; + }; + }; + default = {}; + description = "GNOME experimental features to enable"; + }; + + nightLight = lib.mkOption { + type = lib.types.submodule { + options = { + enable = lib.mkEnableOption "night light (blue light filter)"; + automatic = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to automatically schedule night light based on sunset/sunrise"; + }; + fromTime = lib.mkOption { + type = lib.types.float; + default = 20.0; + description = "Start time for night light in 24-hour format (e.g., 20.0 for 8:00 PM)"; + }; + toTime = lib.mkOption { + type = lib.types.float; + default = 6.0; + description = "End time for night light in 24-hour format (e.g., 6.0 for 6:00 AM)"; + }; + temperature = lib.mkOption { + type = lib.types.int; + default = 4000; + description = "Color temperature for night light (1000-10000K, lower is warmer)"; + }; + }; + }; + default = {}; + description = "Night light configuration"; + }; + + extensions = { + dash-to-dock = { + enable = lib.mkEnableOption "Dash to Dock extension"; + options = lib.mkOption { + type = lib.types.nullOr lib.types.attrs; + default = null; + description = "Dash to Dock configuration options. If null, no custom configuration will be applied."; + }; + }; + + dash-to-panel = { + enable = lib.mkEnableOption "Dash to Panel extension"; + options = lib.mkOption { + type = lib.types.nullOr lib.types.attrs; + default = null; + description = "Dash to Panel configuration options. If null, no custom configuration will be applied."; + }; + }; + }; + }; + + config = { + home.packages = extensions; + dconf = { + settings = lib.mkMerge [ + { + "org/gnome/shell" = { + disable-user-extensions = false; # enables user extensions + enabled-extensions = builtins.map (extension: extension.extensionUuid) extensions; + }; + + "org/gnome/desktop/wm/preferences".button-layout = lib.mkIf config.gnome.extraWindowControls ":minimize,maximize,close"; + + "org/gnome/desktop/interface".color-scheme = config.gnome.colorScheme; + "org/gnome/desktop/interface".accent-color = config.gnome.accentColor; + "org/gnome/desktop/interface".clock-format = config.gnome.clockFormat; + "org/gnome/desktop/interface".text-scaling-factor = lib.mkIf (config.gnome.displayScaling != null) (config.gnome.displayScaling / 100.0); + + "org/gnome/mutter".experimental-features = lib.mkIf (builtins.any (x: x) (builtins.attrValues config.gnome.experimentalFeatures)) ( + lib.optional config.gnome.experimentalFeatures.scaleMonitorFramebuffer "scale-monitor-framebuffer" + ); + } + + # Night light configuration + (lib.mkIf config.gnome.nightLight.enable { + "org/gnome/settings-daemon/plugins/color" = { + night-light-enabled = true; + night-light-schedule-automatic = config.gnome.nightLight.automatic; + night-light-schedule-from = lib.mkIf (!config.gnome.nightLight.automatic) config.gnome.nightLight.fromTime; + night-light-schedule-to = lib.mkIf (!config.gnome.nightLight.automatic) config.gnome.nightLight.toTime; + night-light-temperature = config.gnome.nightLight.temperature; + }; + }) + ( + lib.mkMerge ( + builtins.map (value: let + entry = "org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/${value.key}"; + in { + ${entry} = { + binding = value.binding; + command = value.command; + name = value.name; + }; + + "org/gnome/settings-daemon/plugins/media-keys" = { + custom-keybindings = [ + "/${entry}/" + ]; + }; + }) + ( + lib.attrsets.mapAttrsToList (_: value: value) config.gnome.hotkeys + ) + ) + ) + + # Extension configurations + (lib.mkIf (config.gnome.extensions.dash-to-dock.enable && config.gnome.extensions.dash-to-dock.options != null) { + "org/gnome/shell/extensions/dash-to-dock" = config.gnome.extensions.dash-to-dock.options; + }) + + (lib.mkIf (config.gnome.extensions.dash-to-panel.enable && config.gnome.extensions.dash-to-panel.options != null) { + "org/gnome/shell/extensions/dash-to-panel" = config.gnome.extensions.dash-to-panel.options; + }) + ]; + }; + }; +} diff --git a/modules/home-manager-modules/impermanence.nix b/modules/home-manager-modules/impermanence.nix new file mode 100644 index 0000000..e8b3ec4 --- /dev/null +++ b/modules/home-manager-modules/impermanence.nix @@ -0,0 +1,43 @@ +{ + config, + lib, + osConfig, + ... +}: let + cfg = config.impermanence; +in { + options.impermanence = { + enable = lib.mkEnableOption "impermanence for home directory"; + fallbackPersistence.enable = lib.mkOption { + type = lib.types.bool; + default = true; + }; + persistencePath = lib.mkOption { + type = lib.types.str; + default = + if osConfig.storage.generateBase + then "/persist/replicate/home" + else "/persist"; + description = "The base path for user home persistence. The impermanence module will automatically append the user's home directory path. Automatically adapts based on whether the system uses the new dataset layout or the legacy one."; + }; + }; + + config = lib.mkMerge [ + (lib.mkIf config.impermanence.enable { + assertions = [ + { + assertion = osConfig.storage.impermanence.enable; + message = "impermanence can not be enabled for a user when it is not enabled for the system"; + } + ]; + }) + # If impermanence is not enabled for this user but system impermanence is enabled, + # persist the entire home directory as fallback + (lib.mkIf (osConfig.storage.impermanence.enable && !cfg.enable && cfg.fallbackPersistence.enable) { + home.persistence."${cfg.persistencePath}" = { + directories = ["."]; + allowOther = true; + }; + }) + ]; +} diff --git a/modules/home-manager-modules/openssh.nix b/modules/home-manager-modules/openssh.nix index 7b646b8..2f44957 100644 --- a/modules/home-manager-modules/openssh.nix +++ b/modules/home-manager-modules/openssh.nix @@ -6,6 +6,7 @@ ... }: { options.programs.openssh = { + enable = lib.mkEnableOption "should we enable openssh"; authorizedKeys = lib.mkOption { type = lib.types.listOf lib.types.str; default = []; @@ -37,63 +38,70 @@ }; }; - config = lib.mkMerge [ - ( - lib.mkIf ((builtins.length config.programs.openssh.hostKeys) != 0) { - services.ssh-agent.enable = true; - programs.ssh = { - enable = true; - compression = true; - addKeysToAgent = "confirm"; - extraConfig = lib.strings.concatLines ( - builtins.map (hostKey: "IdentityFile ~/.ssh/${hostKey.path}") config.programs.openssh.hostKeys + config = lib.mkIf config.programs.openssh.enable ( + lib.mkMerge [ + ( + lib.mkIf ((builtins.length config.programs.openssh.hostKeys) != 0) { + services.ssh-agent.enable = true; + programs.ssh = { + enable = true; + enableDefaultConfig = false; + matchBlocks = { + "*" = { + compression = true; + addKeysToAgent = "confirm"; + }; + }; + # extraConfig = lib.strings.concatLines ( + # builtins.map (hostKey: "IdentityFile ~/.ssh/${hostKey.path}") config.programs.openssh.hostKeys + # ); + }; + + systemd.user.services = builtins.listToAttrs ( + builtins.map (hostKey: + lib.attrsets.nameValuePair "ssh-gen-keys-${hostKey.path}" { + Install = { + WantedBy = ["default.target"]; + }; + Service = let + path = "${config.home.homeDirectory}/.ssh/${hostKey.path}"; + in { + Restart = "always"; + Type = "simple"; + ExecStart = "${ + pkgs.writeShellScript "ssh-gen-keys" '' + if ! [ -s "${path}" ]; then + if ! [ -h "${path}" ]; then + rm -f "${path}" + fi + mkdir -p "$(dirname '${path}')" + chmod 0755 "$(dirname '${path}')" + ${pkgs.openssh}/bin/ssh-keygen \ + -t "${hostKey.type}" \ + ${lib.optionalString (hostKey ? bits) "-b ${toString hostKey.bits}"} \ + ${lib.optionalString (hostKey ? rounds) "-a ${toString hostKey.rounds}"} \ + ${lib.optionalString (hostKey ? comment) "-C '${hostKey.comment}'"} \ + ${lib.optionalString (hostKey ? openSSHFormat && hostKey.openSSHFormat) "-o"} \ + -f "${path}" \ + -N "" + chown ${config.home.username} ${path}* + chgrp ${config.home.username} ${path}* + fi + '' + }"; + }; + }) + config.programs.openssh.hostKeys + ); + } + ) + (lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + files = lib.lists.flatten ( + builtins.map (hostKey: [".ssh/${hostKey.path}" ".ssh/${hostKey.path}.pub"]) config.programs.openssh.hostKeys ); }; - - systemd.user.services = builtins.listToAttrs ( - builtins.map (hostKey: - lib.attrsets.nameValuePair "ssh-gen-keys-${hostKey.path}" { - Install = { - WantedBy = ["default.target"]; - }; - Service = let - path = "${config.home.homeDirectory}/.ssh/${hostKey.path}"; - in { - Restart = "always"; - Type = "simple"; - ExecStart = "${ - pkgs.writeShellScript "ssh-gen-keys" '' - if ! [ -s "${path}" ]; then - if ! [ -h "${path}" ]; then - rm -f "${path}" - fi - mkdir -p "$(dirname '${path}')" - chmod 0755 "$(dirname '${path}')" - ${pkgs.openssh}/bin/ssh-keygen \ - -t "${hostKey.type}" \ - ${lib.optionalString (hostKey ? bits) "-b ${toString hostKey.bits}"} \ - ${lib.optionalString (hostKey ? rounds) "-a ${toString hostKey.rounds}"} \ - ${lib.optionalString (hostKey ? comment) "-C '${hostKey.comment}'"} \ - ${lib.optionalString (hostKey ? openSSHFormat && hostKey.openSSHFormat) "-o"} \ - -f "${path}" \ - -N "" - chown ${config.home.username} ${path}* - chgrp ${config.home.username} ${path}* - fi - '' - }"; - }; - }) - config.programs.openssh.hostKeys - ); - } - ) - (lib.mkIf osConfig.host.impermanence.enable { - home.persistence."/persist${config.home.homeDirectory}" = { - files = lib.lists.flatten ( - builtins.map (hostKey: [".ssh/${hostKey.path}" ".ssh/${hostKey.path}.pub"]) config.programs.openssh.hostKeys - ); - }; - }) - ]; + }) + ] + ); } diff --git a/modules/home-manager-modules/programs/android-studio.nix b/modules/home-manager-modules/programs/android-studio.nix new file mode 100644 index 0000000..8d1e28c --- /dev/null +++ b/modules/home-manager-modules/programs/android-studio.nix @@ -0,0 +1,30 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.android-studio = { + enable = lib.mkEnableOption "enable android-studio"; + }; + + config = lib.mkIf config.programs.android-studio.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + android-studio + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/Google/AndroidStudio" + ".android" + ".gradle" + "${config.xdg.cacheHome}/Google/AndroidStudio" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/anki.nix b/modules/home-manager-modules/programs/anki.nix new file mode 100644 index 0000000..dcabce8 --- /dev/null +++ b/modules/home-manager-modules/programs/anki.nix @@ -0,0 +1,13 @@ +{ + lib, + config, + ... +}: { + config = lib.mkIf (config.programs.anki.enable && config.impermanence.enable) { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + ".local/share/Anki2" + ]; + }; + }; +} diff --git a/modules/home-manager-modules/programs/bitwarden.nix b/modules/home-manager-modules/programs/bitwarden.nix new file mode 100644 index 0000000..bbd2086 --- /dev/null +++ b/modules/home-manager-modules/programs/bitwarden.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.bitwarden = { + enable = lib.mkEnableOption "enable bitwarden"; + }; + + config = lib.mkIf config.programs.bitwarden.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + bitwarden-desktop + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/Bitwarden" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/bruno.nix b/modules/home-manager-modules/programs/bruno.nix new file mode 100644 index 0000000..7bc64b6 --- /dev/null +++ b/modules/home-manager-modules/programs/bruno.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.bruno = { + enable = lib.mkEnableOption "enable bruno"; + }; + + config = lib.mkIf config.programs.bruno.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + bruno + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/bruno/" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/calibre.nix b/modules/home-manager-modules/programs/calibre.nix new file mode 100644 index 0000000..7174b43 --- /dev/null +++ b/modules/home-manager-modules/programs/calibre.nix @@ -0,0 +1,23 @@ +{ + lib, + pkgs, + config, + ... +}: { + config = lib.mkIf config.programs.calibre.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + calibre + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/calibre" + ]; + }; + } + ) + ]); +} 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..5956578 --- /dev/null +++ b/modules/home-manager-modules/programs/davinci-resolve.nix @@ -0,0 +1,28 @@ +{ + lib, + pkgs, + config, + ... +}: { + 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 config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.dataHome}/DaVinciResolve" + "${config.xdg.configHome}/blackmagic" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/dbeaver.nix b/modules/home-manager-modules/programs/dbeaver.nix new file mode 100644 index 0000000..1595a02 --- /dev/null +++ b/modules/home-manager-modules/programs/dbeaver.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.dbeaver-bin = { + enable = lib.mkEnableOption "enable dbeaver"; + }; + + config = lib.mkIf config.programs.dbeaver-bin.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + dbeaver-bin + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.dataHome}/DBeaverData/" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/default.nix b/modules/home-manager-modules/programs/default.nix new file mode 100644 index 0000000..044d076 --- /dev/null +++ b/modules/home-manager-modules/programs/default.nix @@ -0,0 +1,55 @@ +{...}: { + imports = [ + ./android-studio.nix + ./firefox.nix + ./signal.nix + ./bitwarden.nix + ./makemkv.nix + ./obs.nix + ./anki.nix + ./piper.nix + ./qbittorrent.nix + ./discord.nix + ./obsidian.nix + ./prostudiomasters.nix + ./idea.nix + ./kdenlive.nix + ./kicad.nix + ./krita.nix + ./protonvpn.nix + ./calibre.nix + ./bruno.nix + ./dbeaver.nix + ./dungeon-draft.nix + ./steam.nix + ./vscode + ./ungoogled-chromium.nix + ./libreoffice.nix + ./mapillary-uploader.nix + ./inkscape.nix + ./gimp.nix + ./guild-wars-2.nix + ./proxmark3.nix + ./freecad.nix + ./onionshare.nix + ./mfoc.nix + ./noita-entangled-worlds.nix + ./pdfarranger.nix + ./picard.nix + ./qflipper.nix + ./openvpn.nix + ./noisetorch.nix + ./olympus.nix + ./openrgb.nix + ./via.nix + ./vortex.nix + ./davinci-resolve.nix + ./gdx-liftoff.nix + ./tor-browser.nix + ./vmware-workstation.nix + ./proton-mail-pwa.nix + ./proton-calendar-pwa.nix + ./matrix-cyberia-pwa.nix + ./e621-downloader.nix + ]; +} diff --git a/modules/home-manager-modules/programs/discord.nix b/modules/home-manager-modules/programs/discord.nix new file mode 100644 index 0000000..e42367b --- /dev/null +++ b/modules/home-manager-modules/programs/discord.nix @@ -0,0 +1,17 @@ +{ + lib, + config, + ... +}: { + config = lib.mkIf config.programs.discord.enable (lib.mkMerge [ + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/discord/" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/dungeon-draft.nix b/modules/home-manager-modules/programs/dungeon-draft.nix new file mode 100644 index 0000000..faa69c6 --- /dev/null +++ b/modules/home-manager-modules/programs/dungeon-draft.nix @@ -0,0 +1,24 @@ +{ + config, + lib, + ... +}: let + cfg = config.programs.dungeon-draft; +in { + options.programs.dungeon-draft = { + enable = lib.mkEnableOption "Dungeon Draft"; + }; + + config = { + assertions = [ + { + assertion = !cfg.enable; + message = '' + Dungeon Draft module is not yet fully configured. + Please download the Dungeon Draft executable (.exe) from the official website, + then configure the Wine environment and executable path as needed. + ''; + } + ]; + }; +} diff --git a/modules/home-manager-modules/programs/e621-downloader.nix b/modules/home-manager-modules/programs/e621-downloader.nix new file mode 100644 index 0000000..2cb32a9 --- /dev/null +++ b/modules/home-manager-modules/programs/e621-downloader.nix @@ -0,0 +1,16 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.e621-downloader = { + enable = lib.mkEnableOption "enable e621-downloader"; + }; + + config = lib.mkIf config.programs.e621-downloader.enable { + home.packages = with pkgs; [ + e621-downloader + ]; + }; +} diff --git a/modules/home-manager-modules/programs/firefox.nix b/modules/home-manager-modules/programs/firefox.nix new file mode 100644 index 0000000..2756e31 --- /dev/null +++ b/modules/home-manager-modules/programs/firefox.nix @@ -0,0 +1,41 @@ +{ + lib, + config, + ... +}: let + buildProfilePersistence = profile: { + directories = [ + ".mozilla/firefox/${profile}/extensions" + ]; + files = [ + ".mozilla/firefox/${profile}/cookies.sqlite" + ".mozilla/firefox/${profile}/favicons.sqlite" + # Permissions and ${profileName} levels for each site + ".mozilla/firefox/${profile}/permissions.sqlite" + ".mozilla/firefox/${profile}/content-prefs.sqlite" + # Browser history and bookmarks + ".mozilla/firefox/${profile}/places.sqlite" + # I guess this is useful? + # https://bugzilla.mozilla.org/show_bug.cgi?id=1511384 + # https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria + ".mozilla/firefox/${profile}/storage.sqlite" + # Extension configuration + ".mozilla/firefox/${profile}/extension-settings.json" + ]; + }; +in { + config = lib.mkIf (config.programs.firefox.enable && config.impermanence.enable) { + home.persistence."${config.impermanence.persistencePath}" = lib.mkMerge ( + ( + lib.attrsets.mapAttrsToList + (profile: _: buildProfilePersistence profile) + config.programs.firefox.profiles + ) + ++ ( + lib.lists.optional + ((builtins.length (lib.attrsets.mapAttrsToList (key: value: value) config.programs.firefox.profiles)) == 0) + (buildProfilePersistence "default") + ) + ); + }; +} diff --git a/modules/home-manager-modules/programs/freecad.nix b/modules/home-manager-modules/programs/freecad.nix new file mode 100644 index 0000000..50600db --- /dev/null +++ b/modules/home-manager-modules/programs/freecad.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.freecad = { + enable = lib.mkEnableOption "enable freecad"; + }; + + config = lib.mkIf config.programs.freecad.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + freecad + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/FreeCAD" + ]; + }; + } + ) + ]); +} 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..4440831 --- /dev/null +++ b/modules/home-manager-modules/programs/gdx-liftoff.nix @@ -0,0 +1,16 @@ +{ + lib, + pkgs, + config, + ... +}: { + 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..95c87e6 --- /dev/null +++ b/modules/home-manager-modules/programs/gimp.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.gimp = { + enable = lib.mkEnableOption "enable gimp"; + }; + + config = lib.mkIf config.programs.gimp.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + gimp + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/GIMP" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/guild-wars-2.nix b/modules/home-manager-modules/programs/guild-wars-2.nix new file mode 100644 index 0000000..3f68ec6 --- /dev/null +++ b/modules/home-manager-modules/programs/guild-wars-2.nix @@ -0,0 +1,24 @@ +{ + config, + lib, + ... +}: let + cfg = config.programs.guild-wars-2; +in { + options.programs.guild-wars-2 = { + enable = lib.mkEnableOption "Guild Wars 2"; + }; + + config = { + assertions = [ + { + assertion = !cfg.enable; + message = '' + Guild Wars 2 module is not yet fully configured. + Please install Guild Wars 2 manually via Steam or the official client, + then configure the Wine environment as needed. + ''; + } + ]; + }; +} diff --git a/modules/home-manager-modules/programs/idea.nix b/modules/home-manager-modules/programs/idea.nix new file mode 100644 index 0000000..a1aebda --- /dev/null +++ b/modules/home-manager-modules/programs/idea.nix @@ -0,0 +1,32 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.jetbrains.idea-oss = { + enable = lib.mkEnableOption "enable idea-oss"; + }; + + config = lib.mkIf config.programs.jetbrains.idea-oss.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + jetbrains.idea-oss + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + # configuration + "${config.xdg.configHome}/JetBrains/" + # plugins + "${config.xdg.dataHome}/JetBrains/" + # System and Logs + "${config.xdg.cacheHome}/JetBrains/" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/inkscape.nix b/modules/home-manager-modules/programs/inkscape.nix new file mode 100644 index 0000000..28eb334 --- /dev/null +++ b/modules/home-manager-modules/programs/inkscape.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.inkscape = { + enable = lib.mkEnableOption "enable inkscape"; + }; + + config = lib.mkIf config.programs.inkscape.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + inkscape + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/inkscape" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/kdenlive.nix b/modules/home-manager-modules/programs/kdenlive.nix new file mode 100644 index 0000000..2c4bac8 --- /dev/null +++ b/modules/home-manager-modules/programs/kdenlive.nix @@ -0,0 +1,35 @@ +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.programs.kdenlive; +in { + options.programs.kdenlive = { + enable = lib.mkEnableOption "kdenlive"; + package = lib.mkOption { + type = lib.types.package; + default = pkgs.kdePackages.kdenlive; + description = "The kdenlive package to install."; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + home.packages = [ + cfg.package + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/kdenliverc" + "${config.xdg.dataHome}/kdenlive" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/kicad.nix b/modules/home-manager-modules/programs/kicad.nix new file mode 100644 index 0000000..c2414c1 --- /dev/null +++ b/modules/home-manager-modules/programs/kicad.nix @@ -0,0 +1,23 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.kicad = { + enable = lib.mkEnableOption "enable kicad"; + }; + + config = lib.mkIf config.programs.kicad.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + kicad + ]; + } + ( + lib.mkIf config.impermanence.enable { + # TODO: + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/krita.nix b/modules/home-manager-modules/programs/krita.nix new file mode 100644 index 0000000..dd7bb12 --- /dev/null +++ b/modules/home-manager-modules/programs/krita.nix @@ -0,0 +1,28 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.krita = { + enable = lib.mkEnableOption "enable krita"; + }; + + config = lib.mkIf config.programs.krita.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + krita + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/kritarc" + "${config.xdg.dataHome}/krita" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/libreoffice.nix b/modules/home-manager-modules/programs/libreoffice.nix new file mode 100644 index 0000000..283c8db --- /dev/null +++ b/modules/home-manager-modules/programs/libreoffice.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.libreoffice = { + enable = lib.mkEnableOption "enable libreoffice"; + }; + + config = lib.mkIf config.programs.libreoffice.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + libreoffice + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/libreoffice" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/makemkv.nix b/modules/home-manager-modules/programs/makemkv.nix new file mode 100644 index 0000000..f748f68 --- /dev/null +++ b/modules/home-manager-modules/programs/makemkv.nix @@ -0,0 +1,41 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.makemkv = { + enable = lib.mkEnableOption "enable makemkv"; + appKeyFile = lib.mkOption { + type = lib.types.str; + }; + destinationDir = lib.mkOption { + type = lib.types.str; + }; + }; + + config = lib.mkIf config.programs.makemkv.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + makemkv + ]; + + sops.templates."MakeMKV.settings.conf".content = '' + app_DestinationDir = "${config.programs.makemkv.destinationDir}" + app_DestinationType = "2" + app_Key = "${config.programs.makemkv.appKeyFile}" + ''; + + home.file.".MakeMKV/settings.conf".source = config.lib.file.mkOutOfStoreSymlink config.sops.templates."MakeMKV.settings.conf".path; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + ".MakeMKV" + ]; + }; + } + ) + ]); +} 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..0d9ad5f --- /dev/null +++ b/modules/home-manager-modules/programs/mapillary-uploader.nix @@ -0,0 +1,29 @@ +{ + 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 (mkMerge [ + { + home.packages = [pkgs.mapillary-uploader]; + } + ( + mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/mapillary-uploader" + "${config.xdg.dataHome}/mapillary-uploader" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/matrix-cyberia-pwa.nix b/modules/home-manager-modules/programs/matrix-cyberia-pwa.nix new file mode 100644 index 0000000..644df92 --- /dev/null +++ b/modules/home-manager-modules/programs/matrix-cyberia-pwa.nix @@ -0,0 +1,56 @@ +{ + lib, + pkgs, + config, + ... +}: let + cfg = config.programs.matrix-cyberia-pwa; + isChromium = cfg.package == pkgs.chromium; + isBrowserImpermanenceSupported = cfg.package == pkgs.chromium; +in { + options.programs.matrix-cyberia-pwa = { + enable = lib.mkEnableOption "enable Matrix Cyberia PWA"; + package = lib.mkOption { + type = lib.types.package; + default = pkgs.chromium; + description = "Browser package to use for the PWA"; + }; + impermanence = { + enable = lib.mkOption { + type = lib.types.bool; + default = isBrowserImpermanenceSupported; + description = "Enable impermanence configuration for the PWA. Only automatically enabled when using chromium."; + }; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + warnings = + lib.optional (config.impermanence.enable && !isBrowserImpermanenceSupported) + "matrix-cyberia-pwa: Using unsupported package. You will need to manually configure pwa for ${cfg.package.pname}. Supported package(s) ${pkgs.chromium.pname}"; + } + ( + lib.mkIf isChromium { + xdg.desktopEntries.matrix-cyberia-pwa = { + name = "Matrix (Cyberia)"; + type = "Application"; + exec = "${cfg.package}/bin/${cfg.package.pname} --app=https://chat.cyberia.club/"; + icon = "matrix"; + terminal = false; + categories = ["Network" "InstantMessaging"]; + }; + } + ) + ( + lib.mkIf (config.impermanence.enable && cfg.impermanence.enable && isChromium) { + home.persistence."/persist${config.home.homeDirectory}" = { + directories = [ + "${config.xdg.configHome}/chromium" + ]; + 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..6006c9b --- /dev/null +++ b/modules/home-manager-modules/programs/mfoc.nix @@ -0,0 +1,16 @@ +{ + lib, + pkgs, + config, + ... +}: { + 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..4b42638 --- /dev/null +++ b/modules/home-manager-modules/programs/noisetorch.nix @@ -0,0 +1,16 @@ +{ + lib, + pkgs, + config, + ... +}: { + 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/noita-entangled-worlds.nix b/modules/home-manager-modules/programs/noita-entangled-worlds.nix new file mode 100644 index 0000000..3f3af64 --- /dev/null +++ b/modules/home-manager-modules/programs/noita-entangled-worlds.nix @@ -0,0 +1,18 @@ +{ + lib, + pkgs, + config, + ... +}: { + options = { + programs.noita-entangled-worlds = { + enable = lib.mkEnableOption "Noita Entangled Worlds multiplayer mod"; + }; + }; + + config = lib.mkIf config.programs.noita-entangled-worlds.enable { + home.packages = with pkgs; [ + noita_entangled_worlds + ]; + }; +} diff --git a/modules/home-manager-modules/programs/obs.nix b/modules/home-manager-modules/programs/obs.nix new file mode 100644 index 0000000..0a4caf7 --- /dev/null +++ b/modules/home-manager-modules/programs/obs.nix @@ -0,0 +1,17 @@ +{ + lib, + config, + ... +}: { + config = lib.mkIf config.programs.obs-studio.enable (lib.mkMerge [ + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/obs-studio" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/obsidian.nix b/modules/home-manager-modules/programs/obsidian.nix new file mode 100644 index 0000000..6676ecd --- /dev/null +++ b/modules/home-manager-modules/programs/obsidian.nix @@ -0,0 +1,17 @@ +{ + lib, + config, + ... +}: { + config = lib.mkIf config.programs.obsidian.enable (lib.mkMerge [ + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/obsidian" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/olympus.nix b/modules/home-manager-modules/programs/olympus.nix new file mode 100644 index 0000000..2d5adb6 --- /dev/null +++ b/modules/home-manager-modules/programs/olympus.nix @@ -0,0 +1,35 @@ +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.programs.olympus; +in { + options.programs.olympus = { + enable = lib.mkEnableOption "olympus"; + package = lib.mkOption { + type = lib.types.package; + default = pkgs.olympus; + description = "The olympus package to install."; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + home.packages = [ + cfg.package + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/olympus" + "${config.xdg.dataHome}/olympus" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/onionshare.nix b/modules/home-manager-modules/programs/onionshare.nix new file mode 100644 index 0000000..475f993 --- /dev/null +++ b/modules/home-manager-modules/programs/onionshare.nix @@ -0,0 +1,16 @@ +{ + lib, + pkgs, + config, + ... +}: { + 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..c350b1e --- /dev/null +++ b/modules/home-manager-modules/programs/openrgb.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.openrgb = { + enable = lib.mkEnableOption "enable openrgb"; + }; + + config = lib.mkIf config.programs.openrgb.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + openrgb + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/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..dcd499c --- /dev/null +++ b/modules/home-manager-modules/programs/openvpn.nix @@ -0,0 +1,16 @@ +{ + lib, + pkgs, + config, + ... +}: { + 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..9246efd --- /dev/null +++ b/modules/home-manager-modules/programs/pdfarranger.nix @@ -0,0 +1,16 @@ +{ + lib, + pkgs, + config, + ... +}: { + 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..ffc4289 --- /dev/null +++ b/modules/home-manager-modules/programs/picard.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.picard = { + enable = lib.mkEnableOption "enable picard"; + }; + + config = lib.mkIf config.programs.picard.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + picard + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/MusicBrainz" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/piper.nix b/modules/home-manager-modules/programs/piper.nix new file mode 100644 index 0000000..3ed25fd --- /dev/null +++ b/modules/home-manager-modules/programs/piper.nix @@ -0,0 +1,16 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.piper = { + enable = lib.mkEnableOption "enable piper"; + }; + + config = lib.mkIf config.programs.piper.enable { + home.packages = with pkgs; [ + piper + ]; + }; +} diff --git a/modules/home-manager-modules/programs/prostudiomasters.nix b/modules/home-manager-modules/programs/prostudiomasters.nix new file mode 100644 index 0000000..d61b7e5 --- /dev/null +++ b/modules/home-manager-modules/programs/prostudiomasters.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.prostudiomasters = { + enable = lib.mkEnableOption "enable prostudiomasters"; + }; + + config = lib.mkIf config.programs.prostudiomasters.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + prostudiomasters + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/ProStudioMasters" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/proton-calendar-pwa.nix b/modules/home-manager-modules/programs/proton-calendar-pwa.nix new file mode 100644 index 0000000..33796e2 --- /dev/null +++ b/modules/home-manager-modules/programs/proton-calendar-pwa.nix @@ -0,0 +1,55 @@ +{ + lib, + pkgs, + config, + ... +}: let + cfg = config.programs.proton-calendar-pwa; + isChromium = cfg.package == pkgs.chromium; + isBrowserImpermanenceSupported = cfg.package == pkgs.chromium; +in { + options.programs.proton-calendar-pwa = { + enable = lib.mkEnableOption "enable Proton Calendar PWA"; + package = lib.mkOption { + type = lib.types.package; + default = pkgs.chromium; + description = "Browser package to use for the PWA"; + }; + impermanence = { + enable = lib.mkOption { + type = lib.types.bool; + default = isBrowserImpermanenceSupported; + description = "Enable impermanence configuration for the PWA. Only automatically enabled when using chromium."; + }; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + warnings = + lib.optional (config.impermanence.enable && !isBrowserImpermanenceSupported) + "proton-calendar-pwa: Using unsupported package. You will need to manually configure pwa for ${cfg.package.pname}. Supported package(s) ${pkgs.chromium.pname}"; + } + ( + lib.mkIf isChromium { + xdg.desktopEntries.proton-calendar-pwa = { + name = "Proton Calendar"; + type = "Application"; + exec = "${cfg.package}/bin/${cfg.package.pname} --app=https://calendar.proton.me"; + icon = "chrome-ojibjkjikcpjonjjngfkegflhmffeemk-Default"; + terminal = false; + }; + } + ) + ( + lib.mkIf (config.impermanence.enable && cfg.impermanence.enable && isChromium) { + home.persistence."/persist${config.home.homeDirectory}" = { + directories = [ + "${config.xdg.configHome}/chromium" + ]; + allowOther = true; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/proton-mail-pwa.nix b/modules/home-manager-modules/programs/proton-mail-pwa.nix new file mode 100644 index 0000000..3a3fe89 --- /dev/null +++ b/modules/home-manager-modules/programs/proton-mail-pwa.nix @@ -0,0 +1,55 @@ +{ + lib, + pkgs, + config, + ... +}: let + cfg = config.programs.proton-mail-pwa; + isChromium = cfg.package == pkgs.chromium; + isBrowserImpermanenceSupported = cfg.package == pkgs.chromium; +in { + options.programs.proton-mail-pwa = { + enable = lib.mkEnableOption "enable Proton Mail PWA"; + package = lib.mkOption { + type = lib.types.package; + default = pkgs.chromium; + description = "Browser package to use for the PWA"; + }; + impermanence = { + enable = lib.mkOption { + type = lib.types.bool; + default = isBrowserImpermanenceSupported; + description = "Enable impermanence configuration for the PWA. Only automatically enabled when using chromium."; + }; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + warnings = + lib.optional (config.impermanence.enable && !isBrowserImpermanenceSupported) + "proton-mail-pwa: Using unsupported package. You will need to manually configure pwa for ${cfg.package.pname}. Supported package(s) ${pkgs.chromium.pname}"; + } + ( + lib.mkIf isChromium { + xdg.desktopEntries.proton-mail-pwa = { + name = "Proton Mail"; + type = "Application"; + exec = "${cfg.package}/bin/${cfg.package.pname} --app=https://mail.proton.me"; + icon = "chrome-jnpecgipniidlgicjocehkhajgdnjekh-Default"; + terminal = false; + }; + } + ) + ( + lib.mkIf (config.impermanence.enable && cfg.impermanence.enable && isChromium) { + home.persistence."/persist${config.home.homeDirectory}" = { + directories = [ + "${config.xdg.configHome}/chromium" + ]; + allowOther = true; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/protonvpn.nix b/modules/home-manager-modules/programs/protonvpn.nix new file mode 100644 index 0000000..5742948 --- /dev/null +++ b/modules/home-manager-modules/programs/protonvpn.nix @@ -0,0 +1,28 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.protonvpn-gui = { + enable = lib.mkEnableOption "enable protonvpn"; + }; + + config = lib.mkIf config.programs.protonvpn-gui.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + protonvpn-gui + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/protonvpn" + "${config.xdg.configHome}/Proton" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/proxmark3.nix b/modules/home-manager-modules/programs/proxmark3.nix new file mode 100644 index 0000000..656be19 --- /dev/null +++ b/modules/home-manager-modules/programs/proxmark3.nix @@ -0,0 +1,16 @@ +{ + lib, + pkgs, + config, + ... +}: { + 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/qbittorrent.nix b/modules/home-manager-modules/programs/qbittorrent.nix new file mode 100644 index 0000000..b2e0f50 --- /dev/null +++ b/modules/home-manager-modules/programs/qbittorrent.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.qbittorrent = { + enable = lib.mkEnableOption "enable qbittorrent"; + }; + + config = lib.mkIf config.programs.qbittorrent.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + qbittorrent + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/qBittorrent" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/qflipper.nix b/modules/home-manager-modules/programs/qflipper.nix new file mode 100644 index 0000000..bb141a4 --- /dev/null +++ b/modules/home-manager-modules/programs/qflipper.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.qflipper = { + enable = lib.mkEnableOption "enable qflipper"; + }; + + config = lib.mkIf config.programs.qflipper.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + qFlipper + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/qFlipper" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/signal.nix b/modules/home-manager-modules/programs/signal.nix new file mode 100644 index 0000000..a50a49e --- /dev/null +++ b/modules/home-manager-modules/programs/signal.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.signal-desktop = { + enable = lib.mkEnableOption "enable signal"; + }; + + config = lib.mkIf config.programs.signal-desktop.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + signal-desktop + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/Signal" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/steam.nix b/modules/home-manager-modules/programs/steam.nix new file mode 100644 index 0000000..4e0644e --- /dev/null +++ b/modules/home-manager-modules/programs/steam.nix @@ -0,0 +1,35 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.steam = { + enable = lib.mkEnableOption "enable steam"; + }; + + config = lib.mkIf config.programs.steam.enable ( + lib.mkMerge [ + { + home.packages = with pkgs; [ + steam + steam.run + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + { + directory = "${config.xdg.dataHome}/Steam"; + method = "symlink"; + } + ]; + }; + } + ) + ] + ); + + # TODO: bind impermanence config +} 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..c108805 --- /dev/null +++ b/modules/home-manager-modules/programs/tor-browser.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + 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 config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.dataHome}/torbrowser" + ]; + }; + } + ) + ]); +} 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..32f4b40 --- /dev/null +++ b/modules/home-manager-modules/programs/ungoogled-chromium.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: { + 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 config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/chromium" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/via.nix b/modules/home-manager-modules/programs/via.nix new file mode 100644 index 0000000..ad6f45a --- /dev/null +++ b/modules/home-manager-modules/programs/via.nix @@ -0,0 +1,28 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.via = { + enable = lib.mkEnableOption "enable via"; + }; + + config = lib.mkIf config.programs.via.enable (lib.mkMerge [ + { + home.packages = with pkgs; [ + via + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + "${config.xdg.configHome}/via" + "${config.xdg.dataHome}/via" + ]; + }; + } + ) + ]); +} diff --git a/modules/home-manager-modules/programs/vmware-workstation.nix b/modules/home-manager-modules/programs/vmware-workstation.nix new file mode 100644 index 0000000..76f260b --- /dev/null +++ b/modules/home-manager-modules/programs/vmware-workstation.nix @@ -0,0 +1,36 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.vmware-workstation = { + enable = lib.mkEnableOption "enable VMware Workstation"; + }; + + config = lib.mkIf config.programs.vmware-workstation.enable ( + lib.mkMerge [ + { + home.packages = with pkgs; [ + vmware-workstation + ]; + } + ( + lib.mkIf config.impermanence.enable { + home.persistence."${config.impermanence.persistencePath}" = { + directories = [ + { + directory = ".vmware"; + method = "symlink"; + } + { + directory = "vmware"; + method = "symlink"; + } + ]; + }; + } + ) + ] + ); +} diff --git a/modules/home-manager-modules/programs/vortex.nix b/modules/home-manager-modules/programs/vortex.nix new file mode 100644 index 0000000..cb86526 --- /dev/null +++ b/modules/home-manager-modules/programs/vortex.nix @@ -0,0 +1,24 @@ +{ + config, + lib, + ... +}: let + cfg = config.programs.vortex; +in { + options.programs.vortex = { + enable = lib.mkEnableOption "Vortex (Nexus Mods manager)"; + }; + + config = { + assertions = [ + { + assertion = !cfg.enable; + message = '' + Vortex module is not yet fully configured. + Please download and install Vortex manually from the Nexus Mods website, + then configure the Wine environment and dependencies as needed. + ''; + } + ]; + }; +} diff --git a/modules/home-manager-modules/programs/vscode/aiCode.nix b/modules/home-manager-modules/programs/vscode/aiCode.nix new file mode 100644 index 0000000..838a439 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/aiCode.nix @@ -0,0 +1,45 @@ +{ + lib, + pkgs, + ... +}: let + pkgsRepository = pkgs.codium-extensions; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.aiCode = { + enable = lib.mkEnableOption "should the ai code extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "ai-code" {}; + ollamaHost = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = "what host should be used for ollama"; + default = null; + }; + inlineCompletion = { + enable = lib.mkOption { + type = lib.types.bool; + description = "should inline completion be enabled"; + default = true; + }; + model = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = "what model should be used for ollama"; + default = null; + }; + }; + }; + }; + config = lib.mkIf config.extraExtensions.aiCode.enable { + extensions = [ + config.extraExtensions.aiCode.extension + ]; + userSettings = { + "aiCode.ollamaHost" = lib.mkIf (config.extraExtensions.aiCode.ollamaHost != null) config.extraExtensions.aiCode.ollamaHost; + "aiCode.inlineCompletion.enable" = config.extraExtensions.aiCode.inlineCompletion.enable; + "aiCode.inlineCompletion.model" = lib.mkIf (config.extraExtensions.aiCode.inlineCompletion.model != null) config.extraExtensions.aiCode.inlineCompletion.model; + }; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/alejandra.nix b/modules/home-manager-modules/programs/vscode/alejandra.nix new file mode 100644 index 0000000..ffeaf96 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/alejandra.nix @@ -0,0 +1,34 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.alejandra = { + enable = lib.mkEnableOption "Enable Alejandra extension for Nix formatting"; + extension = lib.mkPackageOption pkgsRepository "alejandra" { + default = ["kamadorueda" "alejandra"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.alejandra.enable { + extensions = [config.extraExtensions.alejandra.extension]; + userSettings = { + "[nix]" = { + "editor.defaultFormatter" = "kamadorueda.alejandra"; + "editor.formatOnPaste" = true; + "editor.formatOnSave" = true; + "editor.formatOnType" = true; + }; + "alejandra.program" = "alejandra"; + }; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/astroVscode.nix b/modules/home-manager-modules/programs/vscode/astroVscode.nix new file mode 100644 index 0000000..4bae34a --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/astroVscode.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.astroVscode = { + enable = lib.mkEnableOption "should the astro-vscode extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "astro-vscode" { + default = ["astro-build" "astro-vscode"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.astroVscode.enable { + extensions = [ + config.extraExtensions.astroVscode.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/atomKeybindings.nix b/modules/home-manager-modules/programs/vscode/atomKeybindings.nix new file mode 100644 index 0000000..95cd928 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/atomKeybindings.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.atomKeybindings = { + enable = lib.mkEnableOption "should the atom keybindings extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "atom-keybindings" { + default = ["ms-vscode" "atom-keybindings"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.atomKeybindings.enable { + extensions = [ + config.extraExtensions.atomKeybindings.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/autoRenameTag.nix b/modules/home-manager-modules/programs/vscode/autoRenameTag.nix new file mode 100644 index 0000000..5f24a32 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/autoRenameTag.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.autoRenameTag = { + enable = lib.mkEnableOption "should the auto-rename-tag extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "auto-rename-tag" { + default = ["formulahendry" "auto-rename-tag"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.autoRenameTag.enable { + extensions = [ + config.extraExtensions.autoRenameTag.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/claudeDev.nix b/modules/home-manager-modules/programs/vscode/claudeDev.nix new file mode 100644 index 0000000..c4d2dd7 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/claudeDev.nix @@ -0,0 +1,237 @@ +{ + lib, + pkgs, + config, + inputs, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; + + mcp-nixos = inputs.mcp-nixos.packages.${pkgs.stdenv.hostPlatform.system}.default; + + anyProfileHasInstallTool = lib.any ( + profile: + profile.extraExtensions.claudeDev.enable + && profile.extraExtensions.claudeDev.installTool + ) (lib.attrValues config.programs.vscode.profiles); + + getInstallToolPackage = lib.findFirst (package: package != null) pkgs.cline (map ( + profile: + if profile.extraExtensions.claudeDev.enable && profile.extraExtensions.claudeDev.installTool + then profile.extraExtensions.claudeDev.package + else null + ) (lib.attrValues config.programs.vscode.profiles)); + + 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); + + anyProfileHasMcpVitest = lib.any ( + profile: + profile.extraExtensions.claudeDev.enable + && profile.extraExtensions.claudeDev.mcp.vitest.enable + ) (lib.attrValues config.programs.vscode.profiles); + + 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; + + 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, ...}: { + options = { + extraExtensions.claudeDev = { + enable = lib.mkEnableOption "should the claude-dev extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "claude-dev" { + default = ["saoudrizwan" "claude-dev"]; + }; + + installTool = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to install the cline CLI tool for subagent support when the extension is enabled"; + }; + package = lib.mkOption { + type = lib.types.package; + default = pkgs.cline; + description = "The package to install for the cline CLI tool"; + }; + + 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"; + 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"; + set_project_root = lib.mkEnableOption "Should the set_project_root 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"; + }; + }; + }; + }; + }; + config = lib.mkIf config.extraExtensions.claudeDev.enable { + extensions = [ + config.extraExtensions.claudeDev.extension + ]; + }; + })); + }; + + config = lib.mkMerge [ + (lib.mkIf anyProfileHasInstallTool { + home.packages = [ + getInstallToolPackage + ]; + }) + + (lib.mkIf anyProfileHasMcpNixos { + home.packages = [ + mcp-nixos + ]; + }) + + (lib.mkIf anyProfileHasMcp { + 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 = "${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" (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" (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; + }; + }) + ]; +} 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..b667c27 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/conventionalCommits.nix @@ -0,0 +1,40 @@ +{ + 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"]; + }; + + gitmoji = lib.mkEnableOption "should emoji be prompted for as a part of the commit message./"; + + promptScopes = lib.mkEnableOption "prompting for scopes in conventional commits"; + + promptFooter = lib.mkEnableOption "prompting for footer in conventional commits"; + + showNewVersionNotes = lib.mkEnableOption "showing new version notes for conventional commits"; + }; + }; + config = lib.mkIf config.extraExtensions.conventionalCommits.enable { + extensions = [config.extraExtensions.conventionalCommits.extension]; + + userSettings = { + "conventionalCommits.gitmoji" = config.extraExtensions.conventionalCommits.gitmoji; + "conventionalCommits.promptScopes" = config.extraExtensions.conventionalCommits.promptScopes; + "conventionalCommits.promptFooter" = config.extraExtensions.conventionalCommits.promptFooter; + "conventionalCommits.showNewVersionNotes" = config.extraExtensions.conventionalCommits.showNewVersionNotes; + }; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/default.nix b/modules/home-manager-modules/programs/vscode/default.nix new file mode 100644 index 0000000..6b7fbb9 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/default.nix @@ -0,0 +1,31 @@ +{...}: { + imports = [ + ./oneDark.nix + ./atomKeybindings.nix + ./aiCode.nix + ./alejandra.nix + ./nixIde.nix + ./autoRenameTag.nix + ./es7ReactJsSnippets.nix + ./liveServer.nix + ./tauriVscode.nix + ./vscodeEslint.nix + ./vscodeJest.nix + ./vscodeStandard.nix + ./vscodeStylelint.nix + ./go.nix + ./evenBetterToml.nix + ./openRemoteSsh.nix + ./platformIO.nix + ./rustAnalyzer.nix + ./astroVscode.nix + ./vscodeMdx.nix + ./claudeDev.nix + ./nearley.nix + ./vitest.nix + ./direnv.nix + ./conventionalCommits.nix + ./openDyslexicFont.nix + ./graphql.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]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/es7ReactJsSnippets.nix b/modules/home-manager-modules/programs/vscode/es7ReactJsSnippets.nix new file mode 100644 index 0000000..09e6da3 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/es7ReactJsSnippets.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.es7ReactJsSnippets = { + enable = lib.mkEnableOption "should the es7-react-js-snippets extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "es7-react-js-snippets" { + default = ["dsznajder" "es7-react-js-snippets"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.es7ReactJsSnippets.enable { + extensions = [ + config.extraExtensions.es7ReactJsSnippets.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/evenBetterToml.nix b/modules/home-manager-modules/programs/vscode/evenBetterToml.nix new file mode 100644 index 0000000..9813ee1 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/evenBetterToml.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.evenBetterToml = { + enable = lib.mkEnableOption "should the even-better-toml extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "even-better-toml" { + default = ["tamasfe" "even-better-toml"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.evenBetterToml.enable { + extensions = [ + config.extraExtensions.evenBetterToml.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/go.nix b/modules/home-manager-modules/programs/vscode/go.nix new file mode 100644 index 0000000..bd9b771 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/go.nix @@ -0,0 +1,34 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.go = { + enable = lib.mkEnableOption "should the go extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "go" { + default = ["golang" "go"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.go.enable { + extensions = [ + config.extraExtensions.go.extension + ]; + userSettings = { + "go.alternateTools" = { + "gopls" = "gopls"; + }; + "go.toolsManagement.autoUpdate" = false; + "go.useLanguageServer" = true; + }; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/graphql.nix b/modules/home-manager-modules/programs/vscode/graphql.nix new file mode 100644 index 0000000..fde08f3 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/graphql.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.graphql = { + enable = lib.mkEnableOption "should the graphql highlighting extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "vscode-graphql" { + default = ["graphql" "vscode-graphql-syntax"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.graphql.enable { + extensions = [ + config.extraExtensions.graphql.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/liveServer.nix b/modules/home-manager-modules/programs/vscode/liveServer.nix new file mode 100644 index 0000000..3f53ca3 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/liveServer.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.liveServer = { + enable = lib.mkEnableOption "should the live-server extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "live-server" { + default = ["ms-vscode" "live-server"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.liveServer.enable { + extensions = [ + config.extraExtensions.liveServer.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/nearley.nix b/modules/home-manager-modules/programs/vscode/nearley.nix new file mode 100644 index 0000000..3020a9e --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/nearley.nix @@ -0,0 +1,27 @@ +{ + 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.nearley = { + enable = lib.mkEnableOption "should the nearley extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "nearley" { + default = ["karyfoundation" "nearley"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.nearley.enable { + extensions = [ + config.extraExtensions.nearley.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/nixIde.nix b/modules/home-manager-modules/programs/vscode/nixIde.nix new file mode 100644 index 0000000..bc79b69 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/nixIde.nix @@ -0,0 +1,29 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.nixIde = { + enable = lib.mkEnableOption "Enable Nix IDE extension"; + extension = lib.mkPackageOption pkgsRepository "nix-ide" { + default = ["jnoortheen" "nix-ide"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.nixIde.enable { + extensions = [config.extraExtensions.nixIde.extension]; + userSettings = { + "nix.enableLanguageServer" = true; + "nix.serverPath" = "nil"; + }; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/oneDark.nix b/modules/home-manager-modules/programs/vscode/oneDark.nix new file mode 100644 index 0000000..5ed43f4 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/oneDark.nix @@ -0,0 +1,30 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.oneDark = { + enable = lib.mkEnableOption "should the one dark theme for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "onedark" { + default = ["akamud" "vscode-theme-onedark"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.oneDark.enable { + extensions = [ + config.extraExtensions.oneDark.extension + ]; + userSettings = { + "workbench.colorTheme" = "Atom One Dark"; + }; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/openDyslexicFont.nix b/modules/home-manager-modules/programs/vscode/openDyslexicFont.nix new file mode 100644 index 0000000..f1f6215 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/openDyslexicFont.nix @@ -0,0 +1,49 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.openDyslexicFont = { + enable = lib.mkEnableOption "should OpenDyslexic font be set as the default font for VSCode"; + package = lib.mkPackageOption pkgs "nerd-fonts.open-dyslexic" { + default = ["nerd-fonts" "open-dyslexic"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.openDyslexicFont.enable { + userSettings = { + "editor.fontFamily" = "'OpenDyslexicM Nerd Font Mono', Droid Sans Mono, monospace"; + "editor.fontSize" = 14; + "editor.letterSpacing" = -0.3; + }; + }; + })); + }; + + config = let + enabledProfiles = + lib.filter (profile: profile.extraExtensions.openDyslexicFont.enable or false) + (lib.attrValues config.programs.vscode.profiles); + + anyProfileUsesOpenDyslexicFont = enabledProfiles != []; + + fontPackages = lib.unique (map (profile: profile.extraExtensions.openDyslexicFont.package) enabledProfiles); + in { + # Ensure OpenDyslexic font packages are installed when any VSCode profile uses them + home.packages = fontPackages; + + fonts.fontconfig.enable = lib.mkIf anyProfileUsesOpenDyslexicFont true; + + # Add assertion to ensure the fonts are available + assertions = + map (fontPkg: { + assertion = lib.elem fontPkg config.home.packages; + message = "OpenDyslexic font package '${fontPkg.name or "unknown"}' must be installed when using openDyslexicFont extension for VSCode."; + }) + fontPackages; + }; +} diff --git a/modules/home-manager-modules/programs/vscode/openRemoteSsh.nix b/modules/home-manager-modules/programs/vscode/openRemoteSsh.nix new file mode 100644 index 0000000..c1b6daa --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/openRemoteSsh.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.openRemoteSsh = { + enable = lib.mkEnableOption "should the open-remote-ssh extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "open-remote-ssh" { + default = ["jeanp413" "open-remote-ssh"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.openRemoteSsh.enable { + extensions = [ + config.extraExtensions.openRemoteSsh.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/platformIO.nix b/modules/home-manager-modules/programs/vscode/platformIO.nix new file mode 100644 index 0000000..ace83b7 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/platformIO.nix @@ -0,0 +1,30 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.platformIO = { + enable = lib.mkEnableOption "should the platformIO extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "platformIO" { + default = ["pioarduino" "pioarduino-ide"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.platformIO.enable { + extensions = [ + config.extraExtensions.platformIO.extension + ]; + userSettings = { + "platformio-ide.useBuiltinPIOCore" = false; + }; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/rustAnalyzer.nix b/modules/home-manager-modules/programs/vscode/rustAnalyzer.nix new file mode 100644 index 0000000..66e9ebe --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/rustAnalyzer.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.rustAnalyzer = { + enable = lib.mkEnableOption "should the rust-analyzer extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "rust-analyzer" { + default = ["rust-lang" "rust-analyzer"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.rustAnalyzer.enable { + extensions = [ + config.extraExtensions.rustAnalyzer.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/tauriVscode.nix b/modules/home-manager-modules/programs/vscode/tauriVscode.nix new file mode 100644 index 0000000..9185fb3 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/tauriVscode.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.tauriVscode = { + enable = lib.mkEnableOption "should the tauri-vscode extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "tauri-vscode" { + default = ["tauri-apps" "tauri-vscode"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.tauriVscode.enable { + extensions = [ + config.extraExtensions.tauriVscode.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/vitest.nix b/modules/home-manager-modules/programs/vscode/vitest.nix new file mode 100644 index 0000000..446d25b --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/vitest.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.vitest = { + enable = lib.mkEnableOption "should the vitest extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "vitest" { + default = ["vitest" "explorer"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.vitest.enable { + extensions = [ + config.extraExtensions.vitest.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/vscodeEslint.nix b/modules/home-manager-modules/programs/vscode/vscodeEslint.nix new file mode 100644 index 0000000..64d979f --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/vscodeEslint.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.vscodeEslint = { + enable = lib.mkEnableOption "should the vscode-eslint extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "vscode-eslint" { + default = ["dbaeumer" "vscode-eslint"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.vscodeEslint.enable { + extensions = [ + config.extraExtensions.vscodeEslint.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/vscodeJest.nix b/modules/home-manager-modules/programs/vscode/vscodeJest.nix new file mode 100644 index 0000000..7c24f2a --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/vscodeJest.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.vscodeJest = { + enable = lib.mkEnableOption "should the vscode-jest extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "vscode-jest" { + default = ["orta" "vscode-jest"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.vscodeJest.enable { + extensions = [ + config.extraExtensions.vscodeJest.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/vscodeMdx.nix b/modules/home-manager-modules/programs/vscode/vscodeMdx.nix new file mode 100644 index 0000000..c49fe51 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/vscodeMdx.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.vscodeMdx = { + enable = lib.mkEnableOption "should the vscode-mdx extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "vscode-mdx" { + default = ["unifiedjs" "vscode-mdx"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.vscodeMdx.enable { + extensions = [ + config.extraExtensions.vscodeMdx.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/vscodeStandard.nix b/modules/home-manager-modules/programs/vscode/vscodeStandard.nix new file mode 100644 index 0000000..31c8ad0 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/vscodeStandard.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.vscodeStandard = { + enable = lib.mkEnableOption "should the vscode-standard extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "vscode-standard" { + default = ["standard" "vscode-standard"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.vscodeStandard.enable { + extensions = [ + config.extraExtensions.vscodeStandard.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/programs/vscode/vscodeStylelint.nix b/modules/home-manager-modules/programs/vscode/vscodeStylelint.nix new file mode 100644 index 0000000..0d43b29 --- /dev/null +++ b/modules/home-manager-modules/programs/vscode/vscodeStylelint.nix @@ -0,0 +1,27 @@ +{ + lib, + pkgs, + config, + ... +}: let + pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version; + pkgsRepository = pkgsRepositories.open-vsx; +in { + options.programs.vscode.profiles = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({config, ...}: { + options = { + extraExtensions.vscodeStylelint = { + enable = lib.mkEnableOption "should the vscode-stylelint extension for vscode be enabled"; + extension = lib.mkPackageOption pkgsRepository "vscode-stylelint" { + default = ["stylelint" "vscode-stylelint"]; + }; + }; + }; + config = lib.mkIf config.extraExtensions.vscodeStylelint.enable { + extensions = [ + config.extraExtensions.vscodeStylelint.extension + ]; + }; + })); + }; +} diff --git a/modules/home-manager-modules/sops.nix b/modules/home-manager-modules/sops.nix new file mode 100644 index 0000000..910fbb6 --- /dev/null +++ b/modules/home-manager-modules/sops.nix @@ -0,0 +1,7 @@ +{...}: { + config = { + sops = { + age.keyFile = "/var/lib/sops-nix/key.txt"; + }; + }; +} diff --git a/modules/home-manager-modules/user.nix b/modules/home-manager-modules/user.nix new file mode 100644 index 0000000..efce22d --- /dev/null +++ b/modules/home-manager-modules/user.nix @@ -0,0 +1,17 @@ +{ + lib, + config, + osConfig, + ... +}: { + options.user = { + isDesktopUser = lib.mkOption { + type = lib.types.bool; + default = osConfig.host.users.${config.home.username}.isDesktopUser; + }; + isTerminalUser = lib.mkOption { + type = lib.types.bool; + default = osConfig.host.users.${config.home.username}.isTerminalUser; + }; + }; +} diff --git a/modules/nixos-modules/default.nix b/modules/nixos-modules/default.nix index d668a74..34e041e 100644 --- a/modules/nixos-modules/default.nix +++ b/modules/nixos-modules/default.nix @@ -8,14 +8,13 @@ ./desktop.nix ./ssh.nix ./i18n.nix - ./sync.nix - ./impermanence.nix - ./disko.nix - ./ollama.nix + ./sync + ./ollama ./ai.nix - ./tailscale.nix + ./tailscale + ./steam.nix ./server - ./packages + ./storage ]; nixpkgs.config.permittedInsecurePackages = [ diff --git a/modules/nixos-modules/desktop.nix b/modules/nixos-modules/desktop.nix index cf59cd9..66a2433 100644 --- a/modules/nixos-modules/desktop.nix +++ b/modules/nixos-modules/desktop.nix @@ -11,42 +11,51 @@ host.desktop.enable = lib.mkDefault true; } (lib.mkIf config.host.desktop.enable { + environment.gnome.excludePackages = with pkgs; [ + xterm # default terminal + atomix # puzzle game + cheese # webcam tool + epiphany # web browser + geary # email reader + gedit # text editor + decibels # audio player + gnome-characters # character set viewer + gnome-music # music player + gnome-photos # photo viewer + gnome-logs # log viewer + gnome-maps # map viewer + gnome-tour # welcome tour + hitori # sudoku game + iagno # go game + tali # poker game + yelp # help viewer + ]; services = { # Enable CUPS to print documents. - printing.enable = true; + printing = { + enable = true; + drivers = [ + pkgs.hplip + pkgs.gutenprint + pkgs.gutenprintBin + ]; + }; xserver = { # Enable the X11 windowing system. enable = true; - # Enable the GNOME Desktop Environment. - displayManager.gdm.enable = true; - desktopManager = { - gnome.enable = true; - }; - # Get rid of xTerm desktopManager.xterm.enable = false; excludePackages = with pkgs; [ xterm - atomix # puzzle game - cheese # webcam tool - epiphany # web browser - geary # email reader - gedit # text editor - gnome-characters - gnome-music - gnome-photos - gnome-tour - gnome-logs - gnome-maps - hitori # sudoku game - iagno # go game - tali # poker game - yelp # help viewer ]; }; + # Enable the GNOME Desktop Environment. + displayManager.gdm.enable = true; + desktopManager.gnome.enable = true; + pipewire = { enable = true; alsa.enable = true; @@ -70,8 +79,6 @@ # enable RealtimeKit for pulse audio security.rtkit.enable = true; - # disable welcome tour - environment.gnome.excludePackages = [pkgs.gnome-tour]; }) ]; } diff --git a/modules/nixos-modules/disko.nix b/modules/nixos-modules/disko.nix deleted file mode 100644 index 13ddb8f..0000000 --- a/modules/nixos-modules/disko.nix +++ /dev/null @@ -1,237 +0,0 @@ -{ - lib, - pkgs, - config, - inputs, - ... -}: let - # there currently is a bug with disko that causes long disk names to be generated improperly this hash function should alleviate it when used for disk names instead of what we are defaulting to - # max gpt length is 36 and disk adds formats it like disk-xxxx-zfs which means we need to be 9 characters under that - hashDisk = drive: (builtins.substring 0 27 (builtins.hashString "sha256" drive)); - - vdevs = - builtins.map ( - disks: - builtins.map (disk: lib.attrsets.nameValuePair (hashDisk disk) disk) disks - ) - config.host.storage.pool.vdevs; - cache = - builtins.map ( - disk: lib.attrsets.nameValuePair (hashDisk disk) disk - ) - config.host.storage.pool.cache; -in { - options.host.storage = { - enable = lib.mkEnableOption "are we going create zfs disks with disko on this device"; - encryption = lib.mkEnableOption "is the vdev going to be encrypted"; - notifications = { - enable = lib.mkEnableOption "are notifications enabled"; - host = lib.mkOption { - type = lib.types.str; - description = "what is the host that we are going to send the email to"; - }; - port = lib.mkOption { - type = lib.types.port; - description = "what port is the host using to receive mail on"; - }; - to = lib.mkOption { - type = lib.types.str; - description = "what account is the email going to be sent to"; - }; - user = lib.mkOption { - type = lib.types.str; - description = "what user is the email going to be set from"; - }; - tokenFile = lib.mkOption { - type = lib.types.str; - description = "file containing the password to be used by msmtp for notifications"; - }; - }; - pool = { - vdevs = lib.mkOption { - type = lib.types.listOf (lib.types.listOf lib.types.str); - description = "list of disks that are going to be in"; - default = [config.host.storage.pool.drives]; - }; - drives = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "list of drives that are going to be in the vdev"; - default = []; - }; - cache = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "list of drives that are going to be used as cache"; - default = []; - }; - extraDatasets = lib.mkOption { - type = lib.types.attrsOf (inputs.disko.lib.subType { - types = {inherit (inputs.disko.lib.types) zfs_fs zfs_volume;}; - }); - description = "List of datasets to define"; - default = {}; - }; - }; - }; - - config = lib.mkIf config.host.storage.enable { - programs.msmtp = lib.mkIf config.host.storage.notifications.enable { - enable = true; - setSendmail = true; - defaults = { - aliases = "/etc/aliases"; - port = config.host.storage.notifications.port; - tls_trust_file = "/etc/ssl/certs/ca-certificates.crt"; - tls = "on"; - auth = "login"; - tls_starttls = "off"; - }; - accounts = { - zfs_notifications = { - host = config.host.storage.notifications.host; - passwordeval = "cat ${config.host.storage.notifications.tokenFile}"; - user = config.host.storage.notifications.user; - from = config.host.storage.notifications.user; - }; - }; - }; - - services.zfs = { - autoScrub.enable = true; - autoSnapshot.enable = true; - - zed = lib.mkIf config.host.storage.notifications.enable { - # this option is broken we are just going to disable it - enableMail = false; - - settings = { - ZED_DEBUG_LOG = "/tmp/zed.debug.log"; - ZED_EMAIL_ADDR = [config.host.storage.notifications.to]; - ZED_EMAIL_PROG = "${pkgs.msmtp}/bin/msmtp"; - ZED_EMAIL_OPTS = "@ADDRESS@"; - - ZED_NOTIFY_INTERVAL_SECS = 3600; - ZED_NOTIFY_VERBOSE = true; - - ZED_USE_ENCLOSURE_LEDS = true; - ZED_SCRUB_AFTER_RESILVER = true; - }; - }; - }; - - disko.devices = { - disk = ( - builtins.listToAttrs ( - ( - builtins.map - (drive: - lib.attrsets.nameValuePair (drive.name) { - type = "disk"; - device = "/dev/disk/by-id/${drive.value}"; - content = { - type = "gpt"; - partitions = { - zfs = { - size = "100%"; - content = { - type = "zfs"; - pool = "rpool"; - }; - }; - }; - }; - }) - (lib.lists.flatten vdevs) - ) - ++ ( - builtins.map - (drive: - lib.attrsets.nameValuePair (drive.name) { - type = "disk"; - device = "/dev/disk/by-id/${drive.value}"; - content = { - type = "gpt"; - partitions = { - # We are having to boot off of the nvm cache drive because I cant figure out how to boot via the HBA - ESP = { - # 2G here because its not much relative to how much storage we have for caching - size = "2G"; - type = "EF00"; - content = { - type = "filesystem"; - format = "vfat"; - mountpoint = "/boot"; - mountOptions = ["umask=0077"]; - }; - }; - zfs = { - size = "100%"; - content = { - type = "zfs"; - pool = "rpool"; - }; - }; - }; - }; - }) - cache - ) - ) - ); - zpool = { - rpool = { - type = "zpool"; - mode = { - topology = { - type = "topology"; - vdev = ( - builtins.map (disks: { - mode = "raidz2"; - members = - builtins.map (disk: disk.name) disks; - }) - vdevs - ); - cache = builtins.map (disk: disk.name) cache; - }; - }; - - options = { - ashift = "12"; - autotrim = "on"; - }; - - rootFsOptions = - { - canmount = "off"; - mountpoint = "none"; - - xattr = "sa"; - acltype = "posixacl"; - relatime = "on"; - - compression = "lz4"; - - "com.sun:auto-snapshot" = "false"; - } - // ( - lib.attrsets.optionalAttrs config.host.storage.encryption { - encryption = "on"; - keyformat = "hex"; - keylocation = "prompt"; - } - ); - - datasets = lib.mkMerge [ - (lib.attrsets.mapAttrs (name: value: { - type = value.type; - options = value.options; - mountpoint = value.mountpoint; - postCreateHook = value.postCreateHook; - }) - config.host.storage.pool.extraDatasets) - ]; - }; - }; - }; - }; -} diff --git a/modules/nixos-modules/home-manager/default.nix b/modules/nixos-modules/home-manager/default.nix index cab004b..10f86c7 100644 --- a/modules/nixos-modules/home-manager/default.nix +++ b/modules/nixos-modules/home-manager/default.nix @@ -4,5 +4,6 @@ ./flipperzero.nix ./i18n.nix ./openssh.nix + ./steam.nix ]; } diff --git a/modules/nixos-modules/home-manager/steam.nix b/modules/nixos-modules/home-manager/steam.nix new file mode 100644 index 0000000..d151bca --- /dev/null +++ b/modules/nixos-modules/home-manager/steam.nix @@ -0,0 +1,18 @@ +{ + lib, + config, + ... +}: let + setupSteam = + lib.lists.any + (value: value) + (lib.attrsets.mapAttrsToList (name: value: value.programs.steam.enable) config.home-manager.users); +in { + config = lib.mkIf setupSteam { + programs.steam = { + enable = true; + # TODO: figure out how to not install steam here + # package = lib.mkDefault pkgs.emptyFile; + }; + }; +} diff --git a/modules/nixos-modules/impermanence.nix b/modules/nixos-modules/impermanence.nix deleted file mode 100644 index e969e20..0000000 --- a/modules/nixos-modules/impermanence.nix +++ /dev/null @@ -1,115 +0,0 @@ -{ - config, - lib, - ... -}: { - options.host.impermanence.enable = lib.mkEnableOption "are we going to use impermanence on this device"; - - config = lib.mkMerge [ - { - assertions = [ - { - assertion = !(config.host.impermanence.enable && !config.host.storage.enable); - message = '' - Disko storage must be enabled to use impermanence. - ''; - } - ]; - } - ( - lib.mkIf config.host.impermanence.enable { - assertions = [ - { - assertion = config.host.impermanence.enable && config.host.storage.enable; - message = "Impermanence can not be used without managed host storage."; - } - ]; - - programs.fuse.userAllowOther = true; - - boot.initrd.postResumeCommands = lib.mkAfter '' - zfs rollback -r rpool/local/system/root@blank - ''; - - fileSystems = { - "/".neededForBoot = true; - "/persist/system/root".neededForBoot = true; - "/persist/system/var/log".neededForBoot = true; - }; - - host.storage.pool.extraDatasets = { - # local datasets are for data that should be considered ephemeral - "local" = { - type = "zfs_fs"; - options.canmount = "off"; - }; - # nix directory needs to be available pre persist and doesn't need to be snapshotted or backed up - "local/system/nix" = { - type = "zfs_fs"; - mountpoint = "/nix"; - options = { - atime = "off"; - relatime = "off"; - canmount = "on"; - }; - }; - # dataset for root that gets rolled back on every boot - "local/system/root" = { - type = "zfs_fs"; - mountpoint = "/"; - options = { - canmount = "on"; - }; - postCreateHook = '' - zfs snapshot rpool/local/system/root@blank - ''; - }; - - # persist datasets are datasets that contain information that we would like to keep around - "persist" = { - type = "zfs_fs"; - options.canmount = "off"; - options = { - "com.sun:auto-snapshot" = "true"; - }; - }; - # this is where root data actually lives - "persist/system/root" = { - type = "zfs_fs"; - mountpoint = "/persist/system/root"; - }; - "persist/system/var/log" = { - type = "zfs_fs"; - mountpoint = "/persist/system/var/log"; - # logs should be append only so we shouldn't need to snapshot them - options = { - "com.sun:auto-snapshot" = "false"; - }; - }; - }; - - environment.persistence."/persist/system/var/log" = { - enable = true; - hideMounts = true; - directories = [ - "/var/log" - ]; - }; - - environment.persistence."/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - "/var/lib/nixos" - "/var/lib/systemd/coredump" - ]; - files = [ - "/etc/machine-id" - ]; - }; - - security.sudo.extraConfig = "Defaults lecture=never"; - } - ) - ]; -} diff --git a/modules/nixos-modules/ollama/default.nix b/modules/nixos-modules/ollama/default.nix new file mode 100644 index 0000000..896526a --- /dev/null +++ b/modules/nixos-modules/ollama/default.nix @@ -0,0 +1,6 @@ +{...}: { + imports = [ + ./ollama.nix + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/ollama.nix b/modules/nixos-modules/ollama/ollama.nix similarity index 53% rename from modules/nixos-modules/ollama.nix rename to modules/nixos-modules/ollama/ollama.nix index 8f194cf..dc7cdd9 100644 --- a/modules/nixos-modules/ollama.nix +++ b/modules/nixos-modules/ollama/ollama.nix @@ -27,26 +27,6 @@ allowedUDPPorts = ports; }; })) - (lib.mkIf config.host.impermanence.enable { - environment.persistence."/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = config.services.ollama.models; - user = config.services.ollama.user; - group = config.services.ollama.group; - } - { - directory = "/var/lib/private/ollama"; - user = config.services.ollama.user; - group = config.services.ollama.group; - mode = "0700"; - defaultPerms.mode = "0700"; - } - ]; - }; - }) ] ); } diff --git a/modules/nixos-modules/ollama/storage.nix b/modules/nixos-modules/ollama/storage.nix new file mode 100644 index 0000000..6ab0fc8 --- /dev/null +++ b/modules/nixos-modules/ollama/storage.nix @@ -0,0 +1,37 @@ +{ + config, + lib, + ... +}: { + options = { + services.ollama.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.ollama.enable && config.storage.impermanence.enable; + }; + }; + + config = lib.mkIf (config.services.ollama.enable) { + storage.datasets.replicate."system/root" = { + directories."/var/lib/private/ollama" = lib.mkIf config.services.ollama.impermanence.enable { + enable = true; + owner.name = config.services.ollama.user; + group.name = config.services.ollama.group; + owner.permissions = { + read = true; + write = true; + execute = false; + }; + group.permissions = { + read = false; + write = false; + execute = false; + }; + other.permissions = { + read = false; + write = false; + execute = false; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/packages/default.nix b/modules/nixos-modules/packages/default.nix deleted file mode 100644 index 208ee24..0000000 --- a/modules/nixos-modules/packages/default.nix +++ /dev/null @@ -1,17 +0,0 @@ -{pkgs, ...}: { - nixpkgs.overlays = [ - (final: prev: { - webtoon-dl = - pkgs.callPackage - ./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 - ./prostudiomasters.nix - {}; - }) - ]; -} diff --git a/modules/nixos-modules/packages/prostudiomasters.nix b/modules/nixos-modules/packages/prostudiomasters.nix deleted file mode 100644 index c1c03fe..0000000 --- a/modules/nixos-modules/packages/prostudiomasters.nix +++ /dev/null @@ -1,14 +0,0 @@ -{ - fetchurl, - appimageTools, -}: let - pname = "prostudiomasters"; - version = "2.5.6"; - src = fetchurl { - url = "https://download.prostudiomasters.com/linux/ProStudioMasters-${version}.AppImage"; - hash = "sha256-7owOwdcucFfl+JsVj+Seau2KOz0J4P/ep7WrBSNSmbs="; - }; -in - appimageTools.wrapType2 { - inherit pname version src; - } diff --git a/modules/nixos-modules/server/actual/actual.nix b/modules/nixos-modules/server/actual/actual.nix new file mode 100644 index 0000000..4cca449 --- /dev/null +++ b/modules/nixos-modules/server/actual/actual.nix @@ -0,0 +1,24 @@ +{ + lib, + config, + ... +}: let + const = import ./const.nix; + dataDirectory = const.dataDirectory; +in { + options.services.actual = { + port = lib.mkOption { + type = lib.types.port; + description = "The port to listen on"; + default = 5006; + }; + }; + config = lib.mkIf config.services.actual.enable { + services.actual = { + settings = { + port = config.services.actual.port; + dataDir = dataDirectory; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/actual/const.nix b/modules/nixos-modules/server/actual/const.nix new file mode 100644 index 0000000..14b715e --- /dev/null +++ b/modules/nixos-modules/server/actual/const.nix @@ -0,0 +1,3 @@ +{ + dataDirectory = "/var/lib/private/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..99778af --- /dev/null +++ b/modules/nixos-modules/server/actual/default.nix @@ -0,0 +1,8 @@ +{ + imports = [ + ./actual.nix + ./proxy.nix + ./fail2ban.nix + ./storage.nix + ]; +} 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/proxy.nix b/modules/nixos-modules/server/actual/proxy.nix new file mode 100644 index 0000000..9d37574 --- /dev/null +++ b/modules/nixos-modules/server/actual/proxy.nix @@ -0,0 +1,34 @@ +{ + lib, + config, + ... +}: { + options.services.actual = { + domain = lib.mkOption { + type = lib.types.str; + description = "domain that actual will be hosted at"; + default = "actual.arpa"; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for actual"; + default = []; + }; + reverseProxy.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.actual.enable && config.services.reverseProxy.enable; + }; + }; + + config = lib.mkIf config.services.actual.reverseProxy.enable { + services.reverseProxy.services.actual = { + target = "http://localhost:${toString config.services.actual.settings.port}"; + domain = config.services.actual.domain; + extraDomains = config.services.actual.extraDomains; + + settings = { + forwardHeaders.enable = true; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/actual/storage.nix b/modules/nixos-modules/server/actual/storage.nix new file mode 100644 index 0000000..d6b904e --- /dev/null +++ b/modules/nixos-modules/server/actual/storage.nix @@ -0,0 +1,22 @@ +{ + lib, + config, + ... +}: let + const = import ./const.nix; + dataDirectory = const.dataDirectory; +in { + options.services.actual.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.actual.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.actual.enable { + storage.datasets.replicate."system/root" = { + directories."${dataDirectory}" = lib.mkIf config.services.actual.impermanence.enable { + owner.name = "actual"; + group.name = "actual"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/adguardhome.nix b/modules/nixos-modules/server/adguardhome.nix deleted file mode 100644 index 866ad8a..0000000 --- a/modules/nixos-modules/server/adguardhome.nix +++ /dev/null @@ -1,72 +0,0 @@ -{ - lib, - config, - ... -}: let - dnsPort = 53; -in { - options.host.adguardhome = { - enable = lib.mkEnableOption "should home-assistant be enabled on this computer"; - directory = lib.mkOption { - type = lib.types.str; - default = "/var/lib/AdGuardHome/"; - }; - }; - config = lib.mkIf config.host.adguardhome.enable (lib.mkMerge [ - { - services.adguardhome = { - enable = true; - mutableSettings = false; - settings = { - dns = { - bootstrap_dns = [ - "1.1.1.1" - "9.9.9.9" - ]; - upstream_dns = [ - "dns.quad9.net" - ]; - }; - filtering = { - protection_enabled = true; - filtering_enabled = true; - - parental_enabled = false; # Parental control-based DNS requests filtering. - safe_search = { - enabled = false; # Enforcing "Safe search" option for search engines, when possible. - }; - }; - # The following notation uses map - # to not have to manually create {enabled = true; url = "";} for every filter - # This is, however, fully optional - filters = - map (url: { - enabled = true; - url = url; - }) [ - "https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt" - "https://adguardteam.github.io/HostlistsRegistry/assets/filter_9.txt" # The Big List of Hacked Malware Web Sites - "https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt" # malicious url blocklist - ]; - }; - }; - - networking.firewall.allowedTCPPorts = [ - dnsPort - ]; - } - (lib.mkIf config.host.impermanence.enable { - environment.persistence."/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = config.host.adguardhome.directory; - user = "adguardhome"; - group = "adguardhome"; - } - ]; - }; - }) - ]); -} diff --git a/modules/nixos-modules/server/bazarr/default.nix b/modules/nixos-modules/server/bazarr/default.nix new file mode 100644 index 0000000..cb2a5f0 --- /dev/null +++ b/modules/nixos-modules/server/bazarr/default.nix @@ -0,0 +1,5 @@ +{...}: { + imports = [ + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/server/bazarr/storage.nix b/modules/nixos-modules/server/bazarr/storage.nix new file mode 100644 index 0000000..a243d4c --- /dev/null +++ b/modules/nixos-modules/server/bazarr/storage.nix @@ -0,0 +1,21 @@ +{ + lib, + config, + ... +}: let + bazarr_data_directory = "/var/lib/bazarr"; +in { + options.services.bazarr.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.bazarr.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.bazarr.enable { + storage.datasets.replicate."system/root" = { + directories."${bazarr_data_directory}" = lib.mkIf config.services.bazarr.impermanence.enable { + owner.name = "bazarr"; + group.name = "bazarr"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/crab-hole/crab-hole.nix b/modules/nixos-modules/server/crab-hole/crab-hole.nix new file mode 100644 index 0000000..d76323a --- /dev/null +++ b/modules/nixos-modules/server/crab-hole/crab-hole.nix @@ -0,0 +1,193 @@ +{ + config, + lib, + ... +}: let + cfg = config.services.crab-hole; +in { + options.services.crab-hole = { + port = lib.mkOption { + type = lib.types.port; + default = 8080; + description = "Port for the crab-hole API to listen on."; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to open the firewall for the crab-hole API port."; + }; + + listen = lib.mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = "Address for the crab-hole API to listen on."; + }; + + show_doc = lib.mkEnableOption "OpenAPI documentation (loads content from third party websites)"; + + downstreams = { + host = { + enable = lib.mkEnableOption "host downstream DNS server accessible from network on all interfaces"; + port = lib.mkOption { + type = lib.types.port; + default = 53; + description = "Port for the host downstream DNS server to listen on."; + }; + openFirewall = lib.mkEnableOption "automatic port forwarding for the host downstream"; + disableSystemdResolved = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to automatically disable systemd-resolved when using port 53. Set to false if you want to handle the conflict manually."; + }; + }; + }; + + extraDownstreams = lib.mkOption { + type = lib.types.listOf (lib.types.submodule { + options = { + protocol = lib.mkOption { + type = lib.types.enum ["udp" "tcp" "tls" "https" "quic"]; + description = "Protocol for the downstream server."; + }; + + listen = lib.mkOption { + type = lib.types.str; + description = "Address to listen on for downstream connections."; + }; + + port = lib.mkOption { + type = lib.types.port; + description = "Port to listen on for downstream connections."; + }; + }; + }); + default = []; + description = "List of additional downstream DNS server configurations."; + }; + + upstreams = { + cloudFlare = { + enable = lib.mkEnableOption "Cloudflare DNS over TLS upstream servers (1.1.1.1 and 1.0.0.1)"; + }; + }; + + extraUpstreams = lib.mkOption { + type = lib.types.listOf (lib.types.submodule { + options = { + socket_addr = lib.mkOption { + type = lib.types.str; + description = "Socket address of the upstream DNS server (e.g., \"1.1.1.1:853\" or \"[2606:4700:4700::1111]:853\")."; + }; + + protocol = lib.mkOption { + type = lib.types.enum ["udp" "tcp" "tls" "https" "quic"]; + description = "Protocol to use for upstream DNS queries."; + }; + }; + }); + default = []; + description = "List of additional upstream DNS server configurations."; + }; + + blocklists = { + ad_malware = { + enable = lib.mkEnableOption "Host file for blocking ads and malware"; + url = lib.mkOption { + type = lib.types.str; + default = "http://sbc.io/hosts/hosts"; + description = "URL of the ad and malware blocklist host file"; + }; + }; + }; + + extraBlocklists = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = "Additional blocklist URLs to be added to the configuration"; + }; + }; + + config = lib.mkIf cfg.enable { + # Assertions for proper configuration + assertions = [ + { + assertion = !(cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && config.services.resolved.enable && cfg.downstreams.host.disableSystemdResolved); + message = "crab-hole host downstream cannot use port 53 while systemd-resolved is enabled. Either disable systemd-resolved or use a different port."; + } + { + assertion = !(cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && !cfg.downstreams.host.disableSystemdResolved && config.services.resolved.enable); + message = "crab-hole host downstream is configured to use port 53 but systemd-resolved is still enabled and disableSystemdResolved is false. Set disableSystemdResolved = true or manually disable systemd-resolved."; + } + ]; + + # Automatically disable systemd-resolved if using port 53 + services.resolved.enable = lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && cfg.downstreams.host.disableSystemdResolved) (lib.mkForce false); + + # Configure DNS nameservers when disabling systemd-resolved + networking.nameservers = lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && cfg.downstreams.host.disableSystemdResolved) (lib.mkDefault ["127.0.0.1" "1.1.1.1" "8.8.8.8"]); + + services.crab-hole.settings = lib.mkMerge [ + { + api = { + port = cfg.port; + listen = cfg.listen; + show_doc = cfg.show_doc; + }; + downstream = cfg.extraDownstreams; + upstream.name_servers = cfg.extraUpstreams; + blocklist.lists = cfg.extraBlocklists; + } + (lib.mkIf cfg.blocklists.ad_malware.enable { + blocklist.lists = [cfg.blocklists.ad_malware.url]; + }) + (lib.mkIf cfg.downstreams.host.enable { + downstream = [ + { + protocol = "udp"; + listen = "0.0.0.0"; + port = cfg.downstreams.host.port; + } + ]; + }) + (lib.mkIf cfg.upstreams.cloudFlare.enable { + upstream.name_servers = [ + { + socket_addr = "1.1.1.1:853"; + protocol = "tls"; + tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; + trust_nx_responses = false; + } + { + socket_addr = "1.0.0.1:853"; + protocol = "tls"; + tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; + trust_nx_responses = false; + } + { + socket_addr = "[2606:4700:4700::1111]:853"; + protocol = "tls"; + tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; + trust_nx_responses = false; + } + { + socket_addr = "[2606:4700:4700::1001]:853"; + protocol = "tls"; + tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; + trust_nx_responses = false; + } + ]; + }) + ]; + + # Open firewall if requested + networking.firewall = lib.mkMerge [ + (lib.mkIf cfg.openFirewall { + allowedTCPPorts = [cfg.port]; + }) + (lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.openFirewall) { + allowedUDPPorts = [cfg.downstreams.host.port]; + }) + ]; + }; +} diff --git a/modules/nixos-modules/server/crab-hole/default.nix b/modules/nixos-modules/server/crab-hole/default.nix new file mode 100644 index 0000000..9f990c5 --- /dev/null +++ b/modules/nixos-modules/server/crab-hole/default.nix @@ -0,0 +1,6 @@ +{...}: { + imports = [ + ./crab-hole.nix + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/server/crab-hole/storage.nix b/modules/nixos-modules/server/crab-hole/storage.nix new file mode 100644 index 0000000..827fb25 --- /dev/null +++ b/modules/nixos-modules/server/crab-hole/storage.nix @@ -0,0 +1,21 @@ +{ + lib, + config, + ... +}: let + workingDirectory = "/var/lib/private/crab-hole"; +in { + options.services.crab-hole.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.crab-hole.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.crab-hole.enable { + storage.datasets.replicate."system/root" = { + directories."${workingDirectory}" = lib.mkIf config.services.crab-hole.impermanence.enable { + owner.name = "crab-hole"; + group.name = "crab-hole"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/default.nix b/modules/nixos-modules/server/default.nix index 7beee8b..2b33089 100644 --- a/modules/nixos-modules/server/default.nix +++ b/modules/nixos-modules/server/default.nix @@ -1,16 +1,26 @@ {...}: { imports = [ - ./fail2ban.nix + ./reverseProxy + ./fail2ban + ./postgres ./network_storage - ./reverse_proxy.nix - ./postgres.nix - ./podman.nix - ./jellyfin.nix - ./forgejo.nix - ./searx.nix - ./virt-home-assistant.nix - ./adguardhome.nix - ./immich.nix - ./qbittorent.nix + + ./actual + ./bazarr + ./crab-hole + ./flaresolverr + ./forgejo + ./home-assistant + ./immich + ./jackett + ./jellyfin + ./lidarr + ./panoramax + ./paperless + ./qbittorent + ./radarr + ./searx + ./sonarr + ./wyoming.nix ]; } diff --git a/modules/nixos-modules/server/fail2ban.nix b/modules/nixos-modules/server/fail2ban.nix deleted file mode 100644 index be83e6f..0000000 --- a/modules/nixos-modules/server/fail2ban.nix +++ /dev/null @@ -1,98 +0,0 @@ -{ - lib, - pkgs, - config, - ... -}: let - dataFolder = "/var/lib/fail2ban"; - dataFile = "fail2ban.sqlite3"; -in { - config = lib.mkIf config.services.fail2ban.enable (lib.mkMerge [ - { - environment.etc = { - "fail2ban/filter.d/nginx.local".text = lib.mkIf config.services.nginx.enable ( - pkgs.lib.mkDefault (pkgs.lib.mkAfter '' - [Definition] - failregex = "limiting requests, excess:.* by zone.*client: " - '') - ); - # "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 = { - maxretry = 5; - ignoreIP = [ - # Whitelist local networks - "10.0.0.0/8" - "172.16.0.0/12" - "192.168.0.0/16" - - # tail scale tailnet - "100.64.0.0/10" - "fd7a:115c:a1e0::/48" - ]; - bantime = "24h"; # Ban IPs for one day on the first ban - bantime-increment = { - enable = true; # Enable increment of bantime after each violation - formula = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)"; - maxtime = "168h"; # Do not ban for more than 1 week - overalljails = true; # Calculate the ban time based on all the violations - }; - jails = { - nginx-iptables.settings = lib.mkIf config.services.nginx.enable { - enabled = true; - filter = "nginx"; - action = ''iptables-multiport[name=HTTP, port="http,https"]''; - backend = "auto"; - findtime = 600; - bantime = 600; - maxretry = 5; - }; - # 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; - # }; - # TODO; figure out if there is any fail2ban things we can do on searx - # searx-iptables.settings = lib.mkIf config.services.searx.enable {}; - }; - }; - } - (lib.mkIf config.host.impermanence.enable { - assertions = [ - { - assertion = config.services.fail2ban.daemonSettings.Definition.dbfile == "${dataFolder}/${dataFile}"; - message = "fail2ban data file does not match persistence"; - } - ]; - - environment.persistence."/persist/system/root" = { - directories = [ - { - directory = dataFolder; - user = "fail2ban"; - group = "fail2ban"; - } - ]; - }; - }) - ]); -} diff --git a/modules/nixos-modules/server/fail2ban/default.nix b/modules/nixos-modules/server/fail2ban/default.nix new file mode 100644 index 0000000..84a46d4 --- /dev/null +++ b/modules/nixos-modules/server/fail2ban/default.nix @@ -0,0 +1,6 @@ +{...}: { + imports = [ + ./fail2ban.nix + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/server/fail2ban/fail2ban.nix b/modules/nixos-modules/server/fail2ban/fail2ban.nix new file mode 100644 index 0000000..261c68f --- /dev/null +++ b/modules/nixos-modules/server/fail2ban/fail2ban.nix @@ -0,0 +1,51 @@ +{ + lib, + pkgs, + config, + ... +}: { + config = lib.mkIf config.services.fail2ban.enable { + environment.etc = { + "fail2ban/filter.d/nginx.local".text = lib.mkIf config.services.nginx.enable ( + pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [Definition] + failregex = "limiting requests, excess:.* by zone.*client: " + '') + ); + }; + + services.fail2ban = { + maxretry = 5; + ignoreIP = [ + # Whitelist local networks + "10.0.0.0/8" + "172.16.0.0/12" + "192.168.0.0/16" + + # tail scale tailnet + "100.64.0.0/10" + "fd7a:115c:a1e0::/48" + ]; + bantime = "24h"; # Ban IPs for one day on the first ban + bantime-increment = { + enable = true; # Enable increment of bantime after each violation + formula = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)"; + maxtime = "168h"; # Do not ban for more than 1 week + overalljails = true; # Calculate the ban time based on all the violations + }; + jails = { + nginx-iptables.settings = lib.mkIf config.services.nginx.enable { + enabled = true; + filter = "nginx"; + action = ''iptables-multiport[name=HTTP, port="http,https"]''; + backend = "auto"; + findtime = 600; + bantime = 600; + maxretry = 5; + }; + # TODO; figure out if there is any fail2ban things we can do on searx + # searx-iptables.settings = lib.mkIf config.services.searx.enable {}; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/fail2ban/storage.nix b/modules/nixos-modules/server/fail2ban/storage.nix new file mode 100644 index 0000000..1ef02c7 --- /dev/null +++ b/modules/nixos-modules/server/fail2ban/storage.nix @@ -0,0 +1,22 @@ +{ + lib, + config, + ... +}: let + dataFolder = "/var/lib/fail2ban"; + dataFile = "fail2ban.sqlite3"; +in { + options.services.fail2ban.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.fail2ban.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.fail2ban.enable { + storage.datasets.replicate."system/root" = { + directories."${dataFolder}" = lib.mkIf config.services.fail2ban.impermanence.enable { + owner.name = "fail2ban"; + group.name = "fail2ban"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/flaresolverr/default.nix b/modules/nixos-modules/server/flaresolverr/default.nix new file mode 100644 index 0000000..cb2a5f0 --- /dev/null +++ b/modules/nixos-modules/server/flaresolverr/default.nix @@ -0,0 +1,5 @@ +{...}: { + imports = [ + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/server/flaresolverr/storage.nix b/modules/nixos-modules/server/flaresolverr/storage.nix new file mode 100644 index 0000000..919318c --- /dev/null +++ b/modules/nixos-modules/server/flaresolverr/storage.nix @@ -0,0 +1,19 @@ +{ + lib, + config, + ... +}: { + options.services.flaresolverr.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.flaresolverr.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.flaresolverr.enable { + storage.datasets.replicate."system/root" = { + directories."/var/lib/flaresolverr" = lib.mkIf config.services.flaresolverr.impermanence.enable { + owner.name = "flaresolverr"; + group.name = "flaresolverr"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/forgejo.nix b/modules/nixos-modules/server/forgejo.nix deleted file mode 100644 index 40a5303..0000000 --- a/modules/nixos-modules/server/forgejo.nix +++ /dev/null @@ -1,112 +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 [ - { - host = { - reverse_proxy.subdomains.${config.services.forgejo.subdomain} = { - target = "http://localhost:${toString forgejoPort}"; - }; - postgres = { - enable = true; - extraUsers = { - ${db_user} = { - isClient = true; - }; - }; - }; - }; - - 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 = config.users.users.git.name; - 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.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..bb8781c --- /dev/null +++ b/modules/nixos-modules/server/forgejo/database.nix @@ -0,0 +1,32 @@ +{ + lib, + config, + ... +}: let + usingPostgres = config.services.forgejo.database.type == "postgres"; +in { + config = lib.mkIf config.services.forgejo.enable { + assertions = [ + { + assertion = !usingPostgres || config.services.postgresql.enable; + message = "PostgreSQL must be enabled when Forgejo database type is postgres"; + } + { + assertion = !(usingPostgres && config.services.forgejo.database.createDatabase) || (builtins.any (db: db == "forgejo") config.services.postgresql.ensureDatabases); + message = "Forgejo built-in database creation failed - expected 'forgejo' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}"; + } + { + assertion = !(usingPostgres && config.services.forgejo.database.createDatabase) || (builtins.any (user: user.name == "forgejo") config.services.postgresql.ensureUsers); + message = "Forgejo built-in user creation failed - expected user 'forgejo' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}"; + } + ]; + + services.forgejo.database.createDatabase = lib.mkDefault usingPostgres; + + systemd.services.forgejo = lib.mkIf usingPostgres { + requires = [ + config.systemd.services.postgresql.name + ]; + }; + }; +} diff --git a/modules/nixos-modules/server/forgejo/default.nix b/modules/nixos-modules/server/forgejo/default.nix new file mode 100644 index 0000000..c990e57 --- /dev/null +++ b/modules/nixos-modules/server/forgejo/default.nix @@ -0,0 +1,9 @@ +{ + imports = [ + ./forgejo.nix + ./proxy.nix + ./database.nix + ./fail2ban.nix + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/server/forgejo/fail2ban.nix b/modules/nixos-modules/server/forgejo/fail2ban.nix new file mode 100644 index 0000000..dfe221a --- /dev/null +++ b/modules/nixos-modules/server/forgejo/fail2ban.nix @@ -0,0 +1,41 @@ +{ + lib, + config, + pkgs, + ... +}: { + options.services.forgejo = { + fail2ban = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.forgejo.enable && config.services.fail2ban.enable; + }; + }; + }; + + config = lib.mkIf config.services.forgejo.fail2ban.enable { + environment.etc = { + "fail2ban/filter.d/forgejo.local".text = lib.mkIf config.services.forgejo.enable ( + pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [Definition] + failregex = ".*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from " + '') + ); + }; + + services.fail2ban = { + jails = { + forgejo-iptables.settings = lib.mkIf config.services.forgejo.enable { + enabled = true; + filter = "forgejo"; + action = ''iptables-multiport[name=HTTP, port="http,https"]''; + logpath = "${config.services.forgejo.settings.log.ROOT_PATH}/*.log"; + backend = "auto"; + findtime = 600; + bantime = 600; + maxretry = 5; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/forgejo/forgejo.nix b/modules/nixos-modules/server/forgejo/forgejo.nix new file mode 100644 index 0000000..70d3087 --- /dev/null +++ b/modules/nixos-modules/server/forgejo/forgejo.nix @@ -0,0 +1,46 @@ +{ + lib, + config, + ... +}: let + const = import ./const.nix; + httpPort = const.httpPort; + sshPort = const.sshPort; + db_user = "forgejo"; +in { + config = lib.mkIf config.services.forgejo.enable { + assertions = [ + { + assertion = config.services.forgejo.settings.server.BUILTIN_SSH_SERVER_USER == config.users.users.git.name; + message = "Forgejo BUILTIN_SSH_SERVER_USER hardcoded value does not match expected git user name"; + } + ]; + + services.forgejo = { + database = { + type = "postgres"; + socket = "/run/postgresql"; + }; + lfs.enable = true; + settings = { + server = { + DOMAIN = config.services.forgejo.reverseProxy.domain; + HTTP_PORT = httpPort; + START_SSH_SERVER = true; + SSH_LISTEN_PORT = sshPort; + SSH_PORT = 22; + BUILTIN_SSH_SERVER_USER = "git"; + ROOT_URL = "https://git.jan-leila.com"; + }; + service = { + DISABLE_REGISTRATION = true; + }; + database = { + DB_TYPE = "postgres"; + NAME = db_user; + USER = db_user; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/forgejo/proxy.nix b/modules/nixos-modules/server/forgejo/proxy.nix new file mode 100644 index 0000000..c2d3131 --- /dev/null +++ b/modules/nixos-modules/server/forgejo/proxy.nix @@ -0,0 +1,43 @@ +{ + lib, + config, + ... +}: let + const = import ./const.nix; + httpPort = const.httpPort; +in { + options.services.forgejo = { + reverseProxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.forgejo.enable && config.services.reverseProxy.enable; + }; + domain = lib.mkOption { + type = lib.types.str; + description = "domain that forgejo will be hosted at"; + default = "git.jan-leila.com"; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for forgejo"; + default = []; + }; + }; + }; + + config = lib.mkIf config.services.forgejo.reverseProxy.enable { + services.reverseProxy.services.forgejo = { + target = "http://localhost:${toString httpPort}"; + domain = config.services.forgejo.reverseProxy.domain; + extraDomains = config.services.forgejo.reverseProxy.extraDomains; + + settings = { + forwardHeaders.enable = true; + }; + }; + + networking.firewall.allowedTCPPorts = [ + config.services.forgejo.settings.server.SSH_LISTEN_PORT + ]; + }; +} diff --git a/modules/nixos-modules/server/forgejo/storage.nix b/modules/nixos-modules/server/forgejo/storage.nix new file mode 100644 index 0000000..da30ed9 --- /dev/null +++ b/modules/nixos-modules/server/forgejo/storage.nix @@ -0,0 +1,21 @@ +{ + lib, + config, + ... +}: let + stateDir = "/var/lib/forgejo"; +in { + options.services.forgejo.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.forgejo.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.forgejo.enable { + storage.datasets.replicate."system/root" = { + directories."${stateDir}" = lib.mkIf config.services.forgejo.impermanence.enable { + owner.name = "forgejo"; + group.name = "forgejo"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/home-assistant.nix b/modules/nixos-modules/server/home-assistant.nix deleted file mode 100644 index a90bd6d..0000000 --- a/modules/nixos-modules/server/home-assistant.nix +++ /dev/null @@ -1,130 +0,0 @@ -{ - lib, - config, - inputs, - ... -}: let - configDir = "/var/lib/hass"; -in { - options.host.home-assistant = { - enable = lib.mkEnableOption "should home-assistant be enabled on this computer"; - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that home-assistant will be hosted at"; - default = "home-assistant"; - }; - }; - - config = lib.mkIf config.host.home-assistant.enable (lib.mkMerge [ - { - virtualisation.libvirt = { - swtpm.enable = true; - connections."qemu:///session" = { - networks = [ - { - definition = inputs.nix-virt.lib.network.writeXML (inputs.nix-virt.lib.network.templates.bridge - { - uuid = "d57e37e2-311f-4e5c-a484-97c2210c2770"; - subnet_byte = 71; - }); - active = true; - } - ]; - domains = [ - { - definition = inputs.nix-virt.lib.domain.writeXML (inputs.nix-virt.lib.domain.templates.linux - { - name = "Home Assistant"; - uuid = "c5cc0efc-6101-4c1d-be31-acbba203ccde"; - memory = { - count = 4; - unit = "GiB"; - }; - # storage_vol = { - # pool = "MyPool"; - # volume = "Penguin.qcow2"; - # }; - }); - } - ]; - }; - }; - - # systemd.tmpfiles.rules = [ - # "f ${config.services.home-assistant.configDir}/automations.yaml 0755 hass hass" - # ]; - # services.home-assistant = { - # enable = true; - # configDir = configDir; - # extraComponents = [ - # "met" - # "radio_browser" - # "isal" - # "zha" - # "jellyfin" - # "webostv" - # "tailscale" - # "syncthing" - # "sonos" - # "analytics_insights" - # "unifi" - # "openweathermap" - # ]; - # config = { - # http = { - # server_port = 8082; - # use_x_forwarded_for = true; - # trusted_proxies = ["127.0.0.1" "::1"]; - # ip_ban_enabled = true; - # login_attempts_threshold = 10; - # }; - # # recorder.db_url = "postgresql://@/${db_user}"; - # "automation manual" = []; - # "automation ui" = "!include automations.yaml"; - # }; - # extraPackages = python3Packages: - # with python3Packages; [ - # hassil - # numpy - # gtts - # ]; - # }; - # host = { - # reverse_proxy.subdomains.${config.host.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.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..f1927ed --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/database.nix @@ -0,0 +1,53 @@ +{ + lib, + config, + ... +}: { + options.services.home-assistant = { + postgres = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Use PostgreSQL instead of SQLite"; + }; + user = lib.mkOption { + type = lib.types.str; + default = "hass"; + description = "Database user name"; + }; + database = lib.mkOption { + type = lib.types.str; + default = "hass"; + description = "Database name"; + }; + }; + }; + + config = lib.mkIf config.services.home-assistant.enable { + assertions = [ + { + assertion = !config.services.home-assistant.postgres.enable || config.services.postgresql.enable; + message = "PostgreSQL must be enabled when using postgres database for Home Assistant"; + } + ]; + + services.postgresql.databases.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable { + enable = true; + user = config.services.home-assistant.postgres.user; + database = config.services.home-assistant.postgres.database; + }; + + services.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable { + extraPackages = python3Packages: + with python3Packages; [ + psycopg2 + ]; + }; + + systemd.services.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable { + requires = [ + config.systemd.services.postgresql.name + ]; + }; + }; +} diff --git a/modules/nixos-modules/server/home-assistant/default.nix b/modules/nixos-modules/server/home-assistant/default.nix new file mode 100644 index 0000000..d213964 --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/default.nix @@ -0,0 +1,10 @@ +{ + imports = [ + ./home-assistant.nix + ./proxy.nix + ./database.nix + ./fail2ban.nix + ./storage.nix + ./extensions + ]; +} 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..25194ef --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/fail2ban.nix @@ -0,0 +1,49 @@ +{ + lib, + pkgs, + config, + ... +}: { + options.services.home-assistant = { + fail2ban = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.fail2ban.enable && config.services.home-assistant.enable; + }; + }; + }; + + config = lib.mkIf config.services.home-assistant.fail2ban.enable { + environment.etc = { + "fail2ban/filter.d/hass.local".text = ( + pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [INCLUDES] + before = common.conf + + [Definition] + failregex = ^%(__prefix_line)s.*Login attempt or request with invalid authentication from .*$ + + ignoreregex = + + [Init] + datepattern = ^%%Y-%%m-%%d %%H:%%M:%%S + '') + ); + }; + + services.fail2ban = { + jails = { + home-assistant-iptables.settings = { + enabled = true; + filter = "hass"; + action = ''iptables-multiport[name=HTTP, port="http,https"]''; + logpath = "${config.services.home-assistant.configDir}/*.log"; + backend = "auto"; + findtime = 600; + bantime = 600; + maxretry = 5; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/home-assistant/home-assistant.nix b/modules/nixos-modules/server/home-assistant/home-assistant.nix new file mode 100644 index 0000000..fa58d5e --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/home-assistant.nix @@ -0,0 +1,104 @@ +{ + lib, + config, + ... +}: { + options.services.home-assistant = { + database = lib.mkOption { + type = lib.types.enum [ + "builtin" + "postgres" + ]; + description = "what database do we want to use"; + default = "builtin"; + }; + + extensions = { + sonos = { + enable = lib.mkEnableOption "enable the sonos plugin"; + port = lib.mkOption { + type = lib.types.int; + default = 1400; + description = "what port to use for sonos discovery"; + }; + }; + jellyfin = { + enable = lib.mkEnableOption "enable the jellyfin plugin"; + }; + wyoming = { + enable = lib.mkEnableOption "enable wyoming"; + }; + }; + }; + + config = lib.mkIf config.services.home-assistant.enable (lib.mkMerge [ + { + services.home-assistant = { + configDir = "/var/lib/hass"; + extraComponents = [ + "default_config" + "esphome" + "met" + "radio_browser" + "isal" + "zha" + "webostv" + "tailscale" + "syncthing" + "analytics_insights" + "unifi" + "openweathermap" + "ollama" + "mobile_app" + "logbook" + "ssdp" + "usb" + "webhook" + "bluetooth" + "dhcp" + "energy" + "history" + "backup" + "assist_pipeline" + "conversation" + "sun" + "zeroconf" + "cpuspeed" + ]; + config = { + http = { + server_port = 8123; + use_x_forwarded_for = true; + trusted_proxies = ["127.0.0.1" "::1"]; + ip_ban_enabled = true; + login_attempts_threshold = 10; + }; + homeassistant = { + external_url = "https://${config.services.home-assistant.domain}"; + # internal_url = "http://192.168.1.2:8123"; + }; + recorder.db_url = "postgresql://@/${config.services.home-assistant.configDir}"; + "automation manual" = []; + "automation ui" = "!include automations.yaml"; + mobile_app = {}; + }; + extraPackages = python3Packages: + with python3Packages; [ + hassil + numpy + gtts + ]; + }; + + # TODO: configure /var/lib/hass/secrets.yaml via sops + + networking.firewall.allowedUDPPorts = [ + 1900 + ]; + + systemd.tmpfiles.rules = [ + "f ${config.services.home-assistant.configDir}/automations.yaml 0755 hass hass" + ]; + } + ]); +} diff --git a/modules/nixos-modules/server/home-assistant/proxy.nix b/modules/nixos-modules/server/home-assistant/proxy.nix new file mode 100644 index 0000000..b756459 --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/proxy.nix @@ -0,0 +1,43 @@ +{ + lib, + config, + ... +}: { + options.services.home-assistant = { + domain = lib.mkOption { + type = lib.types.str; + description = "domain that home-assistant will be hosted at"; + default = "home-assistant.arpa"; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for home-assistant"; + default = []; + }; + reverseProxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.reverseProxy.enable && config.services.home-assistant.enable; + }; + }; + }; + + config = lib.mkIf config.services.home-assistant.reverseProxy.enable { + services.reverseProxy.services.home-assistant = { + target = "http://localhost:${toString config.services.home-assistant.config.http.server_port}"; + domain = config.services.home-assistant.domain; + extraDomains = config.services.home-assistant.extraDomains; + + settings = { + proxyWebsockets.enable = true; + forwardHeaders.enable = true; + + # Custom timeout settings + proxyHeaders = { + enable = true; + timeout = 90; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/home-assistant/storage.nix b/modules/nixos-modules/server/home-assistant/storage.nix new file mode 100644 index 0000000..60e5085 --- /dev/null +++ b/modules/nixos-modules/server/home-assistant/storage.nix @@ -0,0 +1,21 @@ +{ + lib, + config, + ... +}: let + configDir = "/var/lib/hass"; +in { + options.services.home-assistant.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.home-assistant.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.home-assistant.enable { + storage.datasets.replicate."system/root" = { + directories."${configDir}" = lib.mkIf config.services.home-assistant.impermanence.enable { + owner.name = "hass"; + group.name = "hass"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/immich.nix b/modules/nixos-modules/server/immich.nix deleted file mode 100644 index e7088a9..0000000 --- a/modules/nixos-modules/server/immich.nix +++ /dev/null @@ -1,95 +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 = { - 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 = { - ${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.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..52af51e --- /dev/null +++ b/modules/nixos-modules/server/immich/database.nix @@ -0,0 +1,30 @@ +{ + lib, + config, + ... +}: { + config = lib.mkIf config.services.immich.enable { + assertions = [ + { + assertion = !config.services.immich.database.enable || config.services.postgresql.enable; + message = "PostgreSQL must be enabled when using postgres database for Immich"; + } + { + assertion = !(config.services.immich.database.enable && config.services.immich.database.createDB) || (builtins.any (db: db == "immich") config.services.postgresql.ensureDatabases); + message = "Immich built-in database creation failed - expected 'immich' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}"; + } + { + assertion = !(config.services.immich.database.enable && config.services.immich.database.createDB) || (builtins.any (user: user.name == "immich") config.services.postgresql.ensureUsers); + message = "Immich built-in user creation failed - expected user 'immich' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}"; + } + ]; + + # Note: Immich has built-in database creation via services.immich.database.createDB we only add the systemd dependency + + systemd.services.immich-server = lib.mkIf config.services.immich.database.enable { + requires = [ + config.systemd.services.postgresql.name + ]; + }; + }; +} diff --git a/modules/nixos-modules/server/immich/default.nix b/modules/nixos-modules/server/immich/default.nix new file mode 100644 index 0000000..75ae2fd --- /dev/null +++ b/modules/nixos-modules/server/immich/default.nix @@ -0,0 +1,20 @@ +{...}: { + imports = [ + ./proxy.nix + ./database.nix + ./fail2ban.nix + ./storage.nix + ]; + + # NOTE: This shouldn't be needed now that we are out of testing + # config = lib.mkIf config.services.immich.enable { + # networking.firewall.interfaces.${config.services.tailscale.interfaceName} = { + # allowedUDPPorts = [ + # config.services.immich.port + # ]; + # allowedTCPPorts = [ + # config.services.immich.port + # ]; + # }; + # }; +} diff --git a/modules/nixos-modules/server/immich/fail2ban.nix b/modules/nixos-modules/server/immich/fail2ban.nix new file mode 100644 index 0000000..21593e7 --- /dev/null +++ b/modules/nixos-modules/server/immich/fail2ban.nix @@ -0,0 +1,35 @@ +{ + lib, + config, + pkgs, + ... +}: { + options.services.immich = { + fail2ban = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.fail2ban.enable && config.services.immich.enable; + }; + }; + }; + + config = lib.mkIf config.services.immich.fail2ban.enable { + environment.etc = { + "fail2ban/filter.d/immich.local".text = pkgs.lib.mkDefault (pkgs.lib.mkAfter '' + [Definition] + failregex = immich-server.*Failed login attempt for user.+from ip address\s? + journalmatch = CONTAINER_TAG=immich-server + ''); + }; + + services.fail2ban = { + jails = { + immich-iptables.settings = { + enabled = true; + filter = "immich"; + backend = "systemd"; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/immich/proxy.nix b/modules/nixos-modules/server/immich/proxy.nix new file mode 100644 index 0000000..9c8c165 --- /dev/null +++ b/modules/nixos-modules/server/immich/proxy.nix @@ -0,0 +1,44 @@ +{ + lib, + config, + ... +}: { + options.services.immich = { + domain = lib.mkOption { + type = lib.types.str; + description = "domain that immich will be hosted at"; + default = "immich.arpa"; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for immich"; + default = []; + }; + reverseProxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.immich.enable && config.services.reverseProxy.enable; + }; + }; + }; + + config = lib.mkIf config.services.immich.reverseProxy.enable { + services.reverseProxy.services.immich = { + target = "http://localhost:${toString config.services.immich.port}"; + domain = config.services.immich.domain; + extraDomains = config.services.immich.extraDomains; + + settings = { + proxyWebsockets.enable = true; + forwardHeaders.enable = true; + maxBodySize = 50000; + + # Custom timeout settings + proxyHeaders = { + enable = true; + timeout = 600; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/immich/storage.nix b/modules/nixos-modules/server/immich/storage.nix new file mode 100644 index 0000000..de24329 --- /dev/null +++ b/modules/nixos-modules/server/immich/storage.nix @@ -0,0 +1,21 @@ +{ + lib, + config, + ... +}: let + mediaLocation = "/var/lib/immich"; +in { + options.services.immich.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.immich.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.immich.enable { + storage.datasets.replicate."system/root" = { + directories."${mediaLocation}" = lib.mkIf config.services.immich.impermanence.enable { + owner.name = "immich"; + group.name = "immich"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/jackett/default.nix b/modules/nixos-modules/server/jackett/default.nix new file mode 100644 index 0000000..5043814 --- /dev/null +++ b/modules/nixos-modules/server/jackett/default.nix @@ -0,0 +1,17 @@ +{...}: { + imports = [ + ./storage.nix + ]; + + config = { + nixpkgs.overlays = [ + # Disable jackett tests due to date-related test failures + # (ParseDateTimeGoLangTest expects 2024-09-14 but gets 2025-09-14 due to year rollover logic) + (final: prev: { + jackett = prev.jackett.overrideAttrs (oldAttrs: { + doCheck = false; + }); + }) + ]; + }; +} diff --git a/modules/nixos-modules/server/jackett/storage.nix b/modules/nixos-modules/server/jackett/storage.nix new file mode 100644 index 0000000..5f202e6 --- /dev/null +++ b/modules/nixos-modules/server/jackett/storage.nix @@ -0,0 +1,21 @@ +{ + lib, + config, + ... +}: let + jackett_data_directory = "/var/lib/jackett/.config/Jackett"; +in { + options.services.jackett.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.jackett.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.jackett.enable { + storage.datasets.replicate."system/root" = { + directories."${jackett_data_directory}" = lib.mkIf config.services.jackett.impermanence.enable { + owner.name = "jackett"; + group.name = "jackett"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/jellyfin.nix b/modules/nixos-modules/server/jellyfin.nix deleted file mode 100644 index a8bbe71..0000000 --- a/modules/nixos-modules/server/jellyfin.nix +++ /dev/null @@ -1,140 +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 [ - { - 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; - ''; - }; - environment.systemPackages = [ - pkgs.jellyfin - pkgs.jellyfin-web - pkgs.jellyfin-ffmpeg - ]; - - networking.firewall.allowedTCPPorts = [jellyfinPort dlanPort]; - } - (lib.mkIf config.services.fail2ban.enable { - environment.etc = { - "fail2ban/filter.d/jellyfin.local".text = lib.mkIf config.services.jellyfin.enable ( - pkgs.lib.mkDefault (pkgs.lib.mkAfter '' - [Definition] - failregex = "^.*Authentication request for .* has been denied \\\(IP: \"\"\\\)\\\." - '') - ); - }; - - services.fail2ban = { - jails = { - jellyfin-iptables.settings = lib.mkIf config.services.jellyfin.enable { - 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..4770ae1 --- /dev/null +++ b/modules/nixos-modules/server/jellyfin/default.nix @@ -0,0 +1,8 @@ +{ + imports = [ + ./jellyfin.nix + ./proxy.nix + ./fail2ban.nix + ./storage.nix + ]; +} 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/jellyfin.nix b/modules/nixos-modules/server/jellyfin/jellyfin.nix new file mode 100644 index 0000000..9bfa921 --- /dev/null +++ b/modules/nixos-modules/server/jellyfin/jellyfin.nix @@ -0,0 +1,32 @@ +{ + lib, + pkgs, + config, + ... +}: let + jellyfinPort = 8096; + dlanPort = 1900; +in { + options.services.jellyfin = { + media_directory = lib.mkOption { + type = lib.types.str; + description = "directory jellyfin media will be hosted at"; + default = "/srv/jellyfin/media"; + }; + }; + + config = lib.mkIf config.services.jellyfin.enable { + environment.systemPackages = [ + pkgs.jellyfin + pkgs.jellyfin-web + pkgs.jellyfin-ffmpeg + ]; + + networking.firewall.allowedTCPPorts = [jellyfinPort dlanPort]; + + systemd.tmpfiles.rules = [ + "d ${config.services.jellyfin.media_directory} 2770 jellyfin jellyfin_media" + "A ${config.services.jellyfin.media_directory} - - - - u:jellyfin:rwX,g:jellyfin_media:rwX,o::-" + ]; + }; +} diff --git a/modules/nixos-modules/server/jellyfin/proxy.nix b/modules/nixos-modules/server/jellyfin/proxy.nix new file mode 100644 index 0000000..35289e7 --- /dev/null +++ b/modules/nixos-modules/server/jellyfin/proxy.nix @@ -0,0 +1,41 @@ +{ + lib, + config, + ... +}: let + jellyfinPort = 8096; +in { + options.services.jellyfin = { + domain = lib.mkOption { + type = lib.types.str; + description = "domain that jellyfin will be hosted at"; + default = "jellyfin.arpa"; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for jellyfin"; + default = []; + }; + reverseProxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.jellyfin.enable && config.services.reverseProxy.enable; + }; + }; + }; + + config = lib.mkIf config.services.jellyfin.reverseProxy.enable { + services.reverseProxy.services.jellyfin = { + target = "http://localhost:${toString jellyfinPort}"; + domain = config.services.jellyfin.domain; + extraDomains = config.services.jellyfin.extraDomains; + + settings = { + forwardHeaders.enable = true; + maxBodySize = 20; + noSniff.enable = true; + proxyBuffering.enable = false; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/jellyfin/storage.nix b/modules/nixos-modules/server/jellyfin/storage.nix new file mode 100644 index 0000000..5cff3e8 --- /dev/null +++ b/modules/nixos-modules/server/jellyfin/storage.nix @@ -0,0 +1,56 @@ +{ + lib, + config, + ... +}: let + jellyfin_data_directory = "/var/lib/jellyfin"; + jellyfin_cache_directory = "/var/cache/jellyfin"; +in { + options.services.jellyfin.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.jellyfin.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.jellyfin.enable { + storage.datasets.replicate = { + "system/root" = { + directories = { + "${jellyfin_data_directory}" = lib.mkIf config.services.jellyfin.impermanence.enable { + enable = true; + owner.name = "jellyfin"; + group.name = "jellyfin"; + }; + "${jellyfin_cache_directory}" = lib.mkIf config.services.jellyfin.impermanence.enable { + enable = true; + owner.name = "jellyfin"; + group.name = "jellyfin"; + }; + }; + }; + "system/media" = { + mount = "/persist/replicate/system/media"; + + directories."${config.services.jellyfin.media_directory}" = lib.mkIf config.services.jellyfin.impermanence.enable { + enable = true; + owner.name = "jellyfin"; + group.name = "jellyfin_media"; + owner.permissions = { + read = true; + write = true; + execute = true; + }; + group.permissions = { + read = true; + write = true; + execute = true; + }; + other.permissions = { + read = false; + write = false; + execute = false; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/lidarr/default.nix b/modules/nixos-modules/server/lidarr/default.nix new file mode 100644 index 0000000..cb2a5f0 --- /dev/null +++ b/modules/nixos-modules/server/lidarr/default.nix @@ -0,0 +1,5 @@ +{...}: { + imports = [ + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/server/lidarr/storage.nix b/modules/nixos-modules/server/lidarr/storage.nix new file mode 100644 index 0000000..c4c020e --- /dev/null +++ b/modules/nixos-modules/server/lidarr/storage.nix @@ -0,0 +1,21 @@ +{ + lib, + config, + ... +}: let + lidarr_data_directory = "/var/lib/lidarr/.config/Lidarr"; +in { + options.services.lidarr.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.lidarr.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.lidarr.enable { + storage.datasets.replicate."system/root" = { + directories."${lidarr_data_directory}" = lib.mkIf config.services.lidarr.impermanence.enable { + owner.name = "lidarr"; + group.name = "lidarr"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/network_storage/default.nix b/modules/nixos-modules/server/network_storage/default.nix index 00ea7ac..cd100ab 100644 --- a/modules/nixos-modules/server/network_storage/default.nix +++ b/modules/nixos-modules/server/network_storage/default.nix @@ -1,90 +1,6 @@ { - config, - lib, - ... -}: let - export_directory = config.host.network_storage.export_directory; -in { imports = [ + ./network_storage.nix ./nfs.nix ]; - - options = { - host.network_storage = { - enable = lib.mkEnableOption "is this machine going to export network storage"; - export_directory = lib.mkOption { - type = lib.types.path; - description = "what are exports going to be stored in"; - default = "/export"; - }; - directories = lib.mkOption { - type = lib.types.listOf (lib.types.submodule ({config, ...}: { - options = { - folder = lib.mkOption { - type = lib.types.str; - description = "what is the name of this export directory"; - }; - bind = lib.mkOption { - type = lib.types.nullOr lib.types.path; - description = "is this directory bound to anywhere"; - default = null; - }; - user = lib.mkOption { - type = lib.types.str; - description = "what user owns this directory"; - default = "nouser"; - }; - group = lib.mkOption { - type = lib.types.str; - description = "what group owns this directory"; - default = "nogroup"; - }; - _directory = lib.mkOption { - internal = true; - readOnly = true; - type = lib.types.path; - default = "${export_directory}/${config.folder}"; - }; - }; - })); - description = "list of directory names to export"; - }; - }; - }; - - config = lib.mkIf config.host.network_storage.enable (lib.mkMerge [ - { - # create any folders that we need to have for our exports - systemd.tmpfiles.rules = - [ - "d ${config.host.network_storage.export_directory} 2775 nobody nogroup -" - ] - ++ ( - builtins.map ( - directory: "d ${directory._directory} 2770 ${directory.user} ${directory.group}" - ) - config.host.network_storage.directories - ); - - # set up any bind mounts that we need for our exports - fileSystems = builtins.listToAttrs ( - builtins.map (directory: - lib.attrsets.nameValuePair directory._directory { - device = directory.bind; - options = ["bind"]; - }) ( - builtins.filter (directory: directory.bind != null) config.host.network_storage.directories - ) - ); - } - # (lib.mkIf config.host.impermanence.enable { - # environment.persistence."/persist/system/root" = { - # enable = true; - # hideMounts = true; - # directories = [ - # config.host.network_storage.export_directory - # ]; - # }; - # }) - ]); } diff --git a/modules/nixos-modules/server/network_storage/network_storage.nix b/modules/nixos-modules/server/network_storage/network_storage.nix new file mode 100644 index 0000000..b9d0446 --- /dev/null +++ b/modules/nixos-modules/server/network_storage/network_storage.nix @@ -0,0 +1,86 @@ +{ + config, + lib, + ... +}: let + export_directory = config.host.network_storage.export_directory; +in { + options = { + host.network_storage = { + enable = lib.mkEnableOption "is this machine going to export network storage"; + export_directory = lib.mkOption { + type = lib.types.path; + description = "what are exports going to be stored in"; + default = "/exports"; + }; + directories = lib.mkOption { + type = lib.types.listOf (lib.types.submodule ({config, ...}: { + options = { + folder = lib.mkOption { + type = lib.types.str; + description = "what is the name of this export directory"; + }; + bind = lib.mkOption { + type = lib.types.nullOr lib.types.path; + description = "is this directory bound to anywhere"; + default = null; + }; + user = lib.mkOption { + type = lib.types.str; + description = "what user owns this directory"; + default = "nouser"; + }; + group = lib.mkOption { + type = lib.types.str; + description = "what group owns this directory"; + default = "nogroup"; + }; + _directory = lib.mkOption { + internal = true; + readOnly = true; + type = lib.types.path; + default = "${export_directory}/${config.folder}"; + }; + }; + })); + description = "list of directory names to export"; + }; + }; + }; + + config = lib.mkIf config.host.network_storage.enable (lib.mkMerge [ + { + # create any folders that we need to have for our exports + systemd.tmpfiles.rules = + [ + "d ${config.host.network_storage.export_directory} 2775 nobody nogroup -" + ] + ++ ( + builtins.map ( + directory: "d ${directory._directory} 2770 ${directory.user} ${directory.group}" + ) + config.host.network_storage.directories + ); + + # set up any bind mounts that we need for our exports + fileSystems = builtins.listToAttrs ( + builtins.map (directory: + lib.attrsets.nameValuePair directory._directory { + device = directory.bind; + options = ["bind"]; + }) ( + builtins.filter (directory: directory.bind != null) config.host.network_storage.directories + ) + ); + } + # (lib.mkIf config.host.impermanence.enable { + # environment.persistence."/persist/replicate/system/root" = { + # enable = true; + # hideMounts = true; + # directories = [ + # config.host.network_storage.export_directory + # ]; + # }; + # }) + ]); +} diff --git a/modules/nixos-modules/server/network_storage/nfs.nix b/modules/nixos-modules/server/network_storage/nfs.nix index bad0452..297dc1a 100644 --- a/modules/nixos-modules/server/network_storage/nfs.nix +++ b/modules/nixos-modules/server/network_storage/nfs.nix @@ -61,8 +61,6 @@ # loopback "127.0.0.1" "::1" - # local network - # "192.168.0.0/24" # tailscale "100.64.0.0/10" "fd7a:115c:a1e0::/48" @@ -84,7 +82,7 @@ ); }; }; - networking.firewall.interfaces.${config.services.tailscale.interfaceName} = let + networking.firewall = let ports = [ 111 config.host.network_storage.nfs.port @@ -94,6 +92,12 @@ 20048 ]; in { + # Allow NFS on Tailscale interface + interfaces.${config.services.tailscale.interfaceName} = { + allowedTCPPorts = ports; + allowedUDPPorts = ports; + }; + # Allow NFS on local network (assuming default interface) allowedTCPPorts = ports; allowedUDPPorts = ports; }; diff --git a/modules/nixos-modules/server/panoramax/database.nix b/modules/nixos-modules/server/panoramax/database.nix new file mode 100644 index 0000000..1721726 --- /dev/null +++ b/modules/nixos-modules/server/panoramax/database.nix @@ -0,0 +1,48 @@ +{ + lib, + config, + ... +}: { + options.services.panoramax = { + database = { + postgres = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Use PostgreSQL instead of SQLite"; + }; + user = lib.mkOption { + type = lib.types.str; + default = "panoramax"; + description = "Database user name"; + }; + database = lib.mkOption { + type = lib.types.str; + default = "panoramax"; + description = "Database name"; + }; + }; + }; + }; + + config = lib.mkIf config.services.panoramax.enable { + assertions = [ + { + assertion = !config.services.panoramax.database.postgres.enable || config.services.postgresql.enable; + message = "PostgreSQL must be enabled when using postgres database for Panoramax"; + } + ]; + + services.postgresql.databases.panoramax = lib.mkIf config.services.panoramax.database.postgres.enable { + enable = true; + user = config.services.panoramax.database.postgres.user; + database = config.services.panoramax.database.postgres.database; + }; + + systemd.services.panoramax = lib.mkIf config.services.panoramax.database.postgres.enable { + requires = [ + config.systemd.services.postgresql.name + ]; + }; + }; +} diff --git a/modules/nixos-modules/server/panoramax/default.nix b/modules/nixos-modules/server/panoramax/default.nix new file mode 100644 index 0000000..f5a514f --- /dev/null +++ b/modules/nixos-modules/server/panoramax/default.nix @@ -0,0 +1,9 @@ +{...}: { + imports = [ + ./proxy.nix + ./fail2ban.nix + ./storage.nix + ./panoramax.nix + ./database.nix + ]; +} 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/panoramax.nix b/modules/nixos-modules/server/panoramax/panoramax.nix new file mode 100644 index 0000000..fd77db7 --- /dev/null +++ b/modules/nixos-modules/server/panoramax/panoramax.nix @@ -0,0 +1,359 @@ +{ + config, + lib, + pkgs, + ... +}: { + options.services = { + panoramax = { + enable = lib.mkEnableOption "panoramax"; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.panoramax; + description = "The panoramax package to use"; + }; + + user = lib.mkOption { + type = lib.types.str; + default = "panoramax"; + description = "The user panoramax should run as."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "panoramax"; + description = "The group panoramax should run as."; + }; + + host = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = "Host to bind the panoramax service to"; + }; + + port = lib.mkOption { + type = lib.types.nullOr lib.types.port; + default = 5000; + description = "Port for the panoramax service"; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to open the panoramax port in the firewall"; + }; + + settings = { + urlScheme = lib.mkOption { + type = lib.types.enum ["http" "https"]; + default = "https"; + description = "URL scheme for the application"; + }; + + storage = { + fsUrl = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "/var/lib/panoramax/storage"; + description = "File system URL for storage"; + }; + }; + + infrastructure = { + nbProxies = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = 1; + description = "Number of proxies in front of the application"; + }; + }; + + flask = { + secretKey = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Flask secret key for session security"; + }; + + sessionCookieDomain = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Flask session cookie domain"; + }; + }; + + api = { + pictures = { + licenseSpdxId = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "SPDX license identifier for API pictures"; + }; + + licenseUrl = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "License URL for API pictures"; + }; + }; + }; + + extraEnvironment = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; + description = "Additional environment variables"; + example = { + CUSTOM_SETTING = "value"; + DEBUG = "true"; + }; + }; + }; + + database = { + createDB = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to automatically create the database and user"; + }; + + name = lib.mkOption { + type = lib.types.str; + default = "panoramax"; + description = "The name of the panoramax database"; + }; + + host = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "/run/postgresql"; + description = "Hostname or address of the postgresql server. If an absolute path is given here, it will be interpreted as a unix socket path."; + }; + + port = lib.mkOption { + type = lib.types.nullOr lib.types.port; + default = 5432; + description = "Port of the postgresql server."; + }; + + user = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "panoramax"; + description = "The database user for panoramax."; + }; + + # TODO: password file for external database + }; + + sgblur = { + # TODO: configs to bind to sgblur + }; + }; + sgblur = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to enable sgblur integration for face and license plate blurring"; + }; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.sgblur; + description = "The sgblur package to use"; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 8080; + description = "Port for the sgblur service"; + }; + + host = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = "Host to bind the sgblur service to"; + }; + + url = lib.mkOption { + type = lib.types.str; + default = "http://127.0.0.1:8080"; + description = "URL where sgblur service is accessible"; + }; + }; + }; + + config = lib.mkIf config.services.panoramax.enable (lib.mkMerge [ + { + # Create panoramax user and group + users.users.${config.services.panoramax.user} = { + isSystemUser = true; + group = config.services.panoramax.group; + home = "/var/lib/panoramax"; + createHome = true; + }; + + users.groups.${config.services.panoramax.group} = {}; + + # Ensure storage directory exists with correct permissions + systemd.tmpfiles.rules = [ + "d '${config.services.panoramax.settings.storage.fsUrl}' 0755 ${config.services.panoramax.user} ${config.services.panoramax.group} - -" + ]; + + systemd.services.panoramax-api = { + description = "Panoramax API server (self hosted map street view)"; + after = ["network.target" "postgresql.service"]; + wantedBy = ["multi-user.target"]; + + environment = + { + # Core Flask configuration + FLASK_APP = "geovisio"; + + # Storage configuration + FS_URL = config.services.panoramax.settings.storage.fsUrl; + + # Infrastructure configuration + INFRA_NB_PROXIES = toString config.services.panoramax.settings.infrastructure.nbProxies; + + # Application configuration + PORT = toString config.services.panoramax.port; + + # Python path to include the panoramax package + PYTHONPATH = "${config.services.panoramax.package}/${pkgs.python3.sitePackages}"; + } + // ( + if config.services.panoramax.database.host == "/run/postgresql" + then { + DB_URL = "postgresql://${config.services.panoramax.database.user}@/${config.services.panoramax.database.name}?host=/run/postgresql"; + } + else { + DB_HOST = config.services.panoramax.database.host; + DB_PORT = toString config.services.panoramax.database.port; + DB_USERNAME = config.services.panoramax.database.user; + DB_NAME = config.services.panoramax.database.name; + } + ) + // (lib.optionalAttrs (config.services.panoramax.settings.flask.secretKey != null) { + FLASK_SECRET_KEY = config.services.panoramax.settings.flask.secretKey; + }) + // (lib.optionalAttrs (config.services.panoramax.settings.flask.sessionCookieDomain != null) { + FLASK_SESSION_COOKIE_DOMAIN = config.services.panoramax.settings.flask.sessionCookieDomain; + }) + // (lib.optionalAttrs (config.services.panoramax.settings.api.pictures.licenseSpdxId != null) { + API_PICTURES_LICENSE_SPDX_ID = config.services.panoramax.settings.api.pictures.licenseSpdxId; + }) + // (lib.optionalAttrs (config.services.panoramax.settings.api.pictures.licenseUrl != null) { + API_PICTURES_LICENSE_URL = config.services.panoramax.settings.api.pictures.licenseUrl; + }) + // (lib.optionalAttrs config.services.sgblur.enable { + SGBLUR_API_URL = config.services.sgblur.url; + }) + // config.services.panoramax.settings.extraEnvironment; + + path = with pkgs; [ + (python3.withPackages (ps: with ps; [config.services.panoramax.package waitress])) + ]; + + serviceConfig = { + ExecStart = "${pkgs.python3.withPackages (ps: with ps; [config.services.panoramax.package waitress])}/bin/waitress-serve --port ${toString config.services.panoramax.port} --call geovisio:create_app"; + User = config.services.panoramax.user; + Group = config.services.panoramax.group; + WorkingDirectory = "/var/lib/panoramax"; + Restart = "always"; + RestartSec = 5; + + # Security hardening + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + ReadWritePaths = [ + "/var/lib/panoramax" + config.services.panoramax.settings.storage.fsUrl + ]; + NoNewPrivileges = true; + PrivateDevices = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictSUIDSGID = true; + RestrictRealtime = true; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + SystemCallArchitectures = "native"; + }; + }; + + # Open firewall if requested + networking.firewall.allowedTCPPorts = lib.mkIf config.services.panoramax.openFirewall [ + config.services.panoramax.port + ]; + } + (lib.mkIf config.services.sgblur.enable { + # SGBlur service configuration + systemd.services.sgblur = { + description = "SGBlur face and license plate blurring service"; + after = ["network.target"]; + wantedBy = ["multi-user.target"]; + + path = with pkgs; [ + config.services.sgblur.package + python3 + python3Packages.waitress + ]; + + serviceConfig = { + ExecStart = "${pkgs.python3Packages.waitress}/bin/waitress-serve --host ${config.services.sgblur.host} --port ${toString config.services.sgblur.port} src.detect.detect_api:app"; + WorkingDirectory = "${config.services.sgblur.package}"; + Restart = "always"; + RestartSec = 5; + + # Basic security hardening + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + NoNewPrivileges = true; + PrivateDevices = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictSUIDSGID = true; + RestrictRealtime = true; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + SystemCallArchitectures = "native"; + }; + }; + + networking.firewall.allowedTCPPorts = lib.mkIf config.services.panoramax.openFirewall [ + config.services.sgblur.port + ]; + }) + (lib.mkIf config.services.panoramax.database.createDB { + services.postgresql = { + enable = true; + ensureDatabases = lib.mkIf config.services.panoramax.database.createDB [config.services.panoramax.database.name]; + ensureUsers = lib.mkIf config.services.panoramax.database.createDB [ + { + name = config.services.panoramax.database.user; + ensureDBOwnership = true; + ensureClauses.login = true; + } + ]; + extensions = ps: with ps; [postgis]; + }; + systemd.services.postgresql.serviceConfig.ExecStartPost = let + sqlFile = pkgs.writeText "panoramax-postgis-setup.sql" '' + CREATE EXTENSION IF NOT EXISTS postgis; + + -- TODO: how can we ensure that this runs after the databases have been created + -- ALTER DATABASE ${config.services.panoramax.database.name} SET TIMEZONE TO 'UTC'; + + GRANT SET ON PARAMETER session_replication_role TO ${config.services.panoramax.database.user}; + ''; + in [ + '' + ${lib.getExe' config.services.postgresql.package "psql"} -d "${config.services.panoramax.database.user}" -f "${sqlFile}" + '' + ]; + }) + ]); +} diff --git a/modules/nixos-modules/server/panoramax/proxy.nix b/modules/nixos-modules/server/panoramax/proxy.nix new file mode 100644 index 0000000..7cd7111 --- /dev/null +++ b/modules/nixos-modules/server/panoramax/proxy.nix @@ -0,0 +1,39 @@ +{ + lib, + config, + ... +}: { + options.services.panoramax = { + domain = lib.mkOption { + type = lib.types.str; + description = "domain that panoramax will be hosted at"; + default = "panoramax.arpa"; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for panoramax"; + default = []; + }; + reverseProxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.panoramax.enable && config.services.reverseProxy.enable; + }; + }; + }; + + config = lib.mkIf config.services.panoramax.reverseProxy.enable { + services.reverseProxy.services.panoramax = { + target = "http://localhost:${toString config.services.panoramax.port}"; + domain = config.services.panoramax.domain; + extraDomains = config.services.panoramax.extraDomains; + + settings = { + proxyWebsockets.enable = true; + forwardHeaders.enable = true; + maxBodySize = 100000; + timeout = 300; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/panoramax/storage.nix b/modules/nixos-modules/server/panoramax/storage.nix new file mode 100644 index 0000000..b36e087 --- /dev/null +++ b/modules/nixos-modules/server/panoramax/storage.nix @@ -0,0 +1,19 @@ +{ + lib, + config, + ... +}: { + options.services.panoramax.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.panoramax.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.panoramax.enable { + storage.datasets.replicate."system/root" = { + directories."/var/lib/panoramax" = lib.mkIf config.services.panoramax.impermanence.enable { + owner.name = "panoramax"; + group.name = "panoramax"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/paperless/database.nix b/modules/nixos-modules/server/paperless/database.nix new file mode 100644 index 0000000..c63e59d --- /dev/null +++ b/modules/nixos-modules/server/paperless/database.nix @@ -0,0 +1,30 @@ +{ + config, + lib, + ... +}: { + config = lib.mkIf config.services.paperless.enable { + assertions = [ + { + assertion = !config.services.paperless.database.createLocally || config.services.postgresql.enable; + message = "PostgreSQL must be enabled when using local postgres database for Paperless"; + } + { + assertion = !config.services.paperless.database.createLocally || (builtins.any (db: db == "paperless") config.services.postgresql.ensureDatabases); + message = "Paperless built-in database creation failed - expected 'paperless' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}"; + } + { + assertion = !config.services.paperless.database.createLocally || (builtins.any (user: user.name == "paperless") config.services.postgresql.ensureUsers); + message = "Paperless built-in user creation failed - expected user 'paperless' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}"; + } + ]; + + services.paperless.database.createLocally = lib.mkDefault true; + + systemd.services.paperless-scheduler = lib.mkIf config.services.paperless.database.createLocally { + requires = [ + config.systemd.services.postgresql.name + ]; + }; + }; +} diff --git a/modules/nixos-modules/server/paperless/default.nix b/modules/nixos-modules/server/paperless/default.nix new file mode 100644 index 0000000..f7a5aa7 --- /dev/null +++ b/modules/nixos-modules/server/paperless/default.nix @@ -0,0 +1,9 @@ +{ + imports = [ + ./paperless.nix + ./proxy.nix + ./database.nix + ./fail2ban.nix + ./storage.nix + ]; +} 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/paperless.nix b/modules/nixos-modules/server/paperless/paperless.nix new file mode 100644 index 0000000..5bcbfed --- /dev/null +++ b/modules/nixos-modules/server/paperless/paperless.nix @@ -0,0 +1,27 @@ +{ + config, + lib, + ... +}: { + options.services.paperless = { + database = { + user = lib.mkOption { + type = lib.types.str; + description = "what is the user and database that we are going to use for paperless"; + default = "paperless"; + }; + }; + }; + + config = lib.mkIf config.services.paperless.enable { + services.paperless = { + configureTika = true; + settings = { + PAPERLESS_DBENGINE = "postgresql"; + PAPERLESS_DBHOST = "/run/postgresql"; + PAPERLESS_DBNAME = config.services.paperless.database.user; + PAPERLESS_DBUSER = config.services.paperless.database.user; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/paperless/proxy.nix b/modules/nixos-modules/server/paperless/proxy.nix new file mode 100644 index 0000000..9d152c9 --- /dev/null +++ b/modules/nixos-modules/server/paperless/proxy.nix @@ -0,0 +1,33 @@ +{ + config, + lib, + ... +}: { + options.services.paperless = { + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for paperless"; + default = []; + }; + reverseProxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.paperless.enable && config.services.reverseProxy.enable; + }; + }; + }; + + config = lib.mkIf config.services.paperless.reverseProxy.enable { + services.reverseProxy.services.paperless = { + target = "http://${config.services.paperless.address}:${toString config.services.paperless.port}"; + domain = config.services.paperless.domain; + extraDomains = config.services.paperless.extraDomains; + + settings = { + proxyWebsockets.enable = true; + forwardHeaders.enable = true; + maxBodySize = 50000; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/paperless/storage.nix b/modules/nixos-modules/server/paperless/storage.nix new file mode 100644 index 0000000..6e17bc2 --- /dev/null +++ b/modules/nixos-modules/server/paperless/storage.nix @@ -0,0 +1,21 @@ +{ + config, + lib, + ... +}: let + dataDir = "/var/lib/paperless"; +in { + options.services.paperless.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.paperless.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.paperless.enable { + storage.datasets.replicate."system/root" = { + directories."${dataDir}" = lib.mkIf config.services.paperless.impermanence.enable { + owner.name = "paperless"; + group.name = "paperless"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/podman.nix b/modules/nixos-modules/server/podman.nix deleted file mode 100644 index e806e65..0000000 --- a/modules/nixos-modules/server/podman.nix +++ /dev/null @@ -1,73 +0,0 @@ -{ - lib, - config, - ... -}: { - options.host.podman = { - enable = lib.mkEnableOption "should home-assistant be enabled on this computer"; - macvlan = { - subnet = lib.mkOption { - type = lib.types.str; - description = "Subnet for macvlan address range"; - }; - gateway = lib.mkOption { - type = lib.types.str; - description = "Gateway for macvlan"; - # TODO: see if we can default this to systemd network gateway - }; - networkInterface = lib.mkOption { - type = lib.types.str; - description = "Parent network interface for macvlan"; - # TODO: see if we can default this some interface? - }; - }; - }; - config = lib.mkIf config.host.podman.enable { - systemd = { - services = { - # "podman-network-macvlan" = { - # path = [pkgs.podman]; - # serviceConfig = { - # Type = "oneshot"; - # RemainAfterExit = true; - # ExecStop = "podman network rm -f macvlan"; - # }; - # script = '' - # podman network inspect macvlan || podman network create --driver macvlan --subnet ${config.host.podman.macvlan.subnet} --gateway ${config.host.podman.macvlan.gateway} --opt parent=${config.host.podman.macvlan.networkInterface} macvlan - # ''; - # partOf = ["podman-compose-root.target"]; - # wantedBy = ["podman-compose-root.target"]; - # }; - }; - # disable computer sleeping - targets = { - # Root service - # When started, this will automatically create all resources and start - # the containers. When stopped, this will teardown all resources. - "podman-compose-root" = { - unitConfig = { - Description = "Root target for podman targets."; - }; - wantedBy = ["multi-user.target"]; - }; - }; - }; - - virtualisation = { - # Runtime - podman = { - enable = true; - autoPrune.enable = true; - dockerCompat = true; - # defaultNetwork.settings = { - # # Required for container networking to be able to use names. - # dns_enabled = true; - # }; - }; - - oci-containers = { - backend = "podman"; - }; - }; - }; -} diff --git a/modules/nixos-modules/server/postgres.nix b/modules/nixos-modules/server/postgres.nix deleted file mode 100644 index 71ce44c..0000000 --- a/modules/nixos-modules/server/postgres.nix +++ /dev/null @@ -1,121 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: let - dataDir = "/var/lib/postgresql/16"; - adminUsers = lib.lists.filter (user: user.isAdmin) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers); - clientUsers = lib.lists.filter (user: user.isClient) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers); - createUsers = lib.lists.filter (user: user.createUser) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers); - createDatabases = lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraDatabases; -in { - options = { - host.postgres = { - enable = lib.mkEnableOption "enable postgres"; - extraUsers = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { - options = { - name = lib.mkOption { - type = lib.types.str; - default = name; - }; - isAdmin = lib.mkOption { - type = lib.types.bool; - default = false; - }; - isClient = lib.mkOption { - type = lib.types.bool; - default = false; - }; - createUser = lib.mkOption { - type = lib.types.bool; - default = false; - }; - }; - })); - default = {}; - }; - extraDatabases = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { - options = { - name = lib.mkOption { - type = lib.types.str; - default = name; - }; - }; - })); - default = {}; - }; - }; - }; - - config = lib.mkIf config.host.postgres.enable (lib.mkMerge [ - { - services = { - postgresql = { - enable = true; - package = pkgs.postgresql_16; - ensureUsers = - [ - { - name = "postgres"; - } - ] - ++ ( - builtins.map (user: { - name = user.name; - ensureDBOwnership = true; - }) - createUsers - ); - ensureDatabases = builtins.map (database: database.name) createDatabases; - identMap = - '' - # ArbitraryMapName systemUser DBUser - - # Administration Users - superuser_map root postgres - superuser_map postgres postgres - '' - + ( - lib.strings.concatLines (builtins.map (user: "superuser_map ${user.name} postgres") adminUsers) - ) - + '' - - # Client Users - '' - + ( - lib.strings.concatLines (builtins.map (user: "user_map ${user.name} ${user.name}") clientUsers) - ); - # configuration here lets users access the db that matches their name and lets user postgres access everything - authentication = pkgs.lib.mkOverride 10 '' - # type database DBuser origin-address auth-method optional_ident_map - local all postgres peer map=superuser_map - local sameuser all peer map=user_map - ''; - }; - }; - } - - (lib.mkIf config.host.impermanence.enable { - assertions = [ - { - assertion = config.services.postgresql.dataDir == dataDir; - message = "postgres data directory does not match persistence"; - } - ]; - environment.persistence."/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = dataDir; - user = "postgres"; - group = "postgres"; - } - ]; - }; - }) - ]); -} diff --git a/modules/nixos-modules/server/postgres/default.nix b/modules/nixos-modules/server/postgres/default.nix new file mode 100644 index 0000000..50d90d4 --- /dev/null +++ b/modules/nixos-modules/server/postgres/default.nix @@ -0,0 +1,6 @@ +{...}: { + imports = [ + ./postgres.nix + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/server/postgres/postgres.nix b/modules/nixos-modules/server/postgres/postgres.nix new file mode 100644 index 0000000..af7d1b4 --- /dev/null +++ b/modules/nixos-modules/server/postgres/postgres.nix @@ -0,0 +1,122 @@ +{ + config, + lib, + pkgs, + ... +}: let + enabledDatabases = lib.filterAttrs (_: db: db.enable) config.services.postgresql.databases; + extraDatabasesList = config.services.postgresql.extraDatabases; + + serviceDatabaseUsers = lib.mapAttrsToList (_: db: { + name = db.user; + ensureDBOwnership = true; + }) (lib.filterAttrs (_: db: db.ensureUser) enabledDatabases); + + extraDatabaseUsers = + builtins.map (dbName: { + name = dbName; + ensureDBOwnership = true; + }) + extraDatabasesList; + + serviceDatabases = lib.mapAttrsToList (_: db: db.database) enabledDatabases; + extraDatabaseNames = extraDatabasesList; + + serviceUserMappings = lib.mapAttrsToList (_: db: "user_map ${db.user} ${db.user}") enabledDatabases; + extraUserMappings = builtins.map (dbName: "user_map ${dbName} ${dbName}") extraDatabasesList; + + builtinServiceMappings = let + forgejoMapping = lib.optional (config.services.forgejo.enable && config.services.forgejo.database.type == "postgres") "user_map forgejo forgejo"; + immichMapping = lib.optional (config.services.immich.enable && config.services.immich.database.enable) "user_map immich immich"; + paperlessMapping = lib.optional (config.services.paperless.enable && config.services.paperless.database.createLocally) "user_map paperless paperless"; + in + forgejoMapping ++ immichMapping ++ paperlessMapping; +in { + options = { + services.postgresql = { + databases = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { + options = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to create this database and user"; + }; + user = lib.mkOption { + type = lib.types.str; + default = name; + description = "Database user name"; + }; + database = lib.mkOption { + type = lib.types.str; + default = name; + description = "Database name"; + }; + ensureUser = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to ensure the user exists"; + }; + }; + })); + default = {}; + description = "Databases to create for services"; + }; + + extraDatabases = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = "Additional databases to create (user name will match database name)"; + example = ["custom_db" "test_db"]; + }; + + adminUsers = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = "System users who should have PostgreSQL superuser access"; + example = ["leyla" "admin"]; + }; + }; + }; + + config = lib.mkIf config.services.postgresql.enable { + services = { + postgresql = { + package = pkgs.postgresql_16; + + ensureUsers = + [ + {name = "postgres";} + ] + ++ serviceDatabaseUsers ++ extraDatabaseUsers; + + ensureDatabases = serviceDatabases ++ extraDatabaseNames; + + identMap = + '' + # ArbitraryMapName systemUser DBUser + + # Administration Users + superuser_map root postgres + superuser_map postgres postgres + '' + + ( + lib.strings.concatLines (builtins.map (user: "superuser_map ${user} postgres") config.services.postgresql.adminUsers) + ) + + '' + + # Client Users + '' + + ( + lib.strings.concatLines (serviceUserMappings ++ extraUserMappings ++ builtinServiceMappings) + ); + + authentication = pkgs.lib.mkOverride 10 '' + # type database DBuser origin-address auth-method optional_ident_map + local all postgres peer map=superuser_map + local sameuser all peer map=user_map + ''; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/postgres/storage.nix b/modules/nixos-modules/server/postgres/storage.nix new file mode 100644 index 0000000..58a84a6 --- /dev/null +++ b/modules/nixos-modules/server/postgres/storage.nix @@ -0,0 +1,21 @@ +{ + config, + lib, + ... +}: let + dataDir = "/var/lib/postgresql/16"; +in { + options.services.postgresql.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.postgresql.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.postgresql.enable { + storage.datasets.replicate."system/root" = { + directories."${dataDir}" = lib.mkIf config.services.postgresql.impermanence.enable { + owner.name = "postgres"; + group.name = "postgres"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/qbittorent.nix b/modules/nixos-modules/server/qbittorent.nix deleted file mode 100644 index 9b7b7e8..0000000 --- a/modules/nixos-modules/server/qbittorent.nix +++ /dev/null @@ -1,160 +0,0 @@ -{ - lib, - pkgs, - config, - ... -}: let - qbittorent_data_directory = "/var/lib/qbittorrent"; -in { - options.services.qbittorrent = { - enable = lib.mkEnableOption "should the headless qbittorrent service be enabled"; - - dataDir = lib.mkOption { - type = lib.types.path; - default = "/var/lib/qbittorrent"; - description = lib.mdDoc '' - The directory where qBittorrent stores its data files. - ''; - }; - - mediaDir = lib.mkOption { - type = lib.types.path; - description = lib.mdDoc '' - The directory to create to store qbittorrent media. - ''; - }; - - user = lib.mkOption { - type = lib.types.str; - default = "qbittorrent"; - description = lib.mdDoc '' - User account under which qBittorrent runs. - ''; - }; - - group = lib.mkOption { - type = lib.types.str; - default = "qbittorrent"; - description = lib.mdDoc '' - Group under which qBittorrent runs. - ''; - }; - - webPort = lib.mkOption { - type = lib.types.port; - default = 8080; - description = lib.mdDoc '' - qBittorrent web UI port. - ''; - }; - - openFirewall = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Open services.qBittorrent.webPort to the outside network."; - }; - - package = lib.mkOption { - type = lib.types.package; - default = pkgs.qbittorrent-nox; - defaultText = lib.literalExpression "pkgs.qbittorrent-nox"; - description = "The qbittorrent package to use."; - }; - }; - - config = lib.mkIf config.services.qbittorrent.enable (lib.mkMerge [ - { - networking.firewall = lib.mkIf config.services.qbittorrent.openFirewall { - allowedTCPPorts = [config.services.qbittorrent.webPort]; - }; - - systemd.services.qbittorrent = { - # based on the plex.nix service module and - # https://github.com/qbittorrent/qBittorrent/blob/master/dist/unix/systemd/qbittorrent-nox%40.service.in - description = "qBittorrent-nox service"; - documentation = ["man:qbittorrent-nox(1)"]; - after = ["network.target"]; - wantedBy = ["multi-user.target"]; - - serviceConfig = { - Type = "simple"; - User = config.services.qbittorrent.user; - Group = config.services.qbittorrent.group; - - # Run the pre-start script with full permissions (the "!" prefix) so it - # can create the data directory if necessary. - ExecStartPre = let - preStartScript = pkgs.writeScript "qbittorrent-run-prestart" '' - #!${pkgs.bash}/bin/bash - - # Create data directory if it doesn't exist - if ! test -d "$QBT_PROFILE"; then - echo "Creating initial qBittorrent data directory in: $QBT_PROFILE" - install -d -m 0755 -o "${config.services.qbittorrent.user}" -g "${config.services.qbittorrent.group}" "$QBT_PROFILE" - fi - ''; - in "!${preStartScript}"; - - #ExecStart = "${pkgs.qbittorrent-nox}/bin/qbittorrent-nox"; - ExecStart = "${config.services.qbittorrent.package}/bin/qbittorrent-nox"; - # To prevent "Quit & shutdown daemon" from working; we want systemd to - # manage it! - #Restart = "on-success"; - #UMask = "0002"; - #LimitNOFILE = cfg.openFilesLimit; - }; - - environment = { - QBT_PROFILE = config.services.qbittorrent.dataDir; - QBT_WEBUI_PORT = toString config.services.qbittorrent.webPort; - }; - }; - } - (lib.mkIf config.host.impermanence.enable { - fileSystems."/persist/system/qbittorrent".neededForBoot = true; - - host.storage.pool.extraDatasets = { - # sops age key needs to be available to pre persist for user generation - "persist/system/qbittorrent" = { - type = "zfs_fs"; - mountpoint = "/persist/system/qbittorrent"; - options = { - canmount = "on"; - }; - }; - }; - - assertions = [ - { - assertion = config.services.qbittorrent.dataDir == qbittorent_data_directory; - message = "qbittorrent data directory does not match persistence"; - } - ]; - - environment.persistence = { - "/persist/system/root" = { - directories = [ - { - directory = qbittorent_data_directory; - user = "qbittorrent"; - group = "qbittorrent"; - } - ]; - }; - - "/persist/system/qbittorrent" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = config.services.qbittorrent.mediaDir; - user = "qbittorrent"; - group = "qbittorrent"; - mode = "1775"; - } - ]; - }; - }; - }) - ]); -} diff --git a/modules/nixos-modules/server/qbittorent/default.nix b/modules/nixos-modules/server/qbittorent/default.nix new file mode 100644 index 0000000..11cc449 --- /dev/null +++ b/modules/nixos-modules/server/qbittorent/default.nix @@ -0,0 +1,6 @@ +{...}: { + imports = [ + ./qbittorent.nix + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/server/qbittorent/qbittorent.nix b/modules/nixos-modules/server/qbittorent/qbittorent.nix new file mode 100644 index 0000000..44603c8 --- /dev/null +++ b/modules/nixos-modules/server/qbittorent/qbittorent.nix @@ -0,0 +1,18 @@ +{ + lib, + config, + ... +}: { + options.services.qbittorrent = { + mediaDir = lib.mkOption { + type = lib.types.path; + description = lib.mdDoc '' + The directory to create to store qbittorrent media. + ''; + }; + }; + + config = lib.mkIf config.services.qbittorrent.enable { + # Main qbittorrent configuration goes here if needed + }; +} diff --git a/modules/nixos-modules/server/qbittorent/storage.nix b/modules/nixos-modules/server/qbittorent/storage.nix new file mode 100644 index 0000000..da82bcc --- /dev/null +++ b/modules/nixos-modules/server/qbittorent/storage.nix @@ -0,0 +1,46 @@ +{ + lib, + config, + ... +}: let + qbittorent_profile_directory = "/var/lib/qBittorrent/"; +in { + options.services.qbittorrent.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.qbittorrent.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.qbittorrent.enable { + storage.datasets.replicate = { + "system/root" = { + directories."${qbittorent_profile_directory}" = lib.mkIf config.services.qbittorrent.impermanence.enable { + owner.name = "qbittorrent"; + group.name = "qbittorrent"; + }; + }; + "system/media" = { + mount = "/persist/replicate/system/media"; + + directories."${config.services.qbittorrent.mediaDir}" = lib.mkIf config.services.qbittorrent.impermanence.enable { + owner.name = "qbittorrent"; + group.name = "qbittorrent"; + owner.permissions = { + read = true; + write = true; + execute = true; + }; + group.permissions = { + read = true; + write = true; + execute = true; + }; + other.permissions = { + read = true; + write = false; + execute = true; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/radarr/default.nix b/modules/nixos-modules/server/radarr/default.nix new file mode 100644 index 0000000..cb2a5f0 --- /dev/null +++ b/modules/nixos-modules/server/radarr/default.nix @@ -0,0 +1,5 @@ +{...}: { + imports = [ + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/server/radarr/storage.nix b/modules/nixos-modules/server/radarr/storage.nix new file mode 100644 index 0000000..8f991c0 --- /dev/null +++ b/modules/nixos-modules/server/radarr/storage.nix @@ -0,0 +1,21 @@ +{ + lib, + config, + ... +}: let + radarr_data_directory = "/var/lib/radarr/.config/Radarr"; +in { + options.services.radarr.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.radarr.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.radarr.enable { + storage.datasets.replicate."system/root" = { + directories."${radarr_data_directory}" = lib.mkIf config.services.radarr.impermanence.enable { + owner.name = "radarr"; + group.name = "radarr"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/reverseProxy/default.nix b/modules/nixos-modules/server/reverseProxy/default.nix new file mode 100644 index 0000000..336e28b --- /dev/null +++ b/modules/nixos-modules/server/reverseProxy/default.nix @@ -0,0 +1,6 @@ +{...}: { + imports = [ + ./reverseProxy.nix + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/server/reverseProxy/reverseProxy.nix b/modules/nixos-modules/server/reverseProxy/reverseProxy.nix new file mode 100644 index 0000000..eecc9bf --- /dev/null +++ b/modules/nixos-modules/server/reverseProxy/reverseProxy.nix @@ -0,0 +1,176 @@ +{ + lib, + config, + ... +}: { + options.services.reverseProxy = { + enable = lib.mkEnableOption "turn on the reverse proxy"; + openFirewall = lib.mkEnableOption "open the firewall"; + refuseUnmatchedDomains = lib.mkOption { + type = lib.types.bool; + description = "refuse connections for domains that don't match any configured virtual hosts"; + default = true; + }; + ports = { + http = lib.mkOption { + type = lib.types.port; + description = "HTTP port for the reverse proxy"; + default = 80; + }; + https = lib.mkOption { + type = lib.types.port; + description = "HTTPS port for the reverse proxy"; + default = 443; + }; + }; + acme = { + enable = lib.mkOption { + type = lib.types.bool; + description = "enable ACME certificate management"; + default = true; + }; + email = lib.mkOption { + type = lib.types.str; + description = "email address for ACME certificate registration"; + }; + }; + services = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { + options = { + target = lib.mkOption { + type = lib.types.str; + description = "what url will all traffic to this application be forwarded to"; + }; + domain = lib.mkOption { + type = lib.types.str; + description = "what is the default subdomain to be used for this application to be used for"; + default = name; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for this domain"; + default = []; + }; + settings = { + certificateRenewal.enable = lib.mkOption { + type = lib.types.bool; + description = "auto renew certificates"; + default = true; + }; + forceSSL.enable = lib.mkOption { + type = lib.types.bool; + description = "auto renew certificates"; + default = true; + }; + proxyHeaders = { + enable = lib.mkEnableOption "should we proxy headers"; + timeout = lib.mkOption { + type = lib.types.int; + default = 60; + }; + }; + proxyWebsockets.enable = lib.mkEnableOption "should the default config proxy websockets"; + forwardHeaders.enable = lib.mkEnableOption "should the default config contain forward headers"; + noSniff.enable = lib.mkEnableOption "should the no sniff flags be set"; + proxyBuffering.enable = lib.mkOption { + type = lib.types.bool; + description = "should proxy buffering be enabled"; + default = true; + }; + maxBodySize = lib.mkOption { + type = lib.types.nullOr lib.types.int; + description = ""; + default = null; + }; + }; + }; + })); + }; + }; + + config = let + httpPort = config.services.reverseProxy.ports.http; + httpsPort = config.services.reverseProxy.ports.https; + in + lib.mkIf config.services.reverseProxy.enable { + security.acme = lib.mkIf config.services.reverseProxy.acme.enable { + acceptTerms = true; + defaults.email = config.services.reverseProxy.acme.email; + }; + + services.nginx = { + enable = true; + virtualHosts = lib.mkMerge ( + (lib.optionals config.services.reverseProxy.refuseUnmatchedDomains [ + { + "_" = { + default = true; + serverName = "_"; + locations."/" = { + extraConfig = '' + return 444; + ''; + }; + }; + } + ]) + ++ lib.lists.flatten ( + lib.attrsets.mapAttrsToList ( + name: service: let + hostConfig = { + forceSSL = service.settings.forceSSL.enable; + enableACME = service.settings.certificateRenewal.enable; + locations = { + "/" = { + proxyPass = service.target; + proxyWebsockets = service.settings.proxyWebsockets.enable; + recommendedProxySettings = service.settings.forwardHeaders.enable; + extraConfig = let + # Client upload size configuration + maxBodySizeConfig = + lib.optionalString (service.settings.maxBodySize != null) + "client_max_body_size ${toString service.settings.maxBodySize}M;"; + + # Security header configuration + noSniffConfig = + lib.optionalString service.settings.noSniff.enable + "add_header X-Content-Type-Options nosniff;"; + + # Proxy buffering configuration + proxyBufferingConfig = + lib.optionalString (!service.settings.proxyBuffering.enable) + "proxy_buffering off;"; + + # Proxy timeout configuration + proxyTimeoutConfig = + lib.optionalString service.settings.proxyHeaders.enable + '' + proxy_read_timeout ${toString service.settings.proxyHeaders.timeout}s; + proxy_connect_timeout ${toString service.settings.proxyHeaders.timeout}s; + proxy_send_timeout ${toString service.settings.proxyHeaders.timeout}s; + ''; + in + maxBodySizeConfig + noSniffConfig + proxyBufferingConfig + proxyTimeoutConfig; + }; + }; + }; + in ( + [ + { + ${service.domain} = hostConfig; + } + ] + ++ builtins.map (domain: {${domain} = hostConfig;}) + service.extraDomains + ) + ) + config.services.reverseProxy.services + ) + ); + }; + networking.firewall.allowedTCPPorts = lib.mkIf config.services.reverseProxy.openFirewall [ + httpPort + httpsPort + ]; + }; +} diff --git a/modules/nixos-modules/server/reverseProxy/storage.nix b/modules/nixos-modules/server/reverseProxy/storage.nix new file mode 100644 index 0000000..62b5451 --- /dev/null +++ b/modules/nixos-modules/server/reverseProxy/storage.nix @@ -0,0 +1,21 @@ +{ + lib, + config, + ... +}: let + dataDir = "/var/lib/acme"; +in { + options.services.reverseProxy.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.reverseProxy.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.reverseProxy.enable { + storage.datasets.replicate."system/root" = { + directories."${dataDir}" = lib.mkIf config.services.reverseProxy.impermanence.enable { + owner.name = "acme"; + group.name = "acme"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/reverse_proxy.nix b/modules/nixos-modules/server/reverse_proxy.nix deleted file mode 100644 index 26b4374..0000000 --- a/modules/nixos-modules/server/reverse_proxy.nix +++ /dev/null @@ -1,128 +0,0 @@ -{ - lib, - config, - ... -}: let - dataDir = "/var/lib/acme"; - httpPort = 80; - httpsPort = 443; -in { - options.host.reverse_proxy = { - enable = lib.mkEnableOption "turn on the reverse proxy"; - hostname = lib.mkOption { - type = lib.types.str; - description = "what host name are we going to be proxying from"; - }; - forceSSL = lib.mkOption { - type = lib.types.bool; - description = "force connections to use https"; - default = config.host.reverse_proxy.enableACME; - }; - enableACME = lib.mkOption { - type = lib.types.bool; - description = "auto renew certificates"; - default = true; - }; - subdomains = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { - options = { - subdomain = lib.mkOption { - type = lib.types.str; - description = "what is the default subdomain to be used for this application to be used for"; - default = name; - }; - extraSubdomains = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = "extra domains that should be configured for this domain"; - default = []; - }; - - target = lib.mkOption { - type = lib.types.str; - description = "what url will all traffic to this application be forwarded to"; - }; - - websockets.enable = lib.mkEnableOption "should the default config proxy websockets"; - - forwardHeaders.enable = lib.mkEnableOption "should the default config contain forward headers"; - - extraConfig = lib.mkOption { - type = lib.types.lines; - default = ""; - description = '' - These lines go to the end of the upstream verbatim. - ''; - }; - }; - })); - }; - }; - - config = lib.mkIf config.host.reverse_proxy.enable (lib.mkMerge [ - { - security.acme = lib.mkIf config.host.reverse_proxy.enableACME { - acceptTerms = true; - defaults.email = "jan-leila@protonmail.com"; - }; - - services.nginx = { - enable = true; - virtualHosts = lib.mkMerge ( - lib.lists.flatten ( - lib.attrsets.mapAttrsToList ( - name: value: let - hostConfig = { - forceSSL = config.host.reverse_proxy.forceSSL; - enableACME = config.host.reverse_proxy.enableACME; - locations = { - "/" = { - proxyPass = value.target; - proxyWebsockets = value.websockets.enable; - recommendedProxySettings = value.forwardHeaders.enable; - extraConfig = - value.extraConfig; - }; - }; - }; - in ( - [ - { - ${"${value.subdomain}.${config.host.reverse_proxy.hostname}"} = hostConfig; - } - ] - ++ builtins.map (subdomain: {${"${subdomain}.${config.host.reverse_proxy.hostname}"} = hostConfig;}) - value.extraSubdomains - ) - ) - config.host.reverse_proxy.subdomains - ) - ); - }; - - networking.firewall.allowedTCPPorts = [ - httpPort - httpsPort - ]; - } - (lib.mkIf config.host.impermanence.enable { - # TODO: figure out how to write an assertion for this - # assertions = [ - # { - # assertion = security.acme.certs..directory == dataDir; - # message = "postgres data directory does not match persistence"; - # } - # ]; - environment.persistence."/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = dataDir; - user = "acme"; - group = "acme"; - } - ]; - }; - }) - ]); -} diff --git a/modules/nixos-modules/server/searx/default.nix b/modules/nixos-modules/server/searx/default.nix new file mode 100644 index 0000000..5426380 --- /dev/null +++ b/modules/nixos-modules/server/searx/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./searx.nix + ./proxy.nix + ]; +} diff --git a/modules/nixos-modules/server/searx/proxy.nix b/modules/nixos-modules/server/searx/proxy.nix new file mode 100644 index 0000000..e994e4a --- /dev/null +++ b/modules/nixos-modules/server/searx/proxy.nix @@ -0,0 +1,31 @@ +{ + config, + lib, + ... +}: { + options.services.searx = { + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "extra domains that should be configured for searx"; + default = []; + }; + reverseProxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.services.searx.enable && config.services.reverseProxy.enable; + }; + }; + }; + + config = lib.mkIf config.services.searx.reverseProxy.enable { + services.reverseProxy.services.searx = { + target = "http://localhost:${toString config.services.searx.settings.server.port}"; + domain = config.services.searx.domain; + extraDomains = config.services.searx.extraDomains; + + settings = { + forwardHeaders.enable = true; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/searx.nix b/modules/nixos-modules/server/searx/searx.nix similarity index 74% rename from modules/nixos-modules/server/searx.nix rename to modules/nixos-modules/server/searx/searx.nix index d357308..d4d4012 100644 --- a/modules/nixos-modules/server/searx.nix +++ b/modules/nixos-modules/server/searx/searx.nix @@ -4,26 +4,13 @@ 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 { 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; diff --git a/modules/nixos-modules/server/sonarr/default.nix b/modules/nixos-modules/server/sonarr/default.nix new file mode 100644 index 0000000..cb2a5f0 --- /dev/null +++ b/modules/nixos-modules/server/sonarr/default.nix @@ -0,0 +1,5 @@ +{...}: { + imports = [ + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/server/sonarr/storage.nix b/modules/nixos-modules/server/sonarr/storage.nix new file mode 100644 index 0000000..8587751 --- /dev/null +++ b/modules/nixos-modules/server/sonarr/storage.nix @@ -0,0 +1,21 @@ +{ + lib, + config, + ... +}: let + sonarr_data_directory = "/var/lib/sonarr/.config/NzbDrone"; +in { + options.services.sonarr.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.sonarr.enable && config.storage.impermanence.enable; + }; + + config = lib.mkIf config.services.sonarr.enable { + storage.datasets.replicate."system/root" = { + directories."${sonarr_data_directory}" = lib.mkIf config.services.sonarr.impermanence.enable { + owner.name = "sonarr"; + group.name = "sonarr"; + }; + }; + }; +} diff --git a/modules/nixos-modules/server/virt-home-assistant.nix b/modules/nixos-modules/server/virt-home-assistant.nix deleted file mode 100644 index 4212668..0000000 --- a/modules/nixos-modules/server/virt-home-assistant.nix +++ /dev/null @@ -1,155 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: { - options.services.virt-home-assistant = { - enable = lib.mkEnableOption "Wether to enable home assistant virtual machine"; - networkBridge = lib.mkOption { - type = lib.types.str; - description = "what network bridge should we attach to the image"; - }; - hostDevice = lib.mkOption { - type = lib.types.str; - description = "what host devices should be attached to the image"; - }; - initialVersion = lib.mkOption { - type = lib.types.str; - description = "what home assistant image version should we pull for initial instal"; - default = "15.0"; - }; - imageName = lib.mkOption { - type = lib.types.str; - description = "where should the image be installed to"; - default = "home-assistant.qcow2"; - }; - installLocation = lib.mkOption { - type = lib.types.str; - description = "where should the image be installed to"; - default = "/etc/hass"; - }; - virtualMachineName = lib.mkOption { - type = lib.types.str; - description = "what name should we give the virtual machine"; - default = "home-assistant"; - }; - subdomain = lib.mkOption { - type = lib.types.str; - description = "subdomain of base domain that home-assistant will be hosted at"; - default = "home-assistant"; - }; - }; - config = lib.mkIf config.services.virt-home-assistant.enable (lib.mkMerge [ - { - # environment.systemPackages = with pkgs; [ - # virt-manager - # ]; - - # TODO: move this to external module and just have an assertion here that its enabled - # enable virtualization on the system - virtualisation = { - libvirtd = { - enable = true; - qemu.ovmf.enable = true; - }; - }; - - # TODO: deactivation script? - # create service to install and start the container - systemd.services.virt-install-home-assistant = let - # TODO: all of these need to be escaped to be used in commands reliably - bridgedNetwork = config.services.virt-home-assistant.networkBridge; - hostDevice = config.services.virt-home-assistant.hostDevice; - virtualMachineName = config.services.virt-home-assistant.virtualMachineName; - imageName = config.services.virt-home-assistant.imageName; - installLocation = config.services.virt-home-assistant.installLocation; - installImage = "${installLocation}/${imageName}"; - initialVersion = config.services.virt-home-assistant.initialVersion; - - home-assistant-qcow2 = pkgs.fetchurl { - name = "home-assistant.qcow2"; - url = "https://github.com/home-assistant/operating-system/releases/download/${initialVersion}/haos_ova-${initialVersion}.qcow2.xz"; - hash = "sha256-V1BEjvvLNbMMKJVyMCmipjQ/3owoJteeVxoF9LDHo1U="; - postFetch = '' - cp $out src.xz - rm -r $out - ${pkgs.xz}/bin/unxz src.xz --stdout > $out/${imageName} - ''; - }; - - # Write a script to install the Home Assistant OS qcow2 image - virtInstallScript = pkgs.writeShellScriptBin "virt-install-hass" '' - # Copy the initial image out of the package store to the install location if we don't have one yet - if [ ! -f ${installImage} ]; then - cp ${home-assistant-qcow2} ${installLocation} - fi - - # Check if VM already exists, and other pre-conditions - if ! ${pkgs.libvirt}/bin/virsh list --all | grep -q ${virtualMachineName}; then - ${pkgs.virt-manager}/bin/virt-install --name ${virtualMachineName} \ - --description "Home Assistant OS" \ - --os-variant=generic \ - --boot uefi \ - --ram=2048 \ - --vcpus=2 \ - --import \ - --disk ${installImage},bus=sata \ - --network bridge=${bridgedNetwork} \ - --host-device ${hostDevice} \ - --graphics none - ${pkgs.libvirt}/bin/virsh autostart ${virtualMachineName} - fi - ''; - in { - description = "Install and start Home Assistant"; - wantedBy = ["multi-user.target"]; - after = ["local-fs.target"]; - requires = ["libvirtd.service"]; - serviceConfig.Type = "oneshot"; - serviceConfig = { - ExecStart = "${virtInstallScript}/bin/virt-install-hass"; - }; - }; - - # TODO: figure out what we need to proxy to the virtual image - # host = { - # reverse_proxy.subdomains.${config.services.virt-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 { - # TODO: figure out how to write a config for this, prob based on nginx proxy logs? - }) - (lib.mkIf config.host.impermanence.enable { - # assertions = [ - # { - # assertion = config.services.virt-home-assistant.installLocation == configDir; - # message = "home assistant install location does not match persistence"; - # } - # ]; - environment.persistence."/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = config.services.virt-home-assistant.installLocation; - } - ]; - }; - }) - ]); -} diff --git a/modules/nixos-modules/server/wyoming.nix b/modules/nixos-modules/server/wyoming.nix new file mode 100644 index 0000000..1df6877 --- /dev/null +++ b/modules/nixos-modules/server/wyoming.nix @@ -0,0 +1,63 @@ +{ + lib, + config, + ... +}: { + options.services.wyoming.enable = lib.mkEnableOption "should wyoming be enabled on this device"; + config = lib.mkIf config.services.wyoming.enable (lib.mkMerge [ + { + services.wyoming = { + # Text to speech + piper = { + servers = { + "en" = { + enable = true; + # see https://github.com/rhasspy/rhasspy3/blob/master/programs/tts/piper/script/download.py + voice = "en-us-amy-low"; + uri = "tcp://0.0.0.0:10200"; + speaker = 0; + }; + }; + }; + + # Speech to text + faster-whisper = { + servers = { + "en" = { + enable = true; + # see https://github.com/rhasspy/rhasspy3/blob/master/programs/asr/faster-whisper/script/download.py + model = "tiny-int8"; + language = "en"; + uri = "tcp://0.0.0.0:10300"; + device = "cpu"; + }; + }; + }; + + openwakeword = { + enable = true; + uri = "tcp://0.0.0.0:10400"; + # preloadModels = [ + # "ok_nabu" + # ]; + # TODO: custom models + }; + }; + + # needs access to /proc/cpuinfo + systemd.services."wyoming-faster-whisper-en".serviceConfig.ProcSubset = lib.mkForce "all"; + } + (lib.mkIf config.host.impermanence.enable { + environment.persistence."/persist/replicate/system/root" = { + enable = true; + hideMounts = true; + directories = [ + { + directory = "/var/lib/private/wyoming"; + mode = "0700"; + } + ]; + }; + }) + ]); +} diff --git a/modules/nixos-modules/ssh.nix b/modules/nixos-modules/ssh.nix index 6f5fac1..6fe8e5c 100644 --- a/modules/nixos-modules/ssh.nix +++ b/modules/nixos-modules/ssh.nix @@ -3,26 +3,42 @@ config, ... }: { - config = lib.mkMerge [ - { - services = { - openssh = { - enable = true; - ports = [22]; - settings = { - PasswordAuthentication = false; - UseDns = true; - X11Forwarding = false; - }; + options = { + services.openssh.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.openssh.enable && config.storage.impermanence.enable; + }; + }; + + config = { + services = { + openssh = { + enable = true; + ports = [22]; + settings = { + PasswordAuthentication = false; + UseDns = true; + X11Forwarding = false; }; }; - } - (lib.mkIf config.host.impermanence.enable { - environment.persistence."/persist/system/root" = { - files = lib.lists.flatten ( - builtins.map (hostKey: [hostKey.path "${hostKey.path}.pub"]) config.services.openssh.hostKeys - ); - }; - }) - ]; + }; + + storage.datasets.replicate."system/root" = { + files = lib.mkIf config.services.openssh.impermanence.enable (builtins.listToAttrs ( + lib.lists.flatten ( + builtins.map (hostKey: [ + { + name = hostKey.path; + value = {enable = true;}; + } + { + name = "${hostKey.path}.pub"; + value = {enable = true;}; + } + ]) + config.services.openssh.hostKeys + ) + )); + }; + }; } diff --git a/modules/nixos-modules/steam.nix b/modules/nixos-modules/steam.nix new file mode 100644 index 0000000..20c0978 --- /dev/null +++ b/modules/nixos-modules/steam.nix @@ -0,0 +1,9 @@ +{...}: { + programs = { + steam = { + remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play + dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server + localNetworkGameTransfers.openFirewall = true; # Open ports in the firewall for Steam Local Network Game Transfers + }; + }; +} diff --git a/modules/nixos-modules/storage/default.nix b/modules/nixos-modules/storage/default.nix new file mode 100644 index 0000000..ebf990a --- /dev/null +++ b/modules/nixos-modules/storage/default.nix @@ -0,0 +1,13 @@ +{...}: { + # TODO: we should have an impermanence module for home manager that proxies its values namespaced to the user down here that matches the same interface + + # TODO: we should have a way of enabling impermanence for a systemd config + # these should have an option to put their folder into their own dataset (this needs to support private vs non private) + # options for features that can be added to the dataset + + imports = [ + ./impermanence.nix + ./zfs.nix + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/storage/impermanence.nix b/modules/nixos-modules/storage/impermanence.nix new file mode 100644 index 0000000..4fdf803 --- /dev/null +++ b/modules/nixos-modules/storage/impermanence.nix @@ -0,0 +1,142 @@ +args @ { + lib, + config, + ... +}: let + datasetSubmodules = (import ./submodules/dataset.nix) args; + impermanenceDatasetSubmodule = (import ./submodules/impermanenceDataset.nix) args; + + permissionsToMode = permissions: let + permSetToDigit = permSet: + ( + if permSet.read + then 4 + else 0 + ) + + ( + if permSet.write + then 2 + else 0 + ) + + ( + if permSet.execute + then 1 + else 0 + ); + + ownerDigit = permSetToDigit permissions.owner.permissions; + groupDigit = permSetToDigit permissions.group.permissions; + otherDigit = permSetToDigit permissions.other.permissions; + in + toString ownerDigit + toString groupDigit + toString otherDigit; + + # Get the option names from both submodules to automatically determine which are impermanence-specific + regularDatasetEval = lib.evalModules { + modules = [datasetSubmodules]; + specialArgs = args; + }; + impermanenceDatasetEval = lib.evalModules { + modules = [impermanenceDatasetSubmodule]; + specialArgs = args; + }; + + regularDatasetOptions = builtins.attrNames regularDatasetEval.options; + impermanenceDatasetOptions = builtins.attrNames impermanenceDatasetEval.options; + + # Find options that are only in impermanence datasets (not in regular ZFS datasets) + impermanenceOnlyOptions = lib.lists.subtractLists regularDatasetOptions impermanenceDatasetOptions; +in { + options.storage = { + impermanence = { + enable = lib.mkEnableOption "should impermanence be enabled for this system"; + + datasets = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodule); + default = {}; + }; + }; + }; + + config = lib.mkIf config.storage.impermanence.enable (lib.mkMerge [ + { + assertions = [ + { + assertion = config.storage.zfs.enable; + message = "storage.impermanence can not be used without storage.zfs."; + } + ]; + + system.activationScripts = { + # fixes issues with /var/lib/private not having the correct permissions https://github.com/nix-community/impermanence/issues/254 + "createPersistentStorageDirs".deps = ["var-lib-private-permissions" "users" "groups"]; + + "var-lib-private-permissions" = lib.mkIf config.storage.generateBase { + deps = ["specialfs"]; + text = '' + mkdir -p /persist/replicate/system/root/var/lib/private + chmod 0700 /persist/replicate/system/root/var/lib/private + ''; + }; + }; + + programs.fuse.userAllowOther = true; + + # Suppress sudo lecture on every boot since impermanence wipes the lecture status file + security.sudo.extraConfig = "Defaults lecture=never"; + + fileSystems = + lib.mapAttrs' ( + datasetName: dataset: + lib.nameValuePair "/${datasetName}" { + device = "rpool/${datasetName}"; + fsType = "zfs"; + neededForBoot = true; + } + ) + (lib.filterAttrs ( + datasetName: dataset: dataset.impermanence.enable + ) + config.storage.impermanence.datasets); + + environment.persistence = + lib.mapAttrs (datasetName: dataset: { + enable = true; + hideMounts = true; + persistentStoragePath = "/${datasetName}"; + directories = lib.mapAttrsToList (path: dirConfig: { + directory = path; + user = dirConfig.owner.name; + group = dirConfig.group.name; + mode = permissionsToMode dirConfig; + }) (lib.filterAttrs (_: dirConfig: dirConfig.enable) dataset.directories); + files = lib.mapAttrsToList (path: fileConfig: { + file = path; + parentDirectory = { + user = fileConfig.owner.name; + group = fileConfig.group.name; + mode = permissionsToMode fileConfig; + }; + }) (lib.filterAttrs (_: fileConfig: fileConfig.enable) dataset.files); + }) + (lib.filterAttrs ( + datasetName: dataset: let + enabledDirectories = lib.filterAttrs (_: dirConfig: dirConfig.enable) dataset.directories; + enabledFiles = lib.filterAttrs (_: fileConfig: fileConfig.enable) dataset.files; + in + (enabledDirectories != {}) || (enabledFiles != {}) + ) + (lib.filterAttrs ( + datasetName: dataset: dataset.impermanence.enable + ) + config.storage.impermanence.datasets)); + } + (lib.mkIf config.storage.zfs.enable { + storage.zfs.datasets = + lib.mapAttrs ( + datasetName: dataset: + builtins.removeAttrs dataset impermanenceOnlyOptions + ) + config.storage.impermanence.datasets; + }) + ]); +} diff --git a/modules/nixos-modules/storage/storage.nix b/modules/nixos-modules/storage/storage.nix new file mode 100644 index 0000000..771d661 --- /dev/null +++ b/modules/nixos-modules/storage/storage.nix @@ -0,0 +1,216 @@ +args @ { + lib, + config, + ... +}: let + datasetSubmodule = (import ./submodules/dataset.nix) args; + impermanenceDatasetSubmodule = (import ./submodules/impermanenceDataset.nix) args; + + # Get the option names from both submodules to automatically determine which are impermanence-specific + regularDatasetEval = lib.evalModules { + modules = [datasetSubmodule]; + specialArgs = args; + }; + impermanenceDatasetEval = lib.evalModules { + modules = [impermanenceDatasetSubmodule]; + specialArgs = args; + }; + + regularDatasetOptions = builtins.attrNames regularDatasetEval.options; + impermanenceDatasetOptions = builtins.attrNames impermanenceDatasetEval.options; + + # Find options that are only in impermanence datasets (not in regular ZFS datasets) + impermanenceOnlyOptions = lib.lists.subtractLists regularDatasetOptions impermanenceDatasetOptions; +in { + options.storage = { + generateBase = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + When enabled, enables automatic generation of base datasets (ephemeral, local, replicate roots). + This allows manual definition of datasets matching an existing system layout for migration purposes. + ''; + }; + datasets = { + ephemeral = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule datasetSubmodule); + default = {}; + }; + local = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodule); + default = {}; + }; + replicate = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodule); + default = {}; + }; + }; + }; + + config = lib.mkMerge [ + (lib.mkIf (config.storage.zfs.enable && config.storage.generateBase) { + # Create ZFS datasets based on storage.datasets configuration + storage.datasets = { + local = { + "nix" = { + impermanence.enable = false; + type = "zfs_fs"; + mount = "/nix"; + snapshot = { + autoSnapshot = false; + }; + atime = "off"; + relatime = "off"; + }; + }; + }; + }) + (lib.mkIf (config.storage.zfs.enable && config.storage.impermanence.enable && config.storage.generateBase) { + storage.datasets = { + ephemeral = { + "" = { + type = "zfs_fs"; + mount = null; + }; + "system/root" = { + type = "zfs_fs"; + mount = "/"; + snapshot = { + blankSnapshot = true; + }; + }; + }; + # TODO: can we auto set the mount points on these to just be `"/persist/local/${name}"` + local = { + "" = { + mount = "/persist/local"; + }; + }; + # TODO: can we auto set the mount points on these to just be `"/persist/replicate/${name}"` + replicate = { + "" = { + mount = "/persist/replicate"; + }; + "system/root" = { + mount = "/persist/replicate/system/root"; + snapshot = { + autoSnapshot = true; + }; + directories = { + "/var/lib/nixos".enable = true; + "/var/lib/systemd/coredump".enable = true; + }; + files = { + "/etc/machine-id".enable = true; + }; + }; + "home" = { + mount = "/persist/replicate/home"; + snapshot = { + autoSnapshot = true; + }; + }; + "system/var/log" = { + type = "zfs_fs"; + directories = { + "/var/log".enable = true; + }; + }; + }; + }; + + storage.zfs.datasets = lib.mkMerge [ + (lib.mapAttrs' (name: dataset: { + name = + if name == "" + then "ephemeral" + else "ephemeral/${name}"; + value = dataset; + }) + config.storage.datasets.ephemeral) + ]; + + boot.initrd.postResumeCommands = lib.mkAfter '' + zfs rollback -r rpool/ephemeral/system/root@blank + ''; + + storage.impermanence.datasets = lib.mkMerge [ + (lib.mapAttrs' (name: dataset: { + name = + if name == "" + then "persist/local" + else "persist/local/${name}"; + value = dataset; + }) + config.storage.datasets.local) + (lib.mapAttrs' (name: dataset: { + name = + if name == "" + then "persist/replicate" + else "persist/replicate/${name}"; + value = dataset; + }) + config.storage.datasets.replicate) + ]; + }) + (lib.mkIf (config.storage.zfs.enable && !config.storage.impermanence.enable && config.storage.generateBase) { + storage.datasets = { + # Base organizational datasets (only needed when impermanence is disabled) + local = { + "" = { + type = "zfs_fs"; + mount = null; + }; + "root" = { + type = "zfs_fs"; + mount = "/"; + compression = "lz4"; + acltype = "posixacl"; + relatime = "on"; + xattr = "sa"; + snapshot = { + autoSnapshot = true; + blankSnapshot = true; + }; + }; + }; + replicate = { + "" = { + type = "zfs_fs"; + mount = null; + }; + "system/var/log" = { + type = "zfs_fs"; + mount = "/var/log"; + }; + }; + }; + + storage.zfs.datasets = lib.mkMerge [ + (lib.mapAttrs' (name: dataset: { + name = + if name == "" + then "persist/local" + else "persist/local/${name}"; + value = builtins.removeAttrs dataset impermanenceOnlyOptions; + }) + config.storage.datasets.local) + (lib.mapAttrs' (name: dataset: { + name = + if name == "" + then "persist/replicate" + else "persist/replicate/${name}"; + value = builtins.removeAttrs dataset impermanenceOnlyOptions; + }) + config.storage.datasets.replicate) + ]; + }) + ]; + + # TODO: set up datasets for systemd services that want a dataset created + # TODO: home-manager.users..storage.impermanence.enable + # is false then persist the entire directory of the user + # if true persist home-manager.users..storage.impermanence.datasets + # TODO: systemd.services..storage.datasets persists + # TODO: configure other needed storage modes here +} diff --git a/modules/nixos-modules/storage/submodules/dataset.nix b/modules/nixos-modules/storage/submodules/dataset.nix new file mode 100644 index 0000000..2a45552 --- /dev/null +++ b/modules/nixos-modules/storage/submodules/dataset.nix @@ -0,0 +1,86 @@ +{lib, ...}: {name, ...}: { + options = { + type = lib.mkOption { + type = lib.types.enum ["zfs_fs" "zfs_volume"]; + default = "zfs_fs"; + description = "Type of ZFS dataset (filesystem or volume)"; + }; + + acltype = lib.mkOption { + type = lib.types.nullOr (lib.types.enum ["off" "nfsv4" "posixacl"]); + default = null; + description = "Access control list type"; + }; + + relatime = lib.mkOption { + type = lib.types.nullOr (lib.types.enum ["on" "off"]); + default = null; + description = "Controls when access time is updated"; + }; + + atime = lib.mkOption { + type = lib.types.nullOr (lib.types.enum ["on" "off"]); + default = null; + description = "Controls whether access time is updated"; + }; + + xattr = lib.mkOption { + type = lib.types.nullOr (lib.types.enum ["on" "off" "sa" "dir"]); + default = null; + description = "Extended attribute storage method"; + }; + + compression = lib.mkOption { + type = lib.types.nullOr (lib.types.enum ["on" "off" "lz4" "gzip" "zstd" "lzjb" "zle"]); + default = null; + description = "Compression algorithm to use"; + }; + + sync = lib.mkOption { + type = lib.types.nullOr (lib.types.enum ["standard" "always" "disabled"]); + default = null; + description = "Synchronous write behavior"; + }; + + mount = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = "Controls the mount point used for this file system"; + default = null; + }; + + encryption = { + enable = lib.mkEnableOption "should encryption be enabled"; + type = lib.mkOption { + type = lib.types.enum ["aes-128-ccm" "aes-192-ccm" "aes-256-ccm" "aes-128-gcm" "aes-192-gcm" "aes-256-gcm"]; + description = "What encryption type to use"; + }; + keyformat = lib.mkOption { + type = lib.types.enum ["raw" "hex" "passphrase"]; + description = "Format of the encryption key"; + }; + keylocation = lib.mkOption { + type = lib.types.str; + description = "Location of the encryption key"; + }; + }; + + snapshot = { + # This option should set this option flag + autoSnapshot = lib.mkEnableOption "Enable automatic snapshots for this dataset"; + # Creates a blank snapshot in the post create hook for rollback purposes + blankSnapshot = lib.mkEnableOption "Should a blank snapshot be auto created in the post create hook"; + }; + + recordSize = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Suggested block size for files in the file system"; + }; + + postCreateHook = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Script to run after dataset creation"; + }; + }; +} diff --git a/modules/nixos-modules/storage/submodules/impermanenceDataset.nix b/modules/nixos-modules/storage/submodules/impermanenceDataset.nix new file mode 100644 index 0000000..e4d3584 --- /dev/null +++ b/modules/nixos-modules/storage/submodules/impermanenceDataset.nix @@ -0,0 +1,56 @@ +args @ {lib, ...}: {name, ...}: let + datasetSubmodule = (import ./dataset.nix) args; + pathPermissions = { + read = lib.mkEnableOption "should the path have read permissions"; + write = lib.mkEnableOption "should the path have read permissions"; + execute = lib.mkEnableOption "should the path have read permissions"; + }; + pathTypeSubmodule = {name, ...}: { + options = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + }; + owner = { + name = lib.mkOption { + type = lib.types.str; + default = "root"; + }; + permissions = pathPermissions; + }; + group = { + name = lib.mkOption { + type = lib.types.str; + default = "root"; + }; + permissions = pathPermissions; + }; + other = { + permissions = pathPermissions; + }; + }; + }; +in { + imports = [ + datasetSubmodule + ]; + + options = { + files = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule pathTypeSubmodule); + default = {}; + }; + directories = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule pathTypeSubmodule); + default = {}; + }; + impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = true; + }; + }; + + config = { + mount = lib.mkDefault "/${name}"; + }; +} diff --git a/modules/nixos-modules/storage/zfs.nix b/modules/nixos-modules/storage/zfs.nix new file mode 100644 index 0000000..2fc6cb4 --- /dev/null +++ b/modules/nixos-modules/storage/zfs.nix @@ -0,0 +1,347 @@ +args @ { + lib, + pkgs, + config, + ... +}: let + datasetSubmodule = (import ./submodules/dataset.nix) args; + + # Hash function for disk names (max 27 chars to fit GPT limitations) + hashDisk = drive: (builtins.substring 0 27 (builtins.hashString "sha256" drive)); + + # Map "stripe" to "" for disko compatibility (disko uses "" for stripe mode) + diskoPoolMode = + if config.storage.zfs.pool.mode == "stripe" + then "" + else config.storage.zfs.pool.mode; + + # Helper to flatten vdevs into list of devices with names + allVdevDevices = lib.lists.flatten (builtins.map ( + vdev: + builtins.map ( + device: + lib.attrsets.nameValuePair (hashDisk device.device) device + ) + vdev + ) + config.storage.zfs.pool.vdevs); + + # Cache devices with names + allCacheDevices = builtins.map ( + device: + lib.attrsets.nameValuePair (hashDisk device.device) device + ) (config.storage.zfs.pool.cache); + + # All devices (vdevs + cache) + allDevices = allVdevDevices ++ allCacheDevices; + + # Boot devices - filter devices that have boot = true + bootDevices = builtins.filter (device: device.value.boot) allDevices; + + # Helper function to convert dataset options to ZFS properties + datasetToZfsOptions = dataset: let + baseOptions = + (lib.attrsets.optionalAttrs (dataset.acltype != null) {acltype = dataset.acltype;}) + // (lib.attrsets.optionalAttrs (dataset.relatime != null) {relatime = dataset.relatime;}) + // (lib.attrsets.optionalAttrs (dataset.atime != null) {atime = dataset.atime;}) + // (lib.attrsets.optionalAttrs (dataset.xattr != null) {xattr = dataset.xattr;}) + // (lib.attrsets.optionalAttrs (dataset.compression != null) {compression = dataset.compression;}) + // (lib.attrsets.optionalAttrs (dataset.sync != null) {sync = dataset.sync;}) + // (lib.attrsets.optionalAttrs (dataset.recordSize != null) {recordSize = dataset.recordSize;}); + + encryptionOptions = lib.attrsets.optionalAttrs (dataset.encryption.enable) ( + (lib.attrsets.optionalAttrs (dataset.encryption ? type) {encryption = dataset.encryption.type;}) + // (lib.attrsets.optionalAttrs (dataset.encryption ? keyformat) {keyformat = dataset.encryption.keyformat;}) + // (lib.attrsets.optionalAttrs (dataset.encryption ? keylocation) {keylocation = dataset.encryption.keylocation;}) + ); + + mountOptions = lib.attrsets.optionalAttrs (dataset ? mount && dataset.mount ? enable) ( + if builtins.isBool dataset.mount.enable + then { + canmount = + if dataset.mount.enable + then "on" + else "off"; + } + else {canmount = dataset.mount.enable;} + ); + + snapshotOptions = lib.attrsets.optionalAttrs (dataset ? snapshot && dataset.snapshot ? autoSnapshot) { + "com.sun:auto-snapshot" = + if dataset.snapshot.autoSnapshot + then "true" + else "false"; + }; + in + baseOptions // encryptionOptions // mountOptions // snapshotOptions; + + # Helper to generate post create hooks + generatePostCreateHook = name: dataset: + dataset.postCreateHook + + (lib.optionalString dataset.snapshot.blankSnapshot '' + zfs snapshot rpool/${name}@blank + ''); + + # Convert datasets to disko format + convertedDatasets = builtins.listToAttrs ( + (lib.attrsets.mapAttrsToList ( + name: dataset: + lib.attrsets.nameValuePair name { + type = dataset.type; + options = datasetToZfsOptions dataset; + mountpoint = dataset.mount or null; + postCreateHook = generatePostCreateHook name dataset; + } + ) + config.storage.zfs.datasets) + ++ (lib.optional (config.storage.zfs.rootDataset != null) ( + lib.attrsets.nameValuePair "" { + type = config.storage.zfs.rootDataset.type; + options = datasetToZfsOptions config.storage.zfs.rootDataset; + mountpoint = config.storage.zfs.rootDataset.mount or null; + postCreateHook = generatePostCreateHook "" config.storage.zfs.rootDataset; + } + )) + ); +in { + options.storage = { + zfs = { + enable = lib.mkEnableOption "Should zfs be enabled on this system."; + + notifications = { + enable = lib.mkEnableOption "are notifications enabled"; + host = lib.mkOption { + type = lib.types.str; + description = "what is the host that we are going to send the email to"; + }; + port = lib.mkOption { + type = lib.types.port; + description = "what port is the host using to receive mail on"; + }; + to = lib.mkOption { + type = lib.types.str; + description = "what account is the email going to be sent to"; + }; + user = lib.mkOption { + type = lib.types.str; + description = "what user is the email going to be set from"; + }; + tokenFile = lib.mkOption { + type = lib.types.str; + description = "file containing the password to be used by msmtp for notifications"; + }; + }; + + pool = let + deviceType = + lib.types.coercedTo lib.types.str (device: { + device = device; + boot = false; + }) (lib.types.submodule { + options = { + device = lib.mkOption { + type = lib.types.str; + }; + boot = lib.mkEnableOption "should this device be a boot device"; + }; + }); + in { + encryption = { + enable = lib.mkEnableOption "Should encryption be enabled on this pool."; + keyformat = lib.mkOption { + type = lib.types.enum ["raw" "hex" "passphrase"]; + default = "hex"; + description = "Format of the encryption key"; + }; + keylocation = lib.mkOption { + type = lib.types.str; + default = "prompt"; + description = "Location of the encryption key"; + }; + }; + mode = lib.mkOption { + type = lib.types.enum ["stripe" "mirror" "raidz1" "raidz2" "raidz3"]; + default = "raidz2"; + description = "ZFS redundancy mode for the pool"; + }; + bootPartitionSize = lib.mkOption { + type = lib.types.str; + default = "2G"; + description = "Size of the boot partition on boot drives"; + }; + vdevs = lib.mkOption { + type = lib.types.listOf (lib.types.listOf deviceType); + default = []; + description = "List of vdevs, where each vdev is a list of devices"; + }; + cache = lib.mkOption { + type = lib.types.listOf deviceType; + default = []; + }; + }; + + rootDataset = lib.mkOption { + type = lib.types.nullOr (lib.types.submodule datasetSubmodule); + description = "Root ZFS dataset to create"; + default = null; + }; + + datasets = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule datasetSubmodule); + description = "Additional ZFS datasets to create"; + default = {}; + }; + }; + }; + + config = lib.mkIf config.storage.zfs.enable (lib.mkMerge [ + { + # Assertion that we have at least one boot device + assertions = [ + { + assertion = (builtins.length bootDevices) > 0; + message = "ZFS configuration requires at least one boot device. Set boot = true for at least one device in your vdevs or cache."; + } + ]; + + # # Warning about disk/dataset mismatches - these would be runtime checks + # warnings = let + # configuredDisks = builtins.map (device: device.device) (builtins.map (dev: dev.value) allDevices); + # diskWarnings = + # lib.optional (config.storage.zfs.enable) + # "ZFS: Please ensure the following disks are available on your system: ${builtins.concatStringsSep ", " configuredDisks}"; + + # configuredDatasets = builtins.attrNames config.storage.zfs.datasets; + # datasetWarnings = + # lib.optional (config.storage.zfs.enable && (builtins.length configuredDatasets) > 0) + # "ZFS: Configured datasets: ${builtins.concatStringsSep ", " configuredDatasets}. Ensure these match your intended ZFS layout."; + # in + # diskWarnings ++ datasetWarnings; + + services.zfs = { + autoScrub.enable = true; + autoSnapshot.enable = true; + }; + + # # Configure disko for ZFS setup + disko.devices = { + disk = builtins.listToAttrs ( + builtins.map ( + drive: + lib.attrsets.nameValuePair (drive.name) { + type = "disk"; + device = "/dev/disk/by-id/${drive.value.device}"; + content = { + type = "gpt"; + partitions = { + ESP = lib.mkIf drive.value.boot { + size = config.storage.zfs.pool.bootPartitionSize; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = ["umask=0077"]; + }; + }; + zfs = { + size = "100%"; + content = { + type = "zfs"; + pool = "rpool"; + }; + }; + }; + }; + } + ) + allDevices + ); + + zpool = { + rpool = { + type = "zpool"; + mode = { + topology = { + type = "topology"; + vdev = + builtins.map (vdev: { + mode = diskoPoolMode; + members = builtins.map (device: hashDisk device.device) vdev; + }) + config.storage.zfs.pool.vdevs; + cache = builtins.map (device: hashDisk device.device) config.storage.zfs.pool.cache; + }; + }; + + options = { + ashift = "12"; + autotrim = "on"; + }; + + rootFsOptions = + { + canmount = "off"; + mountpoint = "none"; + xattr = "sa"; + acltype = "posixacl"; + relatime = "on"; + compression = "lz4"; + "com.sun:auto-snapshot" = "false"; + } + // (lib.attrsets.optionalAttrs config.storage.zfs.pool.encryption.enable { + encryption = "on"; + keyformat = config.storage.zfs.pool.encryption.keyformat; + keylocation = config.storage.zfs.pool.encryption.keylocation; + }); + + datasets = convertedDatasets; + }; + }; + }; + } + (lib.mkIf config.storage.zfs.notifications.enable { + programs.msmtp = { + enable = true; + setSendmail = true; + defaults = { + aliases = "/etc/aliases"; + port = config.storage.zfs.notifications.port; + tls_trust_file = "/etc/ssl/certs/ca-certificates.crt"; + tls = "on"; + auth = "login"; + tls_starttls = "off"; + }; + accounts = { + zfs_notifications = { + auth = true; + tls = true; + host = config.storage.zfs.notifications.host; + passwordeval = "cat ${config.storage.zfs.notifications.tokenFile}"; + user = config.storage.zfs.notifications.user; + from = config.storage.zfs.notifications.user; + }; + }; + }; + + services.zfs = { + zed = { + enableMail = true; + + settings = { + ZED_DEBUG_LOG = "/tmp/zed.debug.log"; + ZED_EMAIL_ADDR = [config.storage.zfs.notifications.to]; + ZED_EMAIL_PROG = "${pkgs.msmtp}/bin/msmtp"; + ZED_EMAIL_OPTS = "-a zfs_notifications @ADDRESS@"; + + ZED_NOTIFY_INTERVAL_SECS = 3600; + ZED_NOTIFY_VERBOSE = true; + + ZED_USE_ENCLOSURE_LEDS = true; + ZED_SCRUB_AFTER_RESILVER = true; + }; + }; + }; + }) + ]); +} diff --git a/modules/nixos-modules/sync.nix b/modules/nixos-modules/sync.nix deleted file mode 100644 index 8915dc8..0000000 --- a/modules/nixos-modules/sync.nix +++ /dev/null @@ -1,68 +0,0 @@ -{ - config, - lib, - outputs, - ... -}: let - mountDir = "/mnt/sync"; - configDir = "/etc/syncthing"; -in { - config = lib.mkMerge [ - { - systemd = lib.mkIf config.services.syncthing.enable { - tmpfiles.rules = [ - "d ${mountDir} 2755 syncthing syncthing -" - "d ${config.services.syncthing.dataDir} 775 syncthing syncthing -" - "d ${config.services.syncthing.configDir} 755 syncthing syncthing -" - ]; - }; - } - (lib.mkIf config.services.syncthing.enable (lib.mkMerge [ - { - services.syncthing = { - user = "syncthing"; - group = "syncthing"; - dataDir = "${mountDir}/default"; - configDir = configDir; - overrideDevices = true; - overrideFolders = true; - configuration = outputs.syncthingConfiguration; - deviceName = config.networking.hostName; - }; - } - - (lib.mkIf config.host.impermanence.enable { - assertions = - [ - { - assertion = config.services.syncthing.configDir == configDir; - message = "syncthing config dir does not match persistence"; - } - ] - ++ lib.attrsets.mapAttrsToList (_: folder: { - assertion = lib.strings.hasPrefix mountDir folder.path; - message = "syncthing folder ${folder.label} is stored at ${folder.path} which not under the persisted path of ${mountDir}"; - }) - config.services.syncthing.settings.folders; - environment.persistence = { - "/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = mountDir; - user = "syncthing"; - group = "syncthing"; - } - { - directory = configDir; - user = "syncthing"; - group = "syncthing"; - } - ]; - }; - }; - }) - ])) - ]; -} diff --git a/modules/nixos-modules/sync/default.nix b/modules/nixos-modules/sync/default.nix new file mode 100644 index 0000000..5640417 --- /dev/null +++ b/modules/nixos-modules/sync/default.nix @@ -0,0 +1,6 @@ +{...}: { + imports = [ + ./sync.nix + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/sync/storage.nix b/modules/nixos-modules/sync/storage.nix new file mode 100644 index 0000000..61bf855 --- /dev/null +++ b/modules/nixos-modules/sync/storage.nix @@ -0,0 +1,32 @@ +{ + config, + lib, + ... +}: let + mountDir = "/mnt/sync"; + configDir = "/etc/syncthing"; +in { + options = { + services.syncthing.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.syncthing.enable && config.storage.impermanence.enable; + }; + }; + + config = lib.mkIf config.services.syncthing.enable { + storage.datasets.replicate."system/root" = { + directories = { + "${mountDir}" = lib.mkIf config.services.syncthing.impermanence.enable { + enable = true; + owner.name = "syncthing"; + group.name = "syncthing"; + }; + "${configDir}" = lib.mkIf config.services.syncthing.impermanence.enable { + enable = true; + owner.name = "syncthing"; + group.name = "syncthing"; + }; + }; + }; + }; +} diff --git a/modules/nixos-modules/sync/sync.nix b/modules/nixos-modules/sync/sync.nix new file mode 100644 index 0000000..28b6e38 --- /dev/null +++ b/modules/nixos-modules/sync/sync.nix @@ -0,0 +1,36 @@ +{ + config, + lib, + syncthingConfiguration, + ... +}: let + mountDir = "/mnt/sync"; + configDir = "/etc/syncthing"; +in { + config = lib.mkMerge [ + { + systemd = lib.mkIf config.services.syncthing.enable { + tmpfiles.rules = [ + "A ${mountDir} - - - - u:syncthing:rwX,g:syncthing:rwX,o::-" + "d ${mountDir} 2755 syncthing syncthing -" + "d ${config.services.syncthing.dataDir} 775 syncthing syncthing -" + "d ${config.services.syncthing.configDir} 755 syncthing syncthing -" + ]; + }; + } + (lib.mkIf config.services.syncthing.enable (lib.mkMerge [ + { + services.syncthing = { + user = "syncthing"; + group = "syncthing"; + dataDir = "${mountDir}/default"; + configDir = configDir; + overrideDevices = true; + overrideFolders = true; + configuration = syncthingConfiguration; + deviceName = config.networking.hostName; + }; + } + ])) + ]; +} diff --git a/modules/nixos-modules/system.nix b/modules/nixos-modules/system.nix index 51a92ed..b839067 100644 --- a/modules/nixos-modules/system.nix +++ b/modules/nixos-modules/system.nix @@ -1,6 +1,5 @@ {...}: { nix = { - settings.download-buffer-size = 524288000; gc = { automatic = true; dates = "weekly"; diff --git a/modules/nixos-modules/tailscale.nix b/modules/nixos-modules/tailscale.nix deleted file mode 100644 index db664e8..0000000 --- a/modules/nixos-modules/tailscale.nix +++ /dev/null @@ -1,34 +0,0 @@ -{ - config, - lib, - ... -}: let - tailscale_data_directory = "/var/lib/tailscale"; -in { - options.host.tailscale = { - enable = lib.mkEnableOption "should tailscale be enabled on this computer"; - }; - - config = lib.mkIf config.services.tailscale.enable ( - lib.mkMerge [ - { - # any configs we want shared between all machines - } - (lib.mkIf config.host.impermanence.enable { - environment.persistence = { - "/persist/system/root" = { - enable = true; - hideMounts = true; - directories = [ - { - directory = tailscale_data_directory; - user = "root"; - group = "root"; - } - ]; - }; - }; - }) - ] - ); -} diff --git a/modules/nixos-modules/tailscale/default.nix b/modules/nixos-modules/tailscale/default.nix new file mode 100644 index 0000000..7a283e8 --- /dev/null +++ b/modules/nixos-modules/tailscale/default.nix @@ -0,0 +1,6 @@ +{...}: { + imports = [ + ./tailscale.nix + ./storage.nix + ]; +} diff --git a/modules/nixos-modules/tailscale/storage.nix b/modules/nixos-modules/tailscale/storage.nix new file mode 100644 index 0000000..7ac7e9a --- /dev/null +++ b/modules/nixos-modules/tailscale/storage.nix @@ -0,0 +1,24 @@ +{ + config, + lib, + ... +}: let + tailscale_data_directory = "/var/lib/tailscale"; +in { + options = { + services.tailscale.impermanence.enable = lib.mkOption { + type = lib.types.bool; + default = config.services.tailscale.enable && config.storage.impermanence.enable; + }; + }; + + config = lib.mkIf config.services.tailscale.enable { + storage.datasets.replicate."system/root" = { + directories."${tailscale_data_directory}" = lib.mkIf config.services.tailscale.impermanence.enable { + enable = true; + owner.name = "root"; + group.name = "root"; + }; + }; + }; +} diff --git a/modules/nixos-modules/tailscale/tailscale.nix b/modules/nixos-modules/tailscale/tailscale.nix new file mode 100644 index 0000000..06899b1 --- /dev/null +++ b/modules/nixos-modules/tailscale/tailscale.nix @@ -0,0 +1,19 @@ +{ + config, + lib, + ... +}: { + options = { + host.tailscale = { + enable = lib.mkEnableOption "should tailscale be enabled on this computer"; + }; + }; + + config = lib.mkIf config.services.tailscale.enable ( + lib.mkMerge [ + { + # any configs we want shared between all machines + } + ] + ); +} diff --git a/modules/nixos-modules/users.nix b/modules/nixos-modules/users.nix index 68bd78b..9cef952 100644 --- a/modules/nixos-modules/users.nix +++ b/modules/nixos-modules/users.nix @@ -15,31 +15,47 @@ uids = { leyla = 1000; eve = 1002; + # ester = 1003; + # ivy = 1004; jellyfin = 2000; forgejo = 2002; - adguardhome = 2003; hass = 2004; syncthing = 2007; ollama = 2008; git = 2009; immich = 2010; qbittorrent = 2011; + paperless = 2012; + actual = 2013; + radarr = 2014; + sonarr = 2015; + bazarr = 2016; + lidarr = 2017; + crab-hole = 2018; }; gids = { leyla = 1000; eve = 1002; + # ester = 1003 + # ivy = 1004; users = 100; jellyfin_media = 2001; jellyfin = 2000; forgejo = 2002; - adguardhome = 2003; hass = 2004; syncthing = 2007; ollama = 2008; git = 2009; immich = 2010; qbittorrent = 2011; + paperless = 2012; + actual = 2013; + radarr = 2014; + sonarr = 2015; + bazarr = 2016; + lidarr = 2017; + crab-hole = 2018; }; users = config.users.users; @@ -94,7 +110,7 @@ in { description = "Leyla"; extraGroups = (lib.lists.optionals host.users.leyla.isNormalUser ["networkmanager"]) - ++ (lib.lists.optionals host.users.leyla.isPrincipleUser ["wheel" "dialout"]) + ++ (lib.lists.optionals host.users.leyla.isPrincipleUser ["wheel" "dialout" "docker"]) ++ (lib.lists.optionals host.users.leyla.isDesktopUser ["adbusers"]); hashedPasswordFile = config.sops.secrets."passwords/leyla".path; isNormalUser = host.users.leyla.isNormalUser; @@ -127,12 +143,6 @@ in { group = config.users.users.forgejo.name; }; - adguardhome = { - uid = lib.mkForce uids.adguardhome; - isSystemUser = true; - group = config.users.users.adguardhome.name; - }; - hass = { uid = lib.mkForce uids.hass; isSystemUser = true; @@ -166,9 +176,51 @@ in { qbittorrent = { uid = lib.mkForce uids.qbittorrent; - isNormalUser = true; + isSystemUser = true; group = config.users.users.qbittorrent.name; }; + + paperless = { + uid = lib.mkForce uids.paperless; + isSystemUser = true; + group = config.users.users.paperless.name; + }; + + actual = { + uid = lib.mkForce uids.actual; + isSystemUser = true; + group = config.users.users.actual.name; + }; + + radarr = { + uid = lib.mkForce uids.radarr; + isSystemUser = true; + group = config.users.users.radarr.name; + }; + + sonarr = { + uid = lib.mkForce uids.sonarr; + isSystemUser = true; + group = config.users.users.sonarr.name; + }; + + bazarr = { + uid = lib.mkForce uids.bazarr; + isSystemUser = true; + group = config.users.users.bazarr.name; + }; + + lidarr = { + uid = lib.mkForce uids.lidarr; + isSystemUser = true; + group = config.users.users.lidarr.name; + }; + + crab-hole = { + uid = lib.mkForce uids.crab-hole; + isSystemUser = true; + group = config.users.users.crab-hole.name; + }; }; groups = { @@ -198,6 +250,10 @@ in { gid = lib.mkForce gids.jellyfin_media; members = [ users.jellyfin.name + users.radarr.name + users.sonarr.name + users.bazarr.name + users.lidarr.name leyla eve ]; @@ -219,14 +275,6 @@ in { ]; }; - adguardhome = { - gid = lib.mkForce gids.adguardhome; - members = [ - users.adguardhome.name - # leyla - ]; - }; - hass = { gid = lib.mkForce gids.hass; members = [ @@ -273,82 +321,112 @@ in { leyla ]; }; + + paperless = { + gid = lib.mkForce gids.paperless; + members = [ + users.paperless.name + ]; + }; + + actual = { + gid = lib.mkForce gids.actual; + members = [ + users.actual.name + ]; + }; + + radarr = { + gid = lib.mkForce gids.radarr; + members = [ + users.radarr.name + ]; + }; + + sonarr = { + gid = lib.mkForce gids.sonarr; + members = [ + users.sonarr.name + ]; + }; + + bazarr = { + gid = lib.mkForce gids.bazarr; + members = [ + users.bazarr.name + ]; + }; + + lidarr = { + gid = lib.mkForce gids.lidarr; + members = [ + users.lidarr.name + ]; + }; + + crab-hole = { + gid = lib.mkForce gids.crab-hole; + members = [ + users.crab-hole.name + ]; + }; }; }; } - (lib.mkIf config.host.impermanence.enable { - boot.initrd.postResumeCommands = lib.mkAfter ( - lib.strings.concatLines (builtins.map (user: "zfs rollback -r rpool/local/home/${user.name}@blank") - normalUsers) - ); - - systemd = { - tmpfiles.rules = - builtins.map ( - user: "d /persist/home/${user.name} 700 ${user.name} ${user.name} -" - ) - normalUsers; - }; - - fileSystems = lib.mkMerge [ - { - ${SOPS_AGE_KEY_DIRECTORY}.neededForBoot = true; - } - ( - builtins.listToAttrs ( - builtins.map (user: - lib.attrsets.nameValuePair "/persist/home/${user.name}" { - neededForBoot = true; - }) - normalUsers - ) - ) - ( - builtins.listToAttrs ( - builtins.map (user: - lib.attrsets.nameValuePair "/home/${user.name}" { - neededForBoot = true; - }) - normalUsers - ) - ) - ]; - - host.storage.pool.extraDatasets = lib.mkMerge ( - [ - { - # sops age key needs to be available to pre persist for user generation - "local/system/sops" = { - type = "zfs_fs"; - mountpoint = SOPS_AGE_KEY_DIRECTORY; - options = { - atime = "off"; - relatime = "off"; - canmount = "on"; - }; - }; - } - ] - ++ ( + (lib.mkIf config.storage.zfs.enable (lib.mkMerge [ + { + # sops age key needs to be available to pre persist for user generation + storage.datasets.local."system/sops" = { + type = "zfs_fs"; + mount = SOPS_AGE_KEY_DIRECTORY; + atime = "off"; + relatime = "off"; + impermanence.enable = false; + }; + } + (lib.mkIf (!config.storage.impermanence.enable) { + storage.datasets.replicate = lib.mkMerge ( builtins.map (user: { - "local/home/${user.name}" = { + "home/${user.name}" = { type = "zfs_fs"; - mountpoint = "/home/${user.name}"; - options = { - canmount = "on"; - }; - postCreateHook = '' - zfs snapshot rpool/local/home/${user.name}@blank - ''; - }; - "persist/home/${user.name}" = { - type = "zfs_fs"; - mountpoint = "/persist/home/${user.name}"; + mount = "/home/${user.name}"; + snapshot.autoSnapshot = true; }; }) normalUsers - ) - ); - }) + ); + }) + (lib.mkIf config.storage.impermanence.enable { + storage.datasets.ephemeral = lib.mkMerge ( + builtins.map (user: { + "home/${user.name}" = { + type = "zfs_fs"; + mount = "/home/${user.name}"; + snapshot.blankSnapshot = true; + }; + }) + normalUsers + ); + + # Post resume commands to rollback user home datasets to blank snapshots + # Only add these when generateBase is true -- when false, the legacy + # storage config is responsible for providing rollback commands with + # the correct (old) dataset paths. + boot.initrd.postResumeCommands = lib.mkIf config.storage.generateBase (lib.mkAfter ( + lib.strings.concatLines (builtins.map (user: "zfs rollback -r rpool/ephemeral/home/${user.name}@blank") + normalUsers) + )); + + # TODO: I don't think we need this anymore but I have not tested it + # Create persist home directories with proper permissions + # systemd = { + # tmpfiles.rules = + # builtins.map ( + # user: "d /persist/replicate/home/${user.name} 700 ${user.name} ${user.name} -" + # ) + # normalUsers; + # }; + }) + ])) ]; } diff --git a/nix-config-secrets b/nix-config-secrets index 3d63dff..22be815 160000 --- a/nix-config-secrets +++ b/nix-config-secrets @@ -1 +1 @@ -Subproject commit 3d63dff77f8eda1667e3586169642cf256c4aa34 +Subproject commit 22be81505a49cd205e9b5c91f51af69c0b885ed3 diff --git a/rebuild.sh b/rebuild.sh index 45dae64..48746d9 100755 --- a/rebuild.sh +++ b/rebuild.sh @@ -1,5 +1,15 @@ #!/usr/bin/env bash +# Get current branch and git status for branch-aware behavior +current_branch=$(git branch --show-current 2>/dev/null || echo "unknown") +git_status=$(git status --porcelain 2>/dev/null || echo "") + +# Default values +default_target=$(hostname) +default_user="$USER" +default_host=$(hostname) +default_mode=$(if [[ "$current_branch" != "main" ]]; then echo "test"; else echo "switch"; fi) + if [ -d "result" ]; then preserve_result=true @@ -8,6 +18,7 @@ else fi show_trace=false +clean_vm=false while [ $# -gt 0 ]; do case "$1" in @@ -40,16 +51,37 @@ while [ $# -gt 0 ]; do --show-trace) show_trace=true ;; + --clean-vm) + clean_vm=true + ;; --help|-h) echo "--help -h: print this message" - echo "--target -t: set the target system to rebuild on" - echo "--flake -f: set the flake to rebuild on the target system" - echo "--mode -m: set the mode to rebuild flake as on the target system" - echo "--user -u: set the user to rebuild flake as on the target system" - echo "--host: set the host that the flake will be rebuilt on (unset for current machine)" + echo "--target -t: defaults to the current system" + echo " currently: $default_target" + echo "--flake -f: defaults to same as target" + echo " currently: ${target:-$default_target}" + echo "--mode -m: defaults to 'switch', but 'test' on non-main branches" + echo " currently would be: $default_mode" + echo " Available modes: switch, test, build, boot, vm" + echo " 'vm' mode builds and starts a virtual machine" + echo "--user -u: defaults to the current user" + echo " currently: $default_user" + echo "--host: defaults to building on the current machine" + echo " currently: $default_host" echo "--preserve-result: do not remove the generated result folder after building" echo "--no-preserve-result: remove any result folder after building" echo "--show-trace: show trace on builds" + echo "--clean-vm: remove existing VM disk (nixos.qcow2) before building" + echo "" + echo "Branch-aware behavior:" + echo " - On non-main branches: defaults to test mode with warning" + echo " - On main with uncommitted changes: shows warning about creating a branch" + echo " - Current branch: $current_branch" + if [[ -n "$git_status" ]]; then + echo " - Git status: uncommitted changes detected" + else + echo " - Git status: clean working tree" + fi exit 0 ;; *) @@ -60,30 +92,68 @@ while [ $# -gt 0 ]; do shift done -target=${target:-$(hostname)} +target=${target:-$default_target} flake=${flake:-$target} -mode=${mode:-switch} -user=${user:-$USER} +mode=${mode:-$default_mode} +user=${user:-$default_user} -command="nixos-rebuild $mode --use-remote-sudo --flake .#$flake" - -if [[ $host ]]; -then - command="$command --build-host $host" +# Validate mode +valid_modes="switch test build boot vm" +if [[ ! " $valid_modes " =~ " $mode " ]]; then + echo "Error: Invalid mode '$mode'" + echo "Valid modes are: $valid_modes" + exit 1 fi -if [[ "$target" != "$(hostname)" ]]; -then - command="$command --target-host $user@$target" +# Clean VM disk if requested +if [[ "$clean_vm" = true ]] && [[ -f "nixos.qcow2" ]]; then + echo "Removing existing VM disk: nixos.qcow2" + rm nixos.qcow2 fi -if [[ "$show_trace" = true ]]; -then - command="$command --show-trace" +# Branch-aware warnings and behavior +if [[ "$current_branch" != "main" ]] && [[ "$mode" == "test" ]]; then + echo "⚠️ WARNING: You are on branch '$current_branch' (not main)" + echo " Defaulting to test mode to prevent accidental system changes" + echo " Specify --mode=switch explicitly if you want to apply changes" +elif [[ "$current_branch" == "main" ]] && [[ -n "$git_status" ]] && [[ "$mode" != "test" ]]; then + echo "⚠️ WARNING: You are on main branch with uncommitted changes" + echo " Consider creating a feature branch for development:" + echo " git checkout -b feature/your-feature-name" fi -echo $command -$command +if [[ "$mode" == "vm" ]]; then + command="nix build .#nixosConfigurations.$flake.config.system.build.vm" + + if [[ "$show_trace" = true ]]; then + command="$command --show-trace" + fi + + echo $command + $command + + if [[ $? -eq 0 ]] && [[ -d "result" ]]; then + echo "Starting VM..." + QEMU_KERNEL_PARAMS=console=ttyS0 ./result/bin/run-nixos-vm -nographic; reset + fi +else + command="nixos-rebuild $mode --sudo --ask-sudo-password --flake .#$flake" + + if [[ $host ]]; then + command="$command --build-host $host" + fi + + if [[ "$target" != "$(hostname)" ]]; then + command="$command --target-host $user@$target" + fi + + if [[ "$show_trace" = true ]]; then + command="$command --show-trace" + fi + + echo $command + $command +fi if [ -d "result" ]; then @@ -91,4 +161,4 @@ then then rm -r result fi -fi \ No newline at end of file +fi diff --git a/util/default.nix b/util/default.nix index 4b713da..d72d00d 100644 --- a/util/default.nix +++ b/util/default.nix @@ -29,7 +29,6 @@ common-modules ++ [ sops-nix.homeManagerModules.sops - impermanence.homeManagerModules.impermanence ../modules/home-manager-modules ]; @@ -52,8 +51,14 @@ home-manager-config ../modules/system-modules ]; + + syncthingConfiguration = nix-syncthing.lib.syncthingConfiguration { + modules = [ + (import ../configurations/syncthing) + ]; + }; in { - forEachPkgs = lambda: forEachSystem (system: lambda (pkgsFor system)); + forEachPkgs = lambda: forEachSystem (system: lambda system (pkgsFor system)); mkUnless = condition: yes: (lib.mkIf (!condition) yes); mkIfElse = condition: yes: no: @@ -62,19 +67,9 @@ in { (lib.mkUnless condition no) ]; - mkNixosInstaller = host: userKeys: - nixpkgs.lib.nixosSystem { - modules = [ - { - # TODO: authorized keys for all users and hosts - } - ../configurations/nixos/${host} - ]; - }; - mkNixosSystem = host: nixpkgs.lib.nixosSystem { - specialArgs = {inherit inputs outputs util;}; + specialArgs = {inherit inputs outputs util syncthingConfiguration;}; modules = system-modules ++ [ @@ -119,10 +114,4 @@ in { ../configurations/home-manager/${user} ]; }; - - syncthingConfiguration = nix-syncthing.lib.syncthingConfiguration { - modules = [ - (import ../configurations/syncthing) - ]; - }; }