From fe4a2e86113dba8127f3b580203db2924dc2c0f2 Mon Sep 17 00:00:00 2001 From: tsandrini Date: Wed, 28 Jan 2026 14:09:58 +0100 Subject: [PATCH 01/18] fix(hosts/jetbundle): update dms & niri --- flake-parts/hosts/default.nix | 1 + flake-parts/hosts/jetbundle/default.nix | 2 + .../profiles/graphical-dms-niri.nix | 389 +++++++++++++++++- .../programs/terminals/wezterm.nix | 23 +- .../nixos/profiles/graphical-dms-niri.nix | 15 +- flake.lock | 7 +- flake.nix | 2 +- 7 files changed, 419 insertions(+), 20 deletions(-) diff --git a/flake-parts/hosts/default.nix b/flake-parts/hosts/default.nix index 5fecc417..f82161bd 100644 --- a/flake-parts/hosts/default.nix +++ b/flake-parts/hosts/default.nix @@ -115,6 +115,7 @@ in extraOverlays = sharedOverlays ++ [ inputs.emacs-overlay.overlays.default inputs.nur.overlays.default + inputs.niri.overlays.niri # neovim-nightly-overlay.overlays.default # (final: _prev: { nur = import inputs.nur { pkgs = final; }; }) ]; diff --git a/flake-parts/hosts/jetbundle/default.nix b/flake-parts/hosts/jetbundle/default.nix index dd3ed3c5..9092bacc 100644 --- a/flake-parts/hosts/jetbundle/default.nix +++ b/flake-parts/hosts/jetbundle/default.nix @@ -162,6 +162,8 @@ in home-manager.users."tsandrini" = { tensorfiles.hm = { profiles.graphical-dms-niri.enable = true; + programs.pywal.enable = true; + profiles.accounts.tsandrini.enable = true; security.agenix.enable = true; services.keepassxc.enable = true; diff --git a/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix b/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix index a97e6800..74dffab6 100644 --- a/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix +++ b/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix @@ -32,6 +32,79 @@ let cfg = config.tensorfiles.hm.profiles.graphical-dms-niri; _ = mkOverrideAtHmProfileLevel; + + dmsToWal = pkgs.writeShellApplication { + name = "dms-to-wal"; + runtimeInputs = [ + pkgs.jq + pkgs.coreutils + ]; + text = '' + set -euo pipefail + + CACHE_HOME="''${XDG_CACHE_HOME:-$HOME/.cache}" + DMS_COLORS="$CACHE_HOME/DankMaterialShell/dms-colors.json" + DMS_CACHE="$CACHE_HOME/DankMaterialShell/cache.json" + + WAL_DIR="$CACHE_HOME/wal" + COLORS_JSON="$WAL_DIR/colors.json" + PYWALFOX_JSON="$WAL_DIR/dank-pywalfox.json" + SEQUENCES="$WAL_DIR/sequences" + + mkdir -p "$WAL_DIR" + + if [[ ! -r "$DMS_COLORS" ]]; then + echo "dms-to-wal: missing DMS colors at $DMS_COLORS" >&2 + exit 0 + fi + + # Try to discover the current wallpaper path (for pywalfox compatibility). + # Best-effort: if not found, set to empty string (still better than missing key). + wallpaper="" + if [[ -r "$DMS_CACHE" ]]; then + wallpaper="$(jq -r ' + .wallpaper + // .currentWallpaper + // .current_wallpaper + // .lastWallpaper + // empty + ' "$DMS_CACHE" 2>/dev/null || true)" + fi + + # Generate a pywal-ish colors.json from DMS palette + # Important: capture original input as $dms so reduce doesn't lose access. + jq --arg wallpaper "$wallpaper" ' + . as $dms + | def c($i): ($dms.dank16["color"+($i|tostring)].default // $dms.dank16["color"+($i|tostring)].dark // null); + + { + wallpaper: $wallpaper, + special: { + background: $dms.colors.dark.background, + foreground: $dms.colors.dark.on_background, + cursor: $dms.colors.dark.on_background + }, + colors: (reduce range(0;16) as $i ({}; . + {("color"+($i|tostring)): c($i)})) + } + ' "$DMS_COLORS" > "$COLORS_JSON" + + # Pywalfox: DMS doc expects this to exist; many setups read colors.json directly too. + cp -f "$COLORS_JSON" "$PYWALFOX_JSON" + + # Terminal escape sequences (OSC 4,10,11,12) + # (also avoid "color"+number concat bugs by tostring) + jq -r ' + def osc4(i;c): "\u001b]4;\(i);\(c)\u001b\\"; + def osc10(c): "\u001b]10;\(c)\u001b\\"; + def osc11(c): "\u001b]11;\(c)\u001b\\"; + def osc12(c): "\u001b]12;\(c)\u001b\\"; + (range(0;16) | osc4(.; .colors["color"+(.|tostring)])) + , osc10(.special.foreground) + , osc11(.special.background) + , osc12(.special.cursor) + ' "$COLORS_JSON" > "$SEQUENCES" + ''; + }; in { options.tensorfiles.hm.profiles.graphical-dms-niri = { @@ -51,7 +124,6 @@ in imports = [ inputs.dms.homeModules.dank-material-shell inputs.dms.homeModules.niri - # inputs.niri.homeModules.niri # NOTE: included in NixOS profile ]; config = mkIf cfg.enable (mkMerge [ @@ -73,16 +145,79 @@ in }; }; + # Run once each login (so ~/.cache/wal exists even before the first palette change), + # and also whenever DMS regenerates dms-colors.json. + systemd.user.services.dms-to-wal = { + Unit = { + Description = "Generate pywal artifacts from DMS palette"; + # optional but nice: + After = [ "graphical-session.target" ]; + PartOf = [ "graphical-session.target" ]; + }; + Service = { + Type = "oneshot"; + ExecStart = "${lib.getExe dmsToWal}"; + }; + Install = { + WantedBy = [ "default.target" ]; + }; + }; + + systemd.user.paths.dms-to-wal = { + Unit = { + Description = "Watch DMS palette and refresh pywal artifacts"; + }; + Path = { + PathChanged = "%h/.cache/DankMaterialShell/dms-colors.json"; + Unit = "dms-to-wal.service"; + }; + Install = { + WantedBy = [ "default.target" ]; + }; + }; + home.packages = [ pkgs.neovide # This is a simple graphical user interface for Neovim + pkgs.nerd-fonts.jetbrains-mono + pkgs.nerd-fonts.symbols-only + pkgs.noto-fonts + pkgs.noto-fonts-color-emoji + pkgs.hicolor-icon-theme + pkgs.adwaita-icon-theme + pkgs.papirus-icon-theme + pkgs.kdePackages.breeze-icons + + pkgs.nautilus + pkgs.gvfs + pkgs.udiskie + pkgs.file-roller + pkgs.loupe + pkgs.mpv + pkgs.vlc + pkgs.pavucontrol + pkgs.blueman + pkgs.kdePackages.qt6ct + + pkgs.kdePackages.gwenview + pkgs.qimgv + pkgs.imv + pkgs.kdePackages.ark + pkgs.kdePackages.dolphin + + pkgs.matugen ] ++ (optional cfg.include-nvim localFlake.packages.${system}.nvim-ide-config); services.flameshot = { enable = _ true; + package = pkgs.flameshot.override { + enableWlrSupport = true; + }; settings = { - General.showStartupLaunchMessage = _ false; - General.useGrimAdapter = _ true; + General = { + showStartupLaunchMessage = false; + useGrimAdapter = true; + }; }; }; @@ -97,25 +232,256 @@ in TERMINAL = _ "wezterm"; IDE = _ "nvim"; EMAIL = _ "thunderbird"; + QT_QPA_PLATFORMTHEME = "qt6ct"; }; fonts.fontconfig.enable = _ true; + gtk = { + enable = true; + + theme = { + name = "adw-gtk3-dark"; + package = pkgs.adw-gtk3; + }; + + # Optional but recommended: make icons consistent too + iconTheme = { + name = "Papirus-Dark"; + package = pkgs.papirus-icon-theme; + }; + }; + + programs.niri = { + package = pkgs.niri-unstable; + + settings = { + prefer-no-csd = true; + screenshot-path = "~/FiberBundle/Images/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"; # TODO + workspaces = { + "01" = { + name = "1"; + }; + "02" = { + name = "2"; + }; + "03" = { + name = "3"; + }; + "04" = { + name = "4"; + }; + "05" = { + name = "5"; + }; + "06" = { + name = "6"; + }; + "07" = { + name = "7"; + }; + "08" = { + name = "8"; + }; + }; + + # 1) remove DMS "run" spawn; DMS already spawns via enableSpawn=true + spawn-at-startup = [ + # kdeconnect tray indicator + { argv = [ "kdeconnect-indicator" ]; } + ]; + + binds = + let + a = config.lib.niri.actions; + mod = "Mod"; + dms = + cmd: + a.spawn ( + [ + "dms" + "ipc" + "call" + ] + ++ cmd + ); + in + { + # DMS spotlight + "${mod}+Space".action = dms [ + "spotlight" + "toggle" + ]; + + # Terminal + "${mod}+Return".action = a.spawn config.home.sessionVariables.TERMINAL; + + # Toggle between last visited workspaces + "${mod}+Tab".action = a.focus-workspace-previous; + + # Workspace up/down + "${mod}+Up".action = a.focus-workspace-up; + "${mod}+Down".action = a.focus-workspace-down; + "${mod}+K".action = a.focus-workspace-up; + "${mod}+J".action = a.focus-workspace-down; + + # Columns left/right + "${mod}+Left".action = a.focus-column-left; + "${mod}+Right".action = a.focus-column-right; + "${mod}+H".action = a.focus-column-left; + "${mod}+L".action = a.focus-column-right; + + # Move stuff (optional; keep if you like) + "${mod}+Shift+Left".action = a.move-column-left; + "${mod}+Shift+Right".action = a.move-column-right; + "${mod}+Shift+Up".action = a.move-window-up; + "${mod}+Shift+Down".action = a.move-window-down; + "${mod}+Shift+K".action = a.move-window-up; + "${mod}+Shift+J".action = a.move-window-down; + + # Workspaces 1..9 + "${mod}+1".action = a.focus-workspace 1; + "${mod}+2".action = a.focus-workspace 2; + "${mod}+3".action = a.focus-workspace 3; + "${mod}+4".action = a.focus-workspace 4; + "${mod}+5".action = a.focus-workspace 5; + "${mod}+6".action = a.focus-workspace 6; + "${mod}+7".action = a.focus-workspace 7; + "${mod}+8".action = a.focus-workspace 8; + "${mod}+9".action = a.focus-workspace 9; + + # "Fullscreen but keep bars" -> maximize column + "${mod}+F".action = a.maximize-column; + + # DMS toggles + "${mod}+V".action = dms [ + "clipboard" + "toggle" + ]; + "${mod}+P".action = dms [ + "notepad" + "toggle" + ]; + "${mod}+N".action = dms [ + "notifications" + "toggle" + ]; + "${mod}+Shift+L".action = dms [ + "lock" + "lock" + ]; + + # Close + overview + "${mod}+Q".action = a.close-window; + "${mod}+W".action = a.toggle-overview; + + # PrintScreen -> DMS-native niri screenshot (uses DMS_SCREENSHOT_EDITOR) + "Print".action = a.spawn [ + "flameshot" + "gui" + ]; + "Ctrl+Print".action = a.spawn [ + "flameshot" + "gui" + ]; + "Alt+Print".action = a.spawn [ + "flameshot" + "gui" + ]; + + # --- Media keys via DMS IPC --- + "XF86AudioRaiseVolume" = { + action = dms [ + "audio" + "increment" + "5" + ]; + allow-when-locked = true; + }; + "XF86AudioLowerVolume" = { + action = dms [ + "audio" + "decrement" + "5" + ]; + allow-when-locked = true; + }; + "XF86AudioMute" = { + action = dms [ + "audio" + "mute" + ]; + allow-when-locked = true; + }; + "XF86AudioMicMute" = { + action = dms [ + "audio" + "micmute" + ]; + allow-when-locked = true; + }; + + # Media playback via DMS (MPRIS) + "XF86AudioPlay".action = dms [ + "mpris" + "playPause" + ]; + "XF86AudioPause".action = dms [ + "mpris" + "pause" + ]; + "XF86AudioNext".action = dms [ + "mpris" + "next" + ]; + "XF86AudioPrev".action = dms [ + "mpris" + "previous" + ]; + "XF86AudioStop".action = dms [ + "mpris" + "stop" + ]; + + # Brightness via DMS IPC + "XF86MonBrightnessUp".action = dms [ + "brightness" + "increment" + "10" + "" + ]; + "XF86MonBrightnessDown".action = dms [ + "brightness" + "decrement" + "10" + "" + ]; + }; + }; + }; + programs.dank-material-shell = { enable = _ true; - systemd = { - enable = true; # Systemd service for auto-start - restartIfChanged = true; # Auto-restart dms.service when dank-material-shell changes + enable = _ false; + restartIfChanged = _ true; }; niri = { - enableSpawn = _ false; # Auto-start DMS with niri, if enabled - enableKeybinds = _ true; + enableSpawn = _ true; + enableKeybinds = _ false; includes = { - enable = _ false; - originalFileName = "hm"; - override = true; + enable = _ true; + override = _ true; + filesToInclude = [ + "alttab" + "binds" + "cursor" + "colors" + "layout" + "outputs" + "wpblur" + ]; }; }; @@ -126,7 +492,6 @@ in enableCalendarEvents = _ true; enableClipboardPaste = _ true; }; - } # |----------------------------------------------------------------------| # ]); diff --git a/flake-parts/modules/home-manager/programs/terminals/wezterm.nix b/flake-parts/modules/home-manager/programs/terminals/wezterm.nix index c72ecd44..479df944 100644 --- a/flake-parts/modules/home-manager/programs/terminals/wezterm.nix +++ b/flake-parts/modules/home-manager/programs/terminals/wezterm.nix @@ -53,7 +53,7 @@ in local modal = wezterm.plugin.require("https://github.com/MLFlexer/modal.wezterm") ${ - if pywalCheck then + if (pywalCheck && false) then # TODO '' wezterm.add_to_config_reload_watch_list("~/.cache/wal") config.color_scheme_dirs = {"~/.cache/wal"} @@ -62,6 +62,27 @@ in "" } + local ok, json = pcall(wezterm.read_file, "~/.cache/wal/colors.json") + if ok and json then + local data = wezterm.json_parse(json) + if data and data.colors and data.special then + config.colors = { + foreground = data.special.foreground, + background = data.special.background, + cursor_bg = data.special.cursor, + cursor_fg = data.special.background, + ansi = { + data.colors.color0, data.colors.color1, data.colors.color2, data.colors.color3, + data.colors.color4, data.colors.color5, data.colors.color6, data.colors.color7, + }, + brights = { + data.colors.color8, data.colors.color9, data.colors.color10, data.colors.color11, + data.colors.color12, data.colors.color13, data.colors.color14, data.colors.color15, + }, + } + end + end + config.default_cursor_style = 'BlinkingBar' config.enable_scroll_bar = true config.font_size = 10 diff --git a/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix b/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix index 420d8d04..9b1816e0 100644 --- a/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix +++ b/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix @@ -115,17 +115,28 @@ in }; programs.ssh.startAgent = _ false; # NOTE: using gnome agent - programs.niri.enable = _ true; + programs.niri = { + enable = _ true; + package = _ pkgs.niri-unstable; + }; + services.dbus.enable = _ true; security.polkit.enable = _ true; + programs.dconf.enable = _ true; + xdg.portal = { enable = _ true; - extraPortals = [ pkgs.xdg-desktop-portal-wlr ]; + extraPortals = [ + pkgs.xdg-desktop-portal-wlr + pkgs.xdg-desktop-portal-gtk + ]; config.common.default = "*"; }; environment.sessionVariables = { NIXOS_OZONE_WL = _ "1"; + #DISPLAY = ":0"; + #QT_QPA_PLATFORM = "wayland"; }; services.pcscd.enable = _ true; # needed for gpg pinentry diff --git a/flake.lock b/flake.lock index bd661598..f8aff6b5 100644 --- a/flake.lock +++ b/flake.lock @@ -193,16 +193,15 @@ "quickshell": "quickshell" }, "locked": { - "lastModified": 1768575133, - "narHash": "sha256-P//moH3z9r4PXirTzXVsccQINsK5AIlF9RWOBwK3vLc=", + "lastModified": 1769493135, + "narHash": "sha256-9h3lV7MpAHvugCCUyOEmwThpJp7aSA4qE9UTQR/8KOc=", "owner": "AvengeMedia", "repo": "DankMaterialShell", - "rev": "a7cdb39b0b89b9af86160ad4e847a7d14ea44512", + "rev": "9553cb06d34a255486733e17c11f6874dd9d99a3", "type": "github" }, "original": { "owner": "AvengeMedia", - "ref": "stable", "repo": "DankMaterialShell", "type": "github" } diff --git a/flake.nix b/flake.nix index cadcf402..cd5c258a 100644 --- a/flake.nix +++ b/flake.nix @@ -110,7 +110,7 @@ inputs.nixpkgs.follows = "nixpkgs"; }; dms = { - url = "github:AvengeMedia/DankMaterialShell/stable"; + url = "github:AvengeMedia/DankMaterialShell"; inputs.nixpkgs.follows = "nixpkgs"; }; niri = { From cd1f5ffb5e5cf463b49db9cac2e2dde3a839c77a Mon Sep 17 00:00:00 2001 From: tsandrini Date: Wed, 28 Jan 2026 14:20:08 +0100 Subject: [PATCH 02/18] fix(modules/hm/graphical-dms-niri): patch the pywal sequences generation --- .../profiles/graphical-dms-niri.nix | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix b/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix index 74dffab6..900a97b4 100644 --- a/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix +++ b/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix @@ -91,18 +91,39 @@ let # Pywalfox: DMS doc expects this to exist; many setups read colors.json directly too. cp -f "$COLORS_JSON" "$PYWALFOX_JSON" - # Terminal escape sequences (OSC 4,10,11,12) - # (also avoid "color"+number concat bugs by tostring) - jq -r ' - def osc4(i;c): "\u001b]4;\(i);\(c)\u001b\\"; - def osc10(c): "\u001b]10;\(c)\u001b\\"; - def osc11(c): "\u001b]11;\(c)\u001b\\"; - def osc12(c): "\u001b]12;\(c)\u001b\\"; - (range(0;16) | osc4(.; .colors["color"+(.|tostring)])) - , osc10(.special.foreground) - , osc11(.special.background) - , osc12(.special.cursor) - ' "$COLORS_JSON" > "$SEQUENCES" + # Terminal escape sequences (OSC). One-line blob, like pywal produces. + # Use ST terminator (\033\) like your example. + bg="$(jq -r '.special.background' "$COLORS_JSON")" + fg="$(jq -r '.special.foreground' "$COLORS_JSON")" + cur="$(jq -r '.special.cursor' "$COLORS_JSON")" + + seq="" + + # 16 base colors + for i in $(seq 0 15); do + c="$(jq -r --arg k "color$i" '.colors[$k]' "$COLORS_JSON")" + seq="''${seq}\033]4;''${i};''${c}\033\\" + done + + # Foreground/background/cursor + seq="''${seq}\033]10;''${fg}\033\\" + seq="''${seq}\033]11;''${bg}\033\\" + seq="''${seq}\033]12;''${cur}\033\\" + + # A few extra OSC codes that pywal often includes (safe; helps some apps) + # 13: mouse fg, 17: highlight bg, 19: highlight fg (values reused like typical wal) + seq="''${seq}\033]13;''${fg}\033\\" + seq="''${seq}\033]17;''${fg}\033\\" + seq="''${seq}\033]19;''${bg}\033\\" + + # 232..255 ramp endpoints (pywal writes a couple; harmless) + seq="''${seq}\033]4;232;''${bg}\033\\" + seq="''${seq}\033]4;256;''${fg}\033\\" + + # Some builds also emit this final code; keep it for compatibility + seq="''${seq}\033]708;''${bg}\033\\" + + printf '%b' "$seq" > "$SEQUENCES" ''; }; in From f37c7f32b4632ec30f965681204838c4648cfe92 Mon Sep 17 00:00:00 2001 From: tsandrini Date: Wed, 28 Jan 2026 14:44:58 +0100 Subject: [PATCH 03/18] feat(modules/hm/graphical-dms-niri): add toggle-edp script for laptops --- .../profiles/graphical-dms-niri.nix | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix b/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix index 900a97b4..7ba70e71 100644 --- a/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix +++ b/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix @@ -33,6 +33,34 @@ let cfg = config.tensorfiles.hm.profiles.graphical-dms-niri; _ = mkOverrideAtHmProfileLevel; + toggleEdp = pkgs.writeShellScriptBin "toggle-edp" '' + set -euo pipefail + + EDP="eDP-1" + + # Extract the block for eDP-1 (from its "Output ..." header until the next "Output ..." or EOF) + block="$( + niri msg outputs \ + | awk -v edp="(''${EDP})" ' + $0 ~ "^Output " { + in_block = ($0 ~ edp) + } + in_block { print } + ' + )" + + # If we didn't find the block at all, bail out (or you could default to "on"). + if [ -z "$block" ]; then + exit 0 + fi + + if echo "$block" | grep -q "^[[:space:]]*Disabled[[:space:]]*$"; then + niri msg output "$EDP" on + else + niri msg output "$EDP" off + fi + ''; + dmsToWal = pkgs.writeShellApplication { name = "dms-to-wal"; runtimeInputs = [ @@ -226,6 +254,8 @@ in pkgs.kdePackages.dolphin pkgs.matugen + + toggleEdp ] ++ (optional cfg.include-nvim localFlake.packages.${system}.nvim-ide-config); @@ -477,6 +507,10 @@ in "10" "" ]; + + # Toggle laptop display + "XF86Display".action = a.spawn [ "toggle-edp" ]; + "Mod+F7".action = a.spawn [ "toggle-edp" ]; }; }; }; From f98c034a16edb4d8bf83cd5e9d01db910bd622b4 Mon Sep 17 00:00:00 2001 From: tsandrini Date: Wed, 28 Jan 2026 21:42:23 +0100 Subject: [PATCH 04/18] feat(hosts): tweak cpuFreqGovernor --- flake-parts/hosts/default.nix | 1 + flake-parts/hosts/flatbundle/default.nix | 8 +- .../flatbundle/hardware-configuration.nix | 3 +- flake-parts/hosts/jetbundle/default.nix | 3 +- .../jetbundle/hardware-configuration.nix | 3 +- .../profiles/graphical-dms-niri.nix | 75 ++++++++++--------- .../programs/terminals/wezterm.nix | 23 +----- 7 files changed, 52 insertions(+), 64 deletions(-) diff --git a/flake-parts/hosts/default.nix b/flake-parts/hosts/default.nix index f82161bd..56298663 100644 --- a/flake-parts/hosts/default.nix +++ b/flake-parts/hosts/default.nix @@ -97,6 +97,7 @@ in extraOverlays = sharedOverlays ++ [ inputs.emacs-overlay.overlays.default inputs.nur.overlays.default + inputs.niri.overlays.niri # neovim-nightly-overlay.overlays.default # (final: _prev: { nur = import inputs.nur { pkgs = final; }; }) ]; diff --git a/flake-parts/hosts/flatbundle/default.nix b/flake-parts/hosts/flatbundle/default.nix index 5661374c..1d38f89a 100644 --- a/flake-parts/hosts/flatbundle/default.nix +++ b/flake-parts/hosts/flatbundle/default.nix @@ -55,7 +55,7 @@ in # --------------------- tensorfiles = { profiles = { - graphical-plasma6.enable = true; + graphical-dms-niri.enable = true; packages-base.enable = true; packages-extra.enable = true; @@ -152,11 +152,11 @@ in home-manager.users."tsandrini" = { tensorfiles.hm = { - profiles.graphical-plasma.enable = true; + profiles.graphical-dms-niri.enable = true; + programs.pywal.enable = true; + profiles.accounts.tsandrini.enable = true; security.agenix.enable = true; - - programs.pywal.enable = true; programs.editors.emacs-doom.enable = true; services.keepassxc.enable = true; }; diff --git a/flake-parts/hosts/flatbundle/hardware-configuration.nix b/flake-parts/hosts/flatbundle/hardware-configuration.nix index 9f775c55..68ed2d31 100644 --- a/flake-parts/hosts/flatbundle/hardware-configuration.nix +++ b/flake-parts/hosts/flatbundle/hardware-configuration.nix @@ -66,8 +66,9 @@ powerManagement = { enable = true; - cpuFreqGovernor = "performance"; + # cpuFreqGovernor = "schedutil"; }; + services.power-profiles-daemon.enable = true; programs.gamemode.enable = true; services.fwupd.enable = true; diff --git a/flake-parts/hosts/jetbundle/default.nix b/flake-parts/hosts/jetbundle/default.nix index 9092bacc..4b77ba39 100644 --- a/flake-parts/hosts/jetbundle/default.nix +++ b/flake-parts/hosts/jetbundle/default.nix @@ -61,6 +61,7 @@ in tensorfiles = { profiles = { graphical-dms-niri.enable = true; + packages-base.enable = true; packages-extra.enable = true; packages-graphical-extra.enable = true; @@ -157,8 +158,6 @@ in ]; }; - # Small QoL for Wayland apps (optional) - home-manager.users."tsandrini" = { tensorfiles.hm = { profiles.graphical-dms-niri.enable = true; diff --git a/flake-parts/hosts/jetbundle/hardware-configuration.nix b/flake-parts/hosts/jetbundle/hardware-configuration.nix index 6464e5c5..d73d9d6a 100644 --- a/flake-parts/hosts/jetbundle/hardware-configuration.nix +++ b/flake-parts/hosts/jetbundle/hardware-configuration.nix @@ -44,8 +44,9 @@ powerManagement = { enable = true; - cpuFreqGovernor = "performance"; + # cpuFreqGovernor = "schedutil"; }; + services.power-profiles-daemon.enable = true; programs.gamemode.enable = true; services.fwupd.enable = true; diff --git a/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix b/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix index 7ba70e71..7529bc93 100644 --- a/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix +++ b/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix @@ -296,7 +296,6 @@ in package = pkgs.adw-gtk3; }; - # Optional but recommended: make icons consistent too iconTheme = { name = "Papirus-Dark"; package = pkgs.papirus-icon-theme; @@ -336,9 +335,19 @@ in }; }; - # 1) remove DMS "run" spawn; DMS already spawns via enableSpawn=true + input = { + keyboard = { + xkb = { + layout = "us,cz"; + variant = ",qwerty"; + options = "grp:alt_caps_toggle"; + }; + + track-layout = "global"; + }; + }; + spawn-at-startup = [ - # kdeconnect tray indicator { argv = [ "kdeconnect-indicator" ]; } ]; @@ -358,37 +367,31 @@ in ); in { - # DMS spotlight - "${mod}+Space".action = dms [ - "spotlight" - "toggle" - ]; - - # Terminal - "${mod}+Return".action = a.spawn config.home.sessionVariables.TERMINAL; - - # Toggle between last visited workspaces - "${mod}+Tab".action = a.focus-workspace-previous; - - # Workspace up/down - "${mod}+Up".action = a.focus-workspace-up; - "${mod}+Down".action = a.focus-workspace-down; - "${mod}+K".action = a.focus-workspace-up; - "${mod}+J".action = a.focus-workspace-down; + # --- Columns --- + "${mod}+H".action = a.focus-column-left; + "${mod}+J".action = a.focus-window-down; + "${mod}+K".action = a.focus-window-up; + "${mod}+L".action = a.focus-column-right; - # Columns left/right "${mod}+Left".action = a.focus-column-left; + "${mod}+Down".action = a.focus-window-down; + "${mod}+Up".action = a.focus-window-up; "${mod}+Right".action = a.focus-column-right; - "${mod}+H".action = a.focus-column-left; - "${mod}+L".action = a.focus-column-right; - # Move stuff (optional; keep if you like) + # --- Workspaces --- + "${mod}+U".action = a.focus-workspace-down; + "${mod}+I".action = a.focus-workspace-up; + + # --- Moving stuff --- + "${mod}+Shift+H".action = a.move-column-left; + "${mod}+Shift+J".action = a.move-window-down; + "${mod}+Shift+K".action = a.move-window-up; + "${mod}+Shift+L".action = a.move-column-right; + "${mod}+Shift+Left".action = a.move-column-left; - "${mod}+Shift+Right".action = a.move-column-right; - "${mod}+Shift+Up".action = a.move-window-up; "${mod}+Shift+Down".action = a.move-window-down; - "${mod}+Shift+K".action = a.move-window-up; - "${mod}+Shift+J".action = a.move-window-down; + "${mod}+Shift+Up".action = a.move-window-up; + "${mod}+Shift+Right".action = a.move-column-right; # Workspaces 1..9 "${mod}+1".action = a.focus-workspace 1; @@ -401,9 +404,16 @@ in "${mod}+8".action = a.focus-workspace 8; "${mod}+9".action = a.focus-workspace 9; - # "Fullscreen but keep bars" -> maximize column "${mod}+F".action = a.maximize-column; + # --- Apps --- + "${mod}+Space".action = dms [ + "spotlight" + "toggle" + ]; + "${mod}+Return".action = a.spawn config.home.sessionVariables.TERMINAL; + "${mod}+Tab".action = a.focus-workspace-previous; + # DMS toggles "${mod}+V".action = dms [ "clipboard" @@ -417,16 +427,14 @@ in "notifications" "toggle" ]; - "${mod}+Shift+L".action = dms [ + "Ctrl+Alt+L".action = dms [ "lock" "lock" ]; - # Close + overview "${mod}+Q".action = a.close-window; "${mod}+W".action = a.toggle-overview; - # PrintScreen -> DMS-native niri screenshot (uses DMS_SCREENSHOT_EDITOR) "Print".action = a.spawn [ "flameshot" "gui" @@ -494,7 +502,6 @@ in "stop" ]; - # Brightness via DMS IPC "XF86MonBrightnessUp".action = dms [ "brightness" "increment" @@ -508,7 +515,7 @@ in "" ]; - # Toggle laptop display + # NOTE: just a quickfix when I need to leave and just pull cords out "XF86Display".action = a.spawn [ "toggle-edp" ]; "Mod+F7".action = a.spawn [ "toggle-edp" ]; }; diff --git a/flake-parts/modules/home-manager/programs/terminals/wezterm.nix b/flake-parts/modules/home-manager/programs/terminals/wezterm.nix index 479df944..cf97c303 100644 --- a/flake-parts/modules/home-manager/programs/terminals/wezterm.nix +++ b/flake-parts/modules/home-manager/programs/terminals/wezterm.nix @@ -53,7 +53,7 @@ in local modal = wezterm.plugin.require("https://github.com/MLFlexer/modal.wezterm") ${ - if (pywalCheck && false) then # TODO + if pywalCheck then # TODO '' wezterm.add_to_config_reload_watch_list("~/.cache/wal") config.color_scheme_dirs = {"~/.cache/wal"} @@ -62,27 +62,6 @@ in "" } - local ok, json = pcall(wezterm.read_file, "~/.cache/wal/colors.json") - if ok and json then - local data = wezterm.json_parse(json) - if data and data.colors and data.special then - config.colors = { - foreground = data.special.foreground, - background = data.special.background, - cursor_bg = data.special.cursor, - cursor_fg = data.special.background, - ansi = { - data.colors.color0, data.colors.color1, data.colors.color2, data.colors.color3, - data.colors.color4, data.colors.color5, data.colors.color6, data.colors.color7, - }, - brights = { - data.colors.color8, data.colors.color9, data.colors.color10, data.colors.color11, - data.colors.color12, data.colors.color13, data.colors.color14, data.colors.color15, - }, - } - end - end - config.default_cursor_style = 'BlinkingBar' config.enable_scroll_bar = true config.font_size = 10 From 5f32cfeaf3657b020458cf8b3c2f1061da916d30 Mon Sep 17 00:00:00 2001 From: tsandrini Date: Thu, 5 Feb 2026 14:11:48 +0100 Subject: [PATCH 05/18] feat(hosts): update dms + niri related DE things --- flake-parts/hosts/flatbundle/default.nix | 14 +- .../flatbundle/hardware-configuration.nix | 16 +- flake-parts/hosts/jetbundle/default.nix | 12 +- .../jetbundle/hardware-configuration.nix | 1 + .../profiles/graphical-dms-niri.nix | 351 +++++++++--------- .../modules/home-manager/programs/dsearch.nix | 15 + .../home-manager/programs/shells/fish.nix | 10 +- .../programs/terminals/wezterm.nix | 14 +- .../home-manager/services/pywalfox-native.nix | 65 +++- .../nixos/profiles/graphical-dms-niri.nix | 83 ++--- .../profiles/packages-graphical-extra.nix | 11 +- flake-parts/modules/nixvim/default.nix | 4 +- .../modules/nixvim/profiles/graphical.nix | 2 +- flake-parts/modules/nixvim/profiles/ide.nix | 27 ++ 14 files changed, 373 insertions(+), 252 deletions(-) diff --git a/flake-parts/hosts/flatbundle/default.nix b/flake-parts/hosts/flatbundle/default.nix index 1d38f89a..f565d17f 100644 --- a/flake-parts/hosts/flatbundle/default.nix +++ b/flake-parts/hosts/flatbundle/default.nix @@ -43,11 +43,13 @@ in # | ADDITIONAL SYSTEM PACKAGES | # ------------------------------ environment.systemPackages = [ - pkgs.libva-utils - pkgs.docker-compose - pkgs.wireguard-tools - pkgs.claude-code - pkgs.codex + pkgs.libva-utils # Collection of utilities and examples for VA-API + pkgs.docker-compose # Docker CLI plugin to define and run multi-container applications with Docker + pkgs.wireguard-tools # Tools for the WireGuard secure network tunnel + pkgs.claude-code # Agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster + pkgs.codex # Lightweight coding agent that runs in your terminal + pkgs.bitwarden-desktop # Secure and free password manager for all of your devices + pkgs.bitwarden-cli # Secure and free password manager for all of your devices ]; # --------------------- @@ -89,6 +91,7 @@ in # nix-mineral.enable = true; programs.nh.flake = "/home/tsandrini/ProjectBundle/tsandrini/tensorfiles"; + programs.nh.clean.enable = false; # NOTE We have enough space buddy programs.fish.enable = true; users.defaultUserShell = pkgs.bash; @@ -154,6 +157,7 @@ in tensorfiles.hm = { profiles.graphical-dms-niri.enable = true; programs.pywal.enable = true; + services.pywalfox-native.enable = true; profiles.accounts.tsandrini.enable = true; security.agenix.enable = true; diff --git a/flake-parts/hosts/flatbundle/hardware-configuration.nix b/flake-parts/hosts/flatbundle/hardware-configuration.nix index 68ed2d31..ba3171b4 100644 --- a/flake-parts/hosts/flatbundle/hardware-configuration.nix +++ b/flake-parts/hosts/flatbundle/hardware-configuration.nix @@ -21,7 +21,9 @@ { imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; - environment.systemPackages = [ pkgs.libva-utils ]; + environment.systemPackages = [ + pkgs.libva-utils + ]; networking.useDHCP = lib.mkDefault true; @@ -68,11 +70,21 @@ enable = true; # cpuFreqGovernor = "schedutil"; }; - services.power-profiles-daemon.enable = true; programs.gamemode.enable = true; services.fwupd.enable = true; + services.fprintd = { + enable = true; + # tod.enable = true; + # tod.driver = pkgs.libfprint-2-tod1-vfs0090; + }; + + services.fstrim = { + enable = true; + interval = "weekly"; # the default + }; + boot = { loader = { timeout = 1; diff --git a/flake-parts/hosts/jetbundle/default.nix b/flake-parts/hosts/jetbundle/default.nix index 4b77ba39..39fd5477 100644 --- a/flake-parts/hosts/jetbundle/default.nix +++ b/flake-parts/hosts/jetbundle/default.nix @@ -48,11 +48,13 @@ in # | ADDITIONAL SYSTEM PACKAGES | # ------------------------------ environment.systemPackages = [ - pkgs.libva-utils - pkgs.docker-compose - pkgs.wireguard-tools - pkgs.claude-code - pkgs.codex + pkgs.libva-utils # Collection of utilities and examples for VA-API + pkgs.docker-compose # Docker CLI plugin to define and run multi-container applications with Docker + pkgs.wireguard-tools # Tools for the WireGuard secure network tunnel + pkgs.claude-code # Agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster + pkgs.codex # Lightweight coding agent that runs in your terminal + pkgs.bitwarden-desktop # Secure and free password manager for all of your devices + pkgs.bitwarden-cli # Secure and free password manager for all of your devices ]; # --------------------- diff --git a/flake-parts/hosts/jetbundle/hardware-configuration.nix b/flake-parts/hosts/jetbundle/hardware-configuration.nix index d73d9d6a..408c06ea 100644 --- a/flake-parts/hosts/jetbundle/hardware-configuration.nix +++ b/flake-parts/hosts/jetbundle/hardware-configuration.nix @@ -47,6 +47,7 @@ # cpuFreqGovernor = "schedutil"; }; services.power-profiles-daemon.enable = true; + services.upower.enable = true; programs.gamemode.enable = true; services.fwupd.enable = true; diff --git a/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix b/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix index 7529bc93..3a02261c 100644 --- a/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix +++ b/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix @@ -61,99 +61,6 @@ let fi ''; - dmsToWal = pkgs.writeShellApplication { - name = "dms-to-wal"; - runtimeInputs = [ - pkgs.jq - pkgs.coreutils - ]; - text = '' - set -euo pipefail - - CACHE_HOME="''${XDG_CACHE_HOME:-$HOME/.cache}" - DMS_COLORS="$CACHE_HOME/DankMaterialShell/dms-colors.json" - DMS_CACHE="$CACHE_HOME/DankMaterialShell/cache.json" - - WAL_DIR="$CACHE_HOME/wal" - COLORS_JSON="$WAL_DIR/colors.json" - PYWALFOX_JSON="$WAL_DIR/dank-pywalfox.json" - SEQUENCES="$WAL_DIR/sequences" - - mkdir -p "$WAL_DIR" - - if [[ ! -r "$DMS_COLORS" ]]; then - echo "dms-to-wal: missing DMS colors at $DMS_COLORS" >&2 - exit 0 - fi - - # Try to discover the current wallpaper path (for pywalfox compatibility). - # Best-effort: if not found, set to empty string (still better than missing key). - wallpaper="" - if [[ -r "$DMS_CACHE" ]]; then - wallpaper="$(jq -r ' - .wallpaper - // .currentWallpaper - // .current_wallpaper - // .lastWallpaper - // empty - ' "$DMS_CACHE" 2>/dev/null || true)" - fi - - # Generate a pywal-ish colors.json from DMS palette - # Important: capture original input as $dms so reduce doesn't lose access. - jq --arg wallpaper "$wallpaper" ' - . as $dms - | def c($i): ($dms.dank16["color"+($i|tostring)].default // $dms.dank16["color"+($i|tostring)].dark // null); - - { - wallpaper: $wallpaper, - special: { - background: $dms.colors.dark.background, - foreground: $dms.colors.dark.on_background, - cursor: $dms.colors.dark.on_background - }, - colors: (reduce range(0;16) as $i ({}; . + {("color"+($i|tostring)): c($i)})) - } - ' "$DMS_COLORS" > "$COLORS_JSON" - - # Pywalfox: DMS doc expects this to exist; many setups read colors.json directly too. - cp -f "$COLORS_JSON" "$PYWALFOX_JSON" - - # Terminal escape sequences (OSC). One-line blob, like pywal produces. - # Use ST terminator (\033\) like your example. - bg="$(jq -r '.special.background' "$COLORS_JSON")" - fg="$(jq -r '.special.foreground' "$COLORS_JSON")" - cur="$(jq -r '.special.cursor' "$COLORS_JSON")" - - seq="" - - # 16 base colors - for i in $(seq 0 15); do - c="$(jq -r --arg k "color$i" '.colors[$k]' "$COLORS_JSON")" - seq="''${seq}\033]4;''${i};''${c}\033\\" - done - - # Foreground/background/cursor - seq="''${seq}\033]10;''${fg}\033\\" - seq="''${seq}\033]11;''${bg}\033\\" - seq="''${seq}\033]12;''${cur}\033\\" - - # A few extra OSC codes that pywal often includes (safe; helps some apps) - # 13: mouse fg, 17: highlight bg, 19: highlight fg (values reused like typical wal) - seq="''${seq}\033]13;''${fg}\033\\" - seq="''${seq}\033]17;''${fg}\033\\" - seq="''${seq}\033]19;''${bg}\033\\" - - # 232..255 ramp endpoints (pywal writes a couple; harmless) - seq="''${seq}\033]4;232;''${bg}\033\\" - seq="''${seq}\033]4;256;''${fg}\033\\" - - # Some builds also emit this final code; keep it for compatibility - seq="''${seq}\033]708;''${bg}\033\\" - - printf '%b' "$seq" > "$SEQUENCES" - ''; - }; in { options.tensorfiles.hm.profiles.graphical-dms-niri = { @@ -194,66 +101,75 @@ in }; }; - # Run once each login (so ~/.cache/wal exists even before the first palette change), - # and also whenever DMS regenerates dms-colors.json. - systemd.user.services.dms-to-wal = { - Unit = { - Description = "Generate pywal artifacts from DMS palette"; - # optional but nice: - After = [ "graphical-session.target" ]; - PartOf = [ "graphical-session.target" ]; - }; - Service = { - Type = "oneshot"; - ExecStart = "${lib.getExe dmsToWal}"; - }; - Install = { - WantedBy = [ "default.target" ]; - }; - }; - - systemd.user.paths.dms-to-wal = { - Unit = { - Description = "Watch DMS palette and refresh pywal artifacts"; - }; - Path = { - PathChanged = "%h/.cache/DankMaterialShell/dms-colors.json"; - Unit = "dms-to-wal.service"; - }; - Install = { - WantedBy = [ "default.target" ]; - }; - }; - home.packages = [ + # --- Fonts --- pkgs.neovide # This is a simple graphical user interface for Neovim - pkgs.nerd-fonts.jetbrains-mono - pkgs.nerd-fonts.symbols-only - pkgs.noto-fonts - pkgs.noto-fonts-color-emoji - pkgs.hicolor-icon-theme + pkgs.nerd-fonts.jetbrains-mono # Nerd Fonts: JetBrains officially created font for developers + pkgs.nerd-fonts.symbols-only # Nerd Fonts: Just the Nerd Font Icons. I.e Symbol font only + pkgs.noto-fonts # Beautiful and free fonts for many languages + pkgs.noto-fonts-color-emoji # Color emoji font + + # --- GTK Stuff & Themes --- + pkgs.hicolor-icon-theme # Default fallback theme used by implementations of the icon theme specification pkgs.adwaita-icon-theme - pkgs.papirus-icon-theme - pkgs.kdePackages.breeze-icons - - pkgs.nautilus - pkgs.gvfs - pkgs.udiskie - pkgs.file-roller - pkgs.loupe - pkgs.mpv - pkgs.vlc - pkgs.pavucontrol - pkgs.blueman - pkgs.kdePackages.qt6ct - - pkgs.kdePackages.gwenview - pkgs.qimgv - pkgs.imv - pkgs.kdePackages.ark - pkgs.kdePackages.dolphin - - pkgs.matugen + pkgs.papirus-icon-theme # Pixel perfect icon theme for Linux + pkgs.kdePackages.breeze-icons # Breeze icon theme. + + # --- GNOME apps --- + pkgs.nautilus # File manager for GNOME + pkgs.gvfs # Virtual Filesystem support library + pkgs.udiskie # Removable disk automounter for udisks + pkgs.file-roller # Archive manager for the GNOME desktop environment + pkgs.loupe # Simple image viewer application written with GTK4 and Rust + pkgs.evince # GNOME's document viewer + pkgs.tumbler # D-Bus thumbnailer service + pkgs.sushi # Quick previewer for Nautilus + pkgs.ffmpegthumbnailer # Lightweight video thumbnailer + pkgs.shared-mime-info # Database of common MIME types + pkgs.desktop-file-utils # Command line utilities for working with .desktop files + pkgs.gnome-disk-utility # Udisks graphical front-end + pkgs.totem # Movie player for the GNOME desktop based on GStreamer + pkgs.dconf-editor # GSettings editor for GNOME + pkgs.kooha # Elegantly record your screen + pkgs.gnome-calculator # Application that solves mathematical equations and is suitable as a default application in a Desktop environment + pkgs.snapshot # Take pictures and videos on your computer, tablet, or phone + pkgs.baobab # Graphical application to analyse disk usage in any GNOME environment + pkgs.gnome-connections # Remote desktop client for the GNOME desktop environment + pkgs.gnome-clocks # Simple and elegant clock application for GNOME + pkgs.gnome-console # Simple user-friendly terminal emulator for the GNOME desktop + pkgs.gnome-characters # Simple utility application to find and insert unusual characters + pkgs.gnome-logs # Log viewer for the systemd journal + pkgs.gnome-font-viewer # Program that can preview fonts and create thumbnails for fonts + pkgs.gnome-maps # Map application for GNOME 3 + pkgs.gnome-music # Music player and management application for the GNOME desktop environment + pkgs.gnome-weather # Access current weather conditions and forecasts + # pkgs.constrict # Compresses your videos to your chosen file size + pkgs.gnome-decoder # Scan and generate QR codes + pkgs.curtail # Simple & useful image compressor + pkgs.deja-dup # Simple backup tool + pkgs.impression # Straight-forward and modern application to create bootable drives + pkgs.tuba # Browse the Fediverse + pkgs.wike # Wikipedia Reader for the GNOME Desktop + pkgs.lorem # Generate placeholder text + + # --- KDE apps --- + # NOTE: I have these mostly just for kdeconnect to work and be able to mount the drives + pkgs.kdePackages.qt6ct # Qt6 Configuration Tool + pkgs.kdePackages.ark # File archiver by KDE + pkgs.kdePackages.kio # KIO + pkgs.kdePackages.kio-extras # Additional components to increase the functionality of KIO + pkgs.kdePackages.kio-fuse # FUSE Interface for KIO + pkgs.kdePackages.dolphin # File manager by KDE + # pkgs.kdePackages.gwenview # Image viewer by KDE + # pkgs.qimgv # Qt6 image viewer with optional video support + # pkgs.imv # Command line image viewer for tiling window managers + + # --- General apps --- + pkgs.mpv # General-purpose media player, fork of MPlayer and mplayer2 + pkgs.vlc # Cross-platform media player and streaming server + pkgs.pavucontrol # PulseAudio Volume Control + pkgs.blueman # GTK-based Bluetooth Manager + pkgs.matugen # Material you color generation tool toggleEdp ] @@ -289,7 +205,7 @@ in fonts.fontconfig.enable = _ true; gtk = { - enable = true; + enable = _ true; theme = { name = "adw-gtk3-dark"; @@ -302,12 +218,24 @@ in }; }; - programs.niri = { - package = pkgs.niri-unstable; + services.udiskie = { + enable = _ true; + automount = _ false; + notify = _ true; + tray = _ "auto"; + }; + + services.polkit-gnome.enable = _ true; + services.kdeconnect = { + enable = _ true; + indicator = _ true; + }; + + programs.niri = { + package = _ pkgs.niri-unstable; settings = { prefer-no-csd = true; - screenshot-path = "~/FiberBundle/Images/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"; # TODO workspaces = { "01" = { name = "1"; @@ -347,10 +275,6 @@ in }; }; - spawn-at-startup = [ - { argv = [ "kdeconnect-indicator" ]; } - ]; - binds = let a = config.lib.niri.actions; @@ -373,25 +297,33 @@ in "${mod}+K".action = a.focus-window-up; "${mod}+L".action = a.focus-column-right; - "${mod}+Left".action = a.focus-column-left; - "${mod}+Down".action = a.focus-window-down; - "${mod}+Up".action = a.focus-window-up; - "${mod}+Right".action = a.focus-column-right; + "${mod}+MouseBack".action = a.focus-column-left; + "${mod}+MouseForward".action = a.focus-column-right; # --- Workspaces --- "${mod}+U".action = a.focus-workspace-down; "${mod}+I".action = a.focus-workspace-up; + "${mod}+WheelScrollDown" = { + action = a.focus-workspace-down; + cooldown-ms = 150; + }; + "${mod}+WheelScrollUp" = { + action = a.focus-workspace-up; + cooldown-ms = 150; + }; + # --- Moving stuff --- "${mod}+Shift+H".action = a.move-column-left; "${mod}+Shift+J".action = a.move-window-down; "${mod}+Shift+K".action = a.move-window-up; "${mod}+Shift+L".action = a.move-column-right; - "${mod}+Shift+Left".action = a.move-column-left; - "${mod}+Shift+Down".action = a.move-window-down; - "${mod}+Shift+Up".action = a.move-window-up; - "${mod}+Shift+Right".action = a.move-column-right; + # --- Resizing windowws ---- + "${mod}+Left".action = a.set-column-width "-10%"; + "${mod}+Right".action = a.set-column-width "+10%"; + "${mod}+Up".action = a.set-window-height "-10%"; + "${mod}+Down".action = a.set-window-height "+10%"; # Workspaces 1..9 "${mod}+1".action = a.focus-workspace 1; @@ -404,7 +336,11 @@ in "${mod}+8".action = a.focus-workspace 8; "${mod}+9".action = a.focus-workspace 9; + # "${mod}+F".action = a.maximize-column; "${mod}+F".action = a.maximize-column; + "${mod}+T".action = a.toggle-window-floating; + "${mod}+R".action = a.switch-preset-column-width; + "${mod}+Comma".action = a.consume-or-expel-window-right; # --- Apps --- "${mod}+Space".action = dms [ @@ -419,10 +355,22 @@ in "clipboard" "toggle" ]; + "${mod}+M".action = dms [ + "processlist" + "toggle" + ]; + "Ctrl+Alt+Q".action = dms [ + "powermenu" + "toggle" + ]; "${mod}+P".action = dms [ "notepad" "toggle" ]; + "${mod}+Shift+Q".action = dms [ + "" + "toggle" + ]; "${mod}+N".action = dms [ "notifications" "toggle" @@ -522,15 +470,82 @@ in }; }; + # TODO move this elsewhere + systemd.user.tmpfiles.rules = [ + "d %h/.cache/wal 0700 - - -" + "L+ %h/.cache/wal/colors.json - - - - %h/.cache/wal/dank-pywalfox.json" + ]; + + xdg.mimeApps.defaultApplications = { + "application/pdf" = [ "org.gnome.Evince.desktop" ]; + "application/x-pdf" = [ "org.gnome.Evince.desktop" ]; + + "image/png" = [ "org.gnome.Loupe.desktop" ]; + "image/jpeg" = [ "org.gnome.Loupe.desktop" ]; + "image/webp" = [ "org.gnome.Loupe.desktop" ]; + "image/gif" = [ "org.gnome.Loupe.desktop" ]; + "image/svg+xml" = [ "org.gnome.Loupe.desktop" ]; + "image/bmp" = [ "org.gnome.Loupe.desktop" ]; + "image/tiff" = [ "org.gnome.Loupe.desktop" ]; + + "application/zip" = [ "org.gnome.FileRoller.desktop" ]; + "application/x-tar" = [ "org.gnome.FileRoller.desktop" ]; + "application/x-7z-compressed" = [ "org.gnome.FileRoller.desktop" ]; + "application/x-rar" = [ "org.gnome.FileRoller.desktop" ]; + + "video/mp4" = [ "org.gnome.Totem.desktop" ]; + "video/x-matroska" = [ "org.gnome.Totem.desktop" ]; # mkv + "video/webm" = [ "org.gnome.Totem.desktop" ]; + "video/quicktime" = [ "org.gnome.Totem.desktop" ]; # mov + "video/x-msvideo" = [ "org.gnome.Totem.desktop" ]; # avi + "video/x-ms-wmv" = [ "org.gnome.Totem.desktop" ]; # wmv + "video/mpeg" = [ "org.gnome.Totem.desktop" ]; # mpg/mpeg + "video/ogg" = [ "org.gnome.Totem.desktop" ]; + "video/x-flv" = [ "org.gnome.Totem.desktop" ]; + + "application/gzip" = [ "org.gnome.FileRoller.desktop" ]; + "application/x-bzip2" = [ "org.gnome.FileRoller.desktop" ]; + "application/x-xz" = [ "org.gnome.FileRoller.desktop" ]; + "application/x-compressed-tar" = [ "org.gnome.FileRoller.desktop" ]; + "application/x-zip-compressed" = [ "org.gnome.FileRoller.desktop" ]; + + "text/html" = [ "firefox.desktop" ]; + "application/xhtml+xml" = [ "firefox.desktop" ]; + "x-scheme-handler/http" = [ "firefox.desktop" ]; + "x-scheme-handler/https" = [ "firefox.desktop" ]; + "x-scheme-handler/about" = [ "firefox.desktop" ]; + "x-scheme-handler/unknown" = [ "firefox.desktop" ]; + + "inode/directory" = [ "org.gnome.Nautilus.desktop" ]; + + # # Text / code -> your Neovim desktop entry + "text/plain" = [ "neovide.desktop" ]; + "text/markdown" = [ "neovide.desktop" ]; + "application/json" = [ "neovide.desktop" ]; + "application/x-yaml" = [ "neovide.desktop" ]; + "text/x-python" = [ "neovide.desktop" ]; + "text/x-shellscript" = [ "neovide.desktop" ]; + "text/x-csrc" = [ "neovide.desktop" ]; + "text/x-chdr" = [ "neovide.desktop" ]; + "text/x-c++src" = [ "neovide.desktop" ]; + "text/x-c++hdr" = [ "neovide.desktop" ]; + "text/x-rust" = [ "neovide.desktop" ]; + "text/x-go" = [ "neovide.desktop" ]; + "text/x-toml" = [ "neovide.desktop" ]; + "text/x-nix" = [ "neovide.desktop" ]; + "application/xml" = [ "neovide.desktop" ]; + "text/xml" = [ "neovide.desktop" ]; + }; + programs.dank-material-shell = { enable = _ true; systemd = { - enable = _ false; + enable = _ true; restartIfChanged = _ true; }; niri = { - enableSpawn = _ true; + enableSpawn = _ false; enableKeybinds = _ false; includes = { enable = _ true; diff --git a/flake-parts/modules/home-manager/programs/dsearch.nix b/flake-parts/modules/home-manager/programs/dsearch.nix index 3dd9e55f..31a3ead4 100644 --- a/flake-parts/modules/home-manager/programs/dsearch.nix +++ b/flake-parts/modules/home-manager/programs/dsearch.nix @@ -83,6 +83,21 @@ in "target" ]; } + { + path = config.home.sessionVariables.DOWNLOADS_DIR; + max_depth = 3; + exclude_hidden = true; + exclude_dirs = [ + ".git" + "target" + "dist" + "node_modules" + ".direnv" + ".devenv" + "venv" + "target" + ]; + } { path = config.home.sessionVariables.MISC_DATA_DIR; max_depth = 6; diff --git a/flake-parts/modules/home-manager/programs/shells/fish.nix b/flake-parts/modules/home-manager/programs/shells/fish.nix index 8c51b2d8..a93f487f 100644 --- a/flake-parts/modules/home-manager/programs/shells/fish.nix +++ b/flake-parts/modules/home-manager/programs/shells/fish.nix @@ -193,11 +193,11 @@ in }) # |----------------------------------------------------------------------| # (mkIf ((isModuleLoadedAndEnabled config "tensorfiles.hm.programs.pywal") && cfg.pywal.enable) { - programs.fish.interactiveShellInit = mkBefore '' - # Import colorscheme from 'wal' asynchronously - set -l wal_seq (cat ${config.xdg.cacheHome}/wal/sequences)"" - echo -e $wal_seq & - ''; + # programs.fish.interactiveShellInit = mkBefore '' + # # Import colorscheme from 'wal' asynchronously + # set -l wal_seq (cat ${config.xdg.cacheHome}/wal/sequences)"" + # echo -e $wal_seq & + # ''; }) # |----------------------------------------------------------------------| # (mkIf cfg.shellAliases.catToBat { diff --git a/flake-parts/modules/home-manager/programs/terminals/wezterm.nix b/flake-parts/modules/home-manager/programs/terminals/wezterm.nix index cf97c303..a29acd88 100644 --- a/flake-parts/modules/home-manager/programs/terminals/wezterm.nix +++ b/flake-parts/modules/home-manager/programs/terminals/wezterm.nix @@ -55,8 +55,18 @@ in ${ if pywalCheck then # TODO '' - wezterm.add_to_config_reload_watch_list("~/.cache/wal") - config.color_scheme_dirs = {"~/.cache/wal"} + local home = wezterm.home_dir + local wal_dir = home .. "/.cache/wal" + + -- wezterm.add_to_config_reload_watch_list(wal_dir) + -- config.color_scheme_dirs = { wal_dir } + + local scheme_path = wezterm.config_dir .. "/colors/dank-theme.toml" + local colors, _meta = wezterm.color.load_scheme(scheme_path) + config.colors = colors + config.color_scheme = "dank-theme" + + wezterm.add_to_config_reload_watch_list(scheme_path) '' else "" diff --git a/flake-parts/modules/home-manager/services/pywalfox-native.nix b/flake-parts/modules/home-manager/services/pywalfox-native.nix index 8ffe3e4f..79aae745 100644 --- a/flake-parts/modules/home-manager/services/pywalfox-native.nix +++ b/flake-parts/modules/home-manager/services/pywalfox-native.nix @@ -20,32 +20,67 @@ ... }: let - inherit (lib) mkIf mkMerge mkEnableOption; - - # pywalfox-wrapper = pkgs.writeShellScriptBin "pywalfox-wrapper" '' - # ${pywalfox-native}/bin/pywalfox start - # ''; + inherit (lib) + mkIf + mkMerge + mkEnableOption + getExe + mkPackageOption + ; cfg = config.tensorfiles.hm.services.pywalfox-native; + + pywalfoxUpdateHandleSocket = pkgs.writeShellScript "pywalfox-update-handle-socket" '' + set -euo pipefail + + # TODO: Not sure why, but thunderbird creates a stale socket and the client + # is then unable to send the update commands to the native messaging hosts + # so I temporarily just delete it + sock=/tmp/pywalfox_socket + if [ ! -S "$sock" ]; then + rm -f $sock 2>/dev/null + fi + + exec ${getExe cfg.package} update + ''; in { options.tensorfiles.hm.services.pywalfox-native = { - enable = mkEnableOption '' - Enables NixOS module that configures/handles terminals.kitty colorscheme generator. - ''; + enable = mkEnableOption "Enable pywalfox-native helpers"; + package = mkPackageOption pkgs "pywalfox-native" { }; }; - # TODO config = mkIf cfg.enable (mkMerge [ - # |----------------------------------------------------------------------| # { - home.packages = with pkgs; [ pywalfox-native ]; + home.packages = [ cfg.package ]; + + systemd.user.services.pywalfox-update = { + Unit = { + Description = "Update Pywalfox theme"; + PartOf = [ "graphical-session.target" ]; + After = [ "graphical-session.target" ]; + }; + Service = { + Type = "oneshot"; + ExecStart = pywalfoxUpdateHandleSocket; + PrivateTmp = false; + }; + }; - # home.file.".mozilla/native-messaging-hosts/pywalfox.json".text = replaceStrings [ "" ] [ - # "${pywalfox-wrapper}/bin/pywalfox-wrapper" - # ] (readFile "${pywalfox-native}/lib/python3.11/site-packages/pywalfox/assets/manifest.json"); + systemd.user.paths.pywalfox-update = { + Unit = { + Description = "Run pywalfox update when wal colors.json changes"; + PartOf = [ "graphical-session.target" ]; + }; + Path = { + PathChanged = "%h/.cache/wal/colors.json"; + Unit = "pywalfox-update.service"; + }; + Install = { + WantedBy = [ "graphical-session.target" ]; + }; + }; } - # |----------------------------------------------------------------------| # ]); meta.maintainers = with localFlake.lib.maintainers; [ tsandrini ]; diff --git a/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix b/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix index 9b1816e0..6295989d 100644 --- a/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix +++ b/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix @@ -65,49 +65,17 @@ in wayland-utils # Wayland utilities (wayland-info) aha # ANSI HTML Adapter - # -- KDE PACKAGES -- - # kdePackages.ark # Graphical file compression/decompression utility - # haruna # Open source video player built with Qt/QML and libmpv - # kdePackages.kate # Advanced text editor - # kdePackages.kcalc # Scientific calculator - # kdiff3 # Compares and merges 2 or 3 files or directories - # krename # A powerful batch renamer for KDE - # krusader # Norton/Total Commander clone for KDE - # kdePackages.filelight # Disk usage statistics - # kdePackages.kfind # File search utility by KDE - # kdePackages.kweather - # # kdePackages.kweathercore - # kdePackages.quazip # Provides access to ZIP archives from Qt programs - # kdePackages.ksshaskpass - # kdePackages.accounts-qt # Qt library for accessing the online accounts database - # kdePackages.calendarsupport - # kdePackages.kaccounts-providers # Online account providers - # kdePackages.kaccounts-integration # Online accounts integration - # kdePackages.kdeplasma-addons - # kdePackages.plasma-browser-integration - # kdePackages.kaddressbook # KDE contact manager - # kdePackages.merkuro # A calendar application using Akonadi to sync with external services - # kdePackages.kcontacts # KContacts - Library for working with contact information - # kdePackages.kpeople # A library that provides access to all contacts and the people who hold them - # kdePackages.kompare # Graphical File Differences Tool - - # krita # A free and open source painting application - # kdePackages.kdenlive # Video editor - # kdePackages.kcolorpicker # Qt based Color Picker with popup menu - # kdePackages.kcolorchooser - # kdePackages.kolourpaint # Paint program - # NOTE KNotes is unmaintained upstream, - # kdePackages.knotes # Popup notes - # kdePackages.kalarm # Personal alarm scheduler - # kdePackages.kamoso # A simple and friendly program to use your camera - # kdePackages.kruler # Screen ruler - # kdePackages.kclock # Clock app for plasma mobile - # okteta # A hex editor - # kdePackages.elisa # A simple media player for KDE - # kdePackages.kmag # A small Linux utility to magnify a part of the screen - # kdePackages.itinerary + # -- DMS + NIRI stuff -- + i2c-tools # Set of I2C tools for Linux + seahorse # Application for managing encryption keys and passwords in the GnomeKeyring + # xwayland-satellite # Xwayland outside your Wayland compositor ]; + programs.xwayland = { + enable = _ true; + package = _ pkgs.xwayland-satellite; + }; + programs.dank-material-shell.greeter = { enable = _ true; configHome = _ "/home/tsandrini"; # TODO probably find a better way to do this @@ -115,37 +83,64 @@ in }; programs.ssh.startAgent = _ false; # NOTE: using gnome agent + # NOTE: It's required to have the niri executable in $PATH to populate + # the wayland-sessions for the dms-greeter. Niri itself will then + # load any configuration provided by HM without any issues, but we + # have to traverse from NixOS -> HM somehow. programs.niri = { enable = _ true; package = _ pkgs.niri-unstable; }; + services.accounts-daemon.enable = _ true; # Required to persist user info services.dbus.enable = _ true; security.polkit.enable = _ true; programs.dconf.enable = _ true; + services.udisks2.enable = _ true; # udisks2, a DBus service that allows applications to query and manipulate storage devices. + services.gvfs.enable = _ true; # GVfs, a userspace virtual filesystem. xdg.portal = { enable = _ true; extraPortals = [ - pkgs.xdg-desktop-portal-wlr - pkgs.xdg-desktop-portal-gtk + pkgs.xdg-desktop-portal-wlr # xdg-desktop-portal backend for wlroots + pkgs.xdg-desktop-portal-gtk # Desktop integration portals for sandboxed apps + pkgs.xdg-desktop-portal-gnome # Backend implementation for xdg-desktop-portal for the GNOME desktop environment + ]; + # pick wlr first, fall back to gtk for the misc stuff + config.common.default = [ + "gnome" + "wlr" ]; - config.common.default = "*"; }; environment.sessionVariables = { NIXOS_OZONE_WL = _ "1"; + QT_QPA_PLATFORMTHEME = _ "gtk3"; + XDG_CURRENT_DESKTOP = _ "niri"; + XDG_SESSION_DESKTOP = _ "niri"; #DISPLAY = ":0"; #QT_QPA_PLATFORM = "wayland"; }; + hardware.i2c.enable = _ true; # Required to control brightness of external monitors + + services.power-profiles-daemon.enable = _ true; + services.upower.enable = _ true; + + # Pass various secret management to gnome keyring and autounlock after login + services.gnome.gnome-keyring.enable = _ true; # provides Secret Service + keyring daemon + security.pam.services.greetd.enableGnomeKeyring = _ true; + security.pam.services.login.enableGnomeKeyring = _ true; + services.pcscd.enable = _ true; # needed for gpg pinentry + # AUDIO stuff services.pipewire = { enable = _ true; alsa.enable = _ true; pulse.enable = _ true; jack.enable = _ true; }; + security.rtkit.enable = _ true; # realtime audio scheduling programs.kdeconnect.enable = _ true; systemd.user.services.niri-flake-polkit.enable = _ false; diff --git a/flake-parts/modules/nixos/profiles/packages-graphical-extra.nix b/flake-parts/modules/nixos/profiles/packages-graphical-extra.nix index fc399de7..1d61cdf4 100644 --- a/flake-parts/modules/nixos/profiles/packages-graphical-extra.nix +++ b/flake-parts/modules/nixos/profiles/packages-graphical-extra.nix @@ -21,10 +21,10 @@ }: let inherit (lib) mkIf mkMerge mkEnableOption; - # inherit (localFlake.lib.modules) mkOverrideAtProfileLevel; + inherit (localFlake.lib.modules) mkOverrideAtProfileLevel; cfg = config.tensorfiles.profiles.packages-graphical-extra; - # _ = mkOverrideAtProfileLevel; + _ = mkOverrideAtProfileLevel; in { options.tensorfiles.profiles.packages-graphical-extra = { @@ -85,11 +85,14 @@ in # lapack # openblas with just the LAPACK C and FORTRAN ABI # github-desktop # GitHub Desktop - winbox4 # Graphical configuration utility for RouterOS-based devices + # winbox4 # Graphical configuration utility for RouterOS-based devices hoppscotch # Open source API development ecosystem ]; - programs.winbox.enable = true; + programs.winbox = { + enable = _ true; + package = _ pkgs.winbox4; + }; programs.nix-index-database.comma.enable = true; } # |----------------------------------------------------------------------| # diff --git a/flake-parts/modules/nixvim/default.nix b/flake-parts/modules/nixvim/default.nix index 2ee1ab85..142bf1a2 100644 --- a/flake-parts/modules/nixvim/default.nix +++ b/flake-parts/modules/nixvim/default.nix @@ -38,7 +38,9 @@ in profiles_base = importApply ./profiles/base.nix { inherit localFlake; }; profiles_minimal = importApply ./profiles/minimal.nix { inherit localFlake; }; profiles_graphical = importApply ./profiles/graphical.nix { inherit localFlake; }; - profiles_ide = importApply ./profiles/ide.nix { inherit localFlake; }; + profiles_ide = importApply ./profiles/ide.nix { + inherit localFlake; + }; plugins_git_neogit = importApply ./plugins/git/neogit.nix { inherit localFlake; }; diff --git a/flake-parts/modules/nixvim/profiles/graphical.nix b/flake-parts/modules/nixvim/profiles/graphical.nix index ae071f59..b0bb2b32 100644 --- a/flake-parts/modules/nixvim/profiles/graphical.nix +++ b/flake-parts/modules/nixvim/profiles/graphical.nix @@ -42,7 +42,7 @@ in plugins = { utils.markdown-preview.enable = _ true; - editor.obsidian.enable = _ true; + # editor.obsidian.enable = _ true; editor.leetcode.enable = _ true; # editor.image.enable = _ true; }; diff --git a/flake-parts/modules/nixvim/profiles/ide.nix b/flake-parts/modules/nixvim/profiles/ide.nix index 7b8e3f9e..21419007 100644 --- a/flake-parts/modules/nixvim/profiles/ide.nix +++ b/flake-parts/modules/nixvim/profiles/ide.nix @@ -60,6 +60,33 @@ in plugins.direnv.enable = _ true; plugins.crates.enable = _ true; + + colorschemes.nightfox.enable = false; + colorschemes.base16.enable = _ true; + + # TODO + extraConfigLuaPost = '' + local path = vim.fn.stdpath("config") .. "/lua/plugins/dankcolors.lua" + + local function apply_dms_theme() + local ok, spec = pcall(dofile, path) + if not ok then return end + + -- DMS writes a Lazy-style spec table; we just want to run its config() + local entry = spec and spec[1] + if entry and type(entry.config) == "function" then + pcall(entry.config) + end + end + + -- Run once after startup to ensure plugins are available + vim.api.nvim_create_autocmd("VimEnter", { + once = true, + callback = function() + vim.schedule(apply_dms_theme) + end, + }) + ''; } # |----------------------------------------------------------------------| # ]); From 2c6f43b72870815c30f1ed7427ca5530b28166ea Mon Sep 17 00:00:00 2001 From: tsandrini Date: Thu, 5 Feb 2026 14:16:42 +0100 Subject: [PATCH 06/18] feat(hosts/jetbundle): tweak host & pywal settings --- flake-parts/hosts/flatbundle/default.nix | 1 - flake-parts/hosts/jetbundle/default.nix | 7 ++----- flake-parts/hosts/jetbundle/hardware-configuration.nix | 4 ++++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/flake-parts/hosts/flatbundle/default.nix b/flake-parts/hosts/flatbundle/default.nix index f565d17f..6ee53b16 100644 --- a/flake-parts/hosts/flatbundle/default.nix +++ b/flake-parts/hosts/flatbundle/default.nix @@ -130,7 +130,6 @@ in }; services.tailscale.enable = true; - networking.wireguard.enable = true; networking.firewall = { allowedUDPPorts = [ diff --git a/flake-parts/hosts/jetbundle/default.nix b/flake-parts/hosts/jetbundle/default.nix index 39fd5477..cd7f87fc 100644 --- a/flake-parts/hosts/jetbundle/default.nix +++ b/flake-parts/hosts/jetbundle/default.nix @@ -69,7 +69,6 @@ in packages-graphical-extra.enable = true; }; - services.networking.ssh.enable = true; security.agenix.enable = true; # Use the `nh` garbage collect to also collect .direnv and XDG profiles @@ -96,10 +95,8 @@ in }; # nix-mineral.enable = true; - # TODO maybe use github:tsandrini/tensorfiles instead? programs.nh.flake = "/home/tsandrini/ProjectBundle/tsandrini/tensorfiles"; - # programs.shadow-client.forceDriver = "iHD"; programs.fish.enable = true; users.defaultUserShell = pkgs.bash; @@ -131,13 +128,12 @@ in secrets = [ "ipsec.d/ipsec.nm-l2tp.secrets" ]; }; - services.pcscd.enable = true; # needed for gpg pinentry - virtualisation.docker = { enable = true; autoPrune.enable = true; }; + services.tailscale.enable = true; networking.wireguard.enable = true; networking.firewall = { allowedUDPPorts = [ @@ -164,6 +160,7 @@ in tensorfiles.hm = { profiles.graphical-dms-niri.enable = true; programs.pywal.enable = true; + services.pywalfox-native.enable = true; profiles.accounts.tsandrini.enable = true; security.agenix.enable = true; diff --git a/flake-parts/hosts/jetbundle/hardware-configuration.nix b/flake-parts/hosts/jetbundle/hardware-configuration.nix index 408c06ea..d312f840 100644 --- a/flake-parts/hosts/jetbundle/hardware-configuration.nix +++ b/flake-parts/hosts/jetbundle/hardware-configuration.nix @@ -52,6 +52,10 @@ programs.gamemode.enable = true; services.fwupd.enable = true; + services.fprintd = { + enable = true; + }; + # Thinkpad x270 fingreprint reader # Unfortunately the official services.fprintd option doesn't work and any # custom tos drivers didn't work either. The only way to make it work was to From 1c40576397b3847b8b9ab5ff2abb1cd216120c38 Mon Sep 17 00:00:00 2001 From: tsandrini Date: Thu, 5 Feb 2026 21:42:43 +0100 Subject: [PATCH 07/18] feat(modules/hm/niri-flake): init module and reformat old dms-niri --- flake-parts/modules/home-manager/default.nix | 1 + .../profiles/graphical-dms-niri.nix | 293 +------------- .../home-manager/programs/niri-flake.nix | 379 ++++++++++++++++++ .../nixos/profiles/graphical-dms-niri.nix | 31 +- 4 files changed, 405 insertions(+), 299 deletions(-) create mode 100644 flake-parts/modules/home-manager/programs/niri-flake.nix diff --git a/flake-parts/modules/home-manager/default.nix b/flake-parts/modules/home-manager/default.nix index e1bf718c..94b2e008 100644 --- a/flake-parts/modules/home-manager/default.nix +++ b/flake-parts/modules/home-manager/default.nix @@ -69,6 +69,7 @@ in programs_delta = importApply ./programs/delta.nix { inherit localFlake; }; programs_dsearch = importApply ./programs/dsearch.nix { inherit localFlake inputs; }; programs_gpg = importApply ./programs/gpg.nix { inherit localFlake; }; + programs_niri-flake = importApply ./programs/niri-flake.nix { inherit localFlake; }; programs_newsboat = importApply ./programs/newsboat.nix { inherit localFlake; }; programs_pywal = importApply ./programs/pywal.nix { inherit localFlake; }; programs_shadow-nix = importApply ./programs/shadow-nix.nix { inherit localFlake inputs; }; diff --git a/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix b/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix index 3a02261c..af171ecd 100644 --- a/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix +++ b/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix @@ -33,34 +33,6 @@ let cfg = config.tensorfiles.hm.profiles.graphical-dms-niri; _ = mkOverrideAtHmProfileLevel; - toggleEdp = pkgs.writeShellScriptBin "toggle-edp" '' - set -euo pipefail - - EDP="eDP-1" - - # Extract the block for eDP-1 (from its "Output ..." header until the next "Output ..." or EOF) - block="$( - niri msg outputs \ - | awk -v edp="(''${EDP})" ' - $0 ~ "^Output " { - in_block = ($0 ~ edp) - } - in_block { print } - ' - )" - - # If we didn't find the block at all, bail out (or you could default to "on"). - if [ -z "$block" ]; then - exit 0 - fi - - if echo "$block" | grep -q "^[[:space:]]*Disabled[[:space:]]*$"; then - niri msg output "$EDP" on - else - niri msg output "$EDP" off - fi - ''; - in { options.tensorfiles.hm.profiles.graphical-dms-niri = { @@ -79,7 +51,7 @@ in imports = [ inputs.dms.homeModules.dank-material-shell - inputs.dms.homeModules.niri + # inputs.dms.homeModules.niri ]; config = mkIf cfg.enable (mkMerge [ @@ -97,6 +69,11 @@ in thunderbird.enable = _ true; + niri-flake = { + enable = _ true; + binds.dms.enable = _ true; + binds.flameshot.enable = _ true; + }; dsearch.enable = _ true; }; }; @@ -171,23 +148,11 @@ in pkgs.blueman # GTK-based Bluetooth Manager pkgs.matugen # Material you color generation tool - toggleEdp + # pkgs.python3Packages.aiohttp-oauthlib # NOTE: required for calendar integration + ] ++ (optional cfg.include-nvim localFlake.packages.${system}.nvim-ide-config); - services.flameshot = { - enable = _ true; - package = pkgs.flameshot.override { - enableWlrSupport = true; - }; - settings = { - General = { - showStartupLaunchMessage = false; - useGrimAdapter = true; - }; - }; - }; - home.shellAliases = { "graphical-nvim" = _ (getExe localFlake.packages.${system}.nvim-graphical-config); "ide-nvim" = _ (getExe localFlake.packages.${system}.nvim-ide-config); @@ -232,244 +197,6 @@ in indicator = _ true; }; - programs.niri = { - package = _ pkgs.niri-unstable; - settings = { - prefer-no-csd = true; - workspaces = { - "01" = { - name = "1"; - }; - "02" = { - name = "2"; - }; - "03" = { - name = "3"; - }; - "04" = { - name = "4"; - }; - "05" = { - name = "5"; - }; - "06" = { - name = "6"; - }; - "07" = { - name = "7"; - }; - "08" = { - name = "8"; - }; - }; - - input = { - keyboard = { - xkb = { - layout = "us,cz"; - variant = ",qwerty"; - options = "grp:alt_caps_toggle"; - }; - - track-layout = "global"; - }; - }; - - binds = - let - a = config.lib.niri.actions; - mod = "Mod"; - dms = - cmd: - a.spawn ( - [ - "dms" - "ipc" - "call" - ] - ++ cmd - ); - in - { - # --- Columns --- - "${mod}+H".action = a.focus-column-left; - "${mod}+J".action = a.focus-window-down; - "${mod}+K".action = a.focus-window-up; - "${mod}+L".action = a.focus-column-right; - - "${mod}+MouseBack".action = a.focus-column-left; - "${mod}+MouseForward".action = a.focus-column-right; - - # --- Workspaces --- - "${mod}+U".action = a.focus-workspace-down; - "${mod}+I".action = a.focus-workspace-up; - - "${mod}+WheelScrollDown" = { - action = a.focus-workspace-down; - cooldown-ms = 150; - }; - "${mod}+WheelScrollUp" = { - action = a.focus-workspace-up; - cooldown-ms = 150; - }; - - # --- Moving stuff --- - "${mod}+Shift+H".action = a.move-column-left; - "${mod}+Shift+J".action = a.move-window-down; - "${mod}+Shift+K".action = a.move-window-up; - "${mod}+Shift+L".action = a.move-column-right; - - # --- Resizing windowws ---- - "${mod}+Left".action = a.set-column-width "-10%"; - "${mod}+Right".action = a.set-column-width "+10%"; - "${mod}+Up".action = a.set-window-height "-10%"; - "${mod}+Down".action = a.set-window-height "+10%"; - - # Workspaces 1..9 - "${mod}+1".action = a.focus-workspace 1; - "${mod}+2".action = a.focus-workspace 2; - "${mod}+3".action = a.focus-workspace 3; - "${mod}+4".action = a.focus-workspace 4; - "${mod}+5".action = a.focus-workspace 5; - "${mod}+6".action = a.focus-workspace 6; - "${mod}+7".action = a.focus-workspace 7; - "${mod}+8".action = a.focus-workspace 8; - "${mod}+9".action = a.focus-workspace 9; - - # "${mod}+F".action = a.maximize-column; - "${mod}+F".action = a.maximize-column; - "${mod}+T".action = a.toggle-window-floating; - "${mod}+R".action = a.switch-preset-column-width; - "${mod}+Comma".action = a.consume-or-expel-window-right; - - # --- Apps --- - "${mod}+Space".action = dms [ - "spotlight" - "toggle" - ]; - "${mod}+Return".action = a.spawn config.home.sessionVariables.TERMINAL; - "${mod}+Tab".action = a.focus-workspace-previous; - - # DMS toggles - "${mod}+V".action = dms [ - "clipboard" - "toggle" - ]; - "${mod}+M".action = dms [ - "processlist" - "toggle" - ]; - "Ctrl+Alt+Q".action = dms [ - "powermenu" - "toggle" - ]; - "${mod}+P".action = dms [ - "notepad" - "toggle" - ]; - "${mod}+Shift+Q".action = dms [ - "" - "toggle" - ]; - "${mod}+N".action = dms [ - "notifications" - "toggle" - ]; - "Ctrl+Alt+L".action = dms [ - "lock" - "lock" - ]; - - "${mod}+Q".action = a.close-window; - "${mod}+W".action = a.toggle-overview; - - "Print".action = a.spawn [ - "flameshot" - "gui" - ]; - "Ctrl+Print".action = a.spawn [ - "flameshot" - "gui" - ]; - "Alt+Print".action = a.spawn [ - "flameshot" - "gui" - ]; - - # --- Media keys via DMS IPC --- - "XF86AudioRaiseVolume" = { - action = dms [ - "audio" - "increment" - "5" - ]; - allow-when-locked = true; - }; - "XF86AudioLowerVolume" = { - action = dms [ - "audio" - "decrement" - "5" - ]; - allow-when-locked = true; - }; - "XF86AudioMute" = { - action = dms [ - "audio" - "mute" - ]; - allow-when-locked = true; - }; - "XF86AudioMicMute" = { - action = dms [ - "audio" - "micmute" - ]; - allow-when-locked = true; - }; - - # Media playback via DMS (MPRIS) - "XF86AudioPlay".action = dms [ - "mpris" - "playPause" - ]; - "XF86AudioPause".action = dms [ - "mpris" - "pause" - ]; - "XF86AudioNext".action = dms [ - "mpris" - "next" - ]; - "XF86AudioPrev".action = dms [ - "mpris" - "previous" - ]; - "XF86AudioStop".action = dms [ - "mpris" - "stop" - ]; - - "XF86MonBrightnessUp".action = dms [ - "brightness" - "increment" - "10" - "" - ]; - "XF86MonBrightnessDown".action = dms [ - "brightness" - "decrement" - "10" - "" - ]; - - # NOTE: just a quickfix when I need to leave and just pull cords out - "XF86Display".action = a.spawn [ "toggle-edp" ]; - "Mod+F7".action = a.spawn [ "toggle-edp" ]; - }; - }; - }; - # TODO move this elsewhere systemd.user.tmpfiles.rules = [ "d %h/.cache/wal 0700 - - -" @@ -540,12 +267,12 @@ in programs.dank-material-shell = { enable = _ true; systemd = { - enable = _ true; + enable = _ false; restartIfChanged = _ true; }; niri = { - enableSpawn = _ false; + enableSpawn = _ true; enableKeybinds = _ false; includes = { enable = _ true; diff --git a/flake-parts/modules/home-manager/programs/niri-flake.nix b/flake-parts/modules/home-manager/programs/niri-flake.nix new file mode 100644 index 00000000..81eeef37 --- /dev/null +++ b/flake-parts/modules/home-manager/programs/niri-flake.nix @@ -0,0 +1,379 @@ +# --- flake-parts/modules/home-manager/programs/niri-flake.nix +# +# Author: tsandrini +# URL: https://github.com/tsandrini/tensorfiles +# License: MIT +# +# 888 .d888 d8b 888 +# 888 d88P" Y8P 888 +# 888 888 888 +# 888888 .d88b. 88888b. .d8888b .d88b. 888d888 888888 888 888 .d88b. .d8888b +# 888 d8P Y8b 888 "88b 88K d88""88b 888P" 888 888 888 d8P Y8b 88K +# 888 88888888 888 888 "Y8888b. 888 888 888 888 888 888 88888888 "Y8888b. +# Y88b. Y8b. 888 888 X88 Y88..88P 888 888 888 888 Y8b. X88 +# "Y888 "Y8888 888 888 88888P' "Y88P" 888 888 888 888 "Y8888 88888P' +{ localFlake }: +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib) + mkIf + mkMerge + mkEnableOption + mkOption + types + ; + inherit (localFlake.lib.modules) mkOverrideAtHmModuleLevel; + + cfg = config.tensorfiles.hm.programs.niri-flake; + _ = mkOverrideAtHmModuleLevel; + + toggleEdp = pkgs.writeShellScriptBin "toggle-edp" '' + set -euo pipefail + + EDP="eDP-1" + + # Extract the block for eDP-1 (from its "Output ..." header until the next "Output ..." or EOF) + block="$( + niri msg outputs \ + | awk -v edp="(''${EDP})" ' + $0 ~ "^Output " { + in_block = ($0 ~ edp) + } + in_block { print } + ' + )" + + # If we didn't find the block at all, bail out (or you could default to "on"). + if [ -z "$block" ]; then + exit 0 + fi + + if echo "$block" | grep -q "^[[:space:]]*Disabled[[:space:]]*$"; then + niri msg output "$EDP" on + else + niri msg output "$EDP" off + fi + ''; +in +{ + options.tensorfiles.hm.programs.niri-flake = { + enable = mkEnableOption '' + TODO + ''; + + binds = { + mod = mkOption { + type = types.str; + default = "Mod"; + description = "Default modkey to be used"; + }; + + dms = { + enable = mkEnableOption "Enables various default DMS keybinds"; + }; + + flameshot = { + enable = mkEnableOption "Enables flameshot as the screenshot backend for niri"; + }; + }; + }; + + imports = [ + # TODO: This is problematic, we would ideally import `inputs.niri.homeModules.niri` + # however, `inputs.dms.homeModules.niri` also includes this module and that needs + # to be imported as well, importing both leads to conflict so this leads us + # with only a single option to import only `inputs.dms.homeModules.niri`, but + # we logically can't do that here => we import nothing 💀💀💀 + + # inputs.niri.homeModules.niri + ]; + + config = mkIf cfg.enable (mkMerge [ + # |----------------------------------------------------------------------| # + { + home.packages = [ toggleEdp ]; + + programs.niri = { + package = _ pkgs.niri-unstable; + settings = { + prefer-no-csd = _ true; + workspaces = { + "01" = { + name = _ "1"; + }; + "02" = { + name = _ "2"; + }; + "03" = { + name = _ "3"; + }; + "04" = { + name = _ "4"; + }; + "05" = { + name = _ "5"; + }; + "06" = { + name = _ "6"; + }; + "07" = { + name = _ "7"; + }; + "08" = { + name = _ "8"; + }; + }; + + input = { + keyboard = { + xkb = { + layout = _ "us,cz"; + variant = _ ",qwerty"; + options = _ "grp:alt_caps_toggle"; + }; + + track-layout = _ "global"; + }; + }; + + binds = + let + a = config.lib.niri.actions; + in + { + # --- Columns --- + "${cfg.binds.mod}+H".action = _ a.focus-column-left; + "${cfg.binds.mod}+J".action = _ a.focus-window-down; + "${cfg.binds.mod}+K".action = _ a.focus-window-up; + "${cfg.binds.mod}+L".action = _ a.focus-column-right; + + "${cfg.binds.mod}+MouseBack".action = _ a.focus-column-left; + "${cfg.binds.mod}+MouseForward".action = _ a.focus-column-right; + + # --- Workspaces --- + "${cfg.binds.mod}+U".action = _ a.focus-workspace-down; + "${cfg.binds.mod}+I".action = _ a.focus-workspace-up; + + "${cfg.binds.mod}+WheelScrollDown" = { + action = _ a.focus-workspace-down; + cooldown-ms = _ 150; + }; + "${cfg.binds.mod}+WheelScrollUp" = { + action = _ a.focus-workspace-up; + cooldown-ms = _ 150; + }; + + # --- Moving stuff --- + "${cfg.binds.mod}+Shift+H".action = _ a.move-column-left; + "${cfg.binds.mod}+Shift+J".action = _ a.move-window-down; + "${cfg.binds.mod}+Shift+K".action = _ a.move-window-up; + "${cfg.binds.mod}+Shift+L".action = _ a.move-column-right; + + # --- Resizing windowws ---- + "${cfg.binds.mod}+Left".action = _ (a.set-column-width "-10%"); + "${cfg.binds.mod}+Right".action = _ (a.set-column-width "+10%"); + "${cfg.binds.mod}+Up".action = _ (a.set-window-height "-10%"); + "${cfg.binds.mod}+Down".action = _ (a.set-window-height "+10%"); + + # --- Windows and columns manipulation --- + "${cfg.binds.mod}+F".action = _ a.maximize-column; + "${cfg.binds.mod}+T".action = _ a.toggle-window-floating; + "${cfg.binds.mod}+R".action = _ a.switch-preset-column-width; + "${cfg.binds.mod}+Comma".action = _ a.consume-or-expel-window-right; + + # --- Workspaces --- + "${cfg.binds.mod}+Tab".action = _ a.focus-workspace-previous; + + "${cfg.binds.mod}+1".action = _ (a.focus-workspace 1); + "${cfg.binds.mod}+2".action = _ (a.focus-workspace 2); + "${cfg.binds.mod}+3".action = _ (a.focus-workspace 3); + "${cfg.binds.mod}+4".action = _ (a.focus-workspace 4); + "${cfg.binds.mod}+5".action = _ (a.focus-workspace 5); + "${cfg.binds.mod}+6".action = _ (a.focus-workspace 6); + "${cfg.binds.mod}+7".action = _ (a.focus-workspace 7); + "${cfg.binds.mod}+8".action = _ (a.focus-workspace 8); + "${cfg.binds.mod}+9".action = _ (a.focus-workspace 9); + + # --- Apps --- + "${cfg.binds.mod}+Q".action = _ a.close-window; + "${cfg.binds.mod}+W".action = _ a.toggle-overview; + "${cfg.binds.mod}+Return".action = _ (a.spawn config.home.sessionVariables.TERMINAL); + + "XF86Display".action = _ (a.spawn [ "toggle-edp" ]); + "Mod+F7".action = _ (a.spawn [ "toggle-edp" ]); + }; + }; + }; + } + # |----------------------------------------------------------------------| # + (mkIf cfg.binds.dms.enable { + programs.niri.settings.binds = + let + a = config.lib.niri.actions; + dms = + cmd: + _ ( + a.spawn ( + [ + "dms" + "ipc" + "call" + ] + ++ cmd + ) + ); + in + { + "Ctrl+Alt+Q".action = dms [ + "powermenu" + "toggle" + ]; + + # DMS toggles + "${cfg.binds.mod}+Space".action = dms [ + "spotlight" + "toggle" + ]; + "${cfg.binds.mod}+V".action = dms [ + "clipboard" + "toggle" + ]; + "${cfg.binds.mod}+M".action = dms [ + "processlist" + "toggle" + ]; + "${cfg.binds.mod}+P".action = dms [ + "notepad" + "toggle" + ]; + "${cfg.binds.mod}+Shift+Q".action = dms [ + "" + "toggle" + ]; + "${cfg.binds.mod}+N".action = dms [ + "notifications" + "toggle" + ]; + "Ctrl+Alt+L".action = dms [ + "lock" + "lock" + ]; + + # --- Media keys via DMS IPC --- + "XF86AudioRaiseVolume" = { + action = dms [ + "audio" + "increment" + "5" + ]; + allow-when-locked = _ true; + }; + "XF86AudioLowerVolume" = { + action = dms [ + "audio" + "decrement" + "5" + ]; + allow-when-locked = _ true; + }; + "XF86AudioMute" = { + action = dms [ + "audio" + "mute" + ]; + allow-when-locked = _ true; + }; + "XF86AudioMicMute" = { + action = dms [ + "audio" + "micmute" + ]; + allow-when-locked = _ true; + }; + + # Media playback via DMS (MPRIS) + "XF86AudioPlay".action = dms [ + "mpris" + "playPause" + ]; + "XF86AudioPause".action = dms [ + "mpris" + "pause" + ]; + "XF86AudioNext".action = dms [ + "mpris" + "next" + ]; + "XF86AudioPrev".action = dms [ + "mpris" + "previous" + ]; + "XF86AudioStop".action = dms [ + "mpris" + "stop" + ]; + + "XF86MonBrightnessUp".action = dms [ + "brightness" + "increment" + "10" + "" + ]; + "XF86MonBrightnessDown".action = dms [ + "brightness" + "decrement" + "10" + "" + ]; + }; + }) + # |----------------------------------------------------------------------| # + (mkIf cfg.binds.flameshot.enable { + services.flameshot = { + enable = _ true; + package = pkgs.flameshot.override { + enableWlrSupport = true; + }; + settings = _ { + General = { + showStartupLaunchMessage = false; + useGrimAdapter = true; + }; + }; + }; + + programs.niri.settings.binds = + let + a = config.lib.niri.actions; + in + { + "Print".action = _ ( + a.spawn [ + "flameshot" + "gui" + ] + ); + "Ctrl+Print".action = _ ( + a.spawn [ + "flameshot" + "gui" + ] + ); + "Alt+Print".action = _ ( + a.spawn [ + "flameshot" + "gui" + ] + ); + }; + }) + # |----------------------------------------------------------------------| # + ]); + + meta.maintainers = with localFlake.lib.maintainers; [ tsandrini ]; +} diff --git a/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix b/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix index 6295989d..a7e37c0d 100644 --- a/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix +++ b/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix @@ -49,25 +49,25 @@ in networking.nftables.enable = _ true; networking.firewall.enable = _ true; - environment.systemPackages = with pkgs; [ + environment.systemPackages = [ # -- GENERAL PACKAGES -- - libnotify # A library that sends desktop notifications to a notification daemon - notify-desktop # Little application that lets you send desktop notifications with one command - wl-clipboard # Command-line copy/paste utilities for Wayland - maim # A command-line screenshot utility - xxdiff # Graphical file and directories comparator and merge tool - networkmanagerapplet # need this to configure L2TP ipsec + pkgs.libnotify # A library that sends desktop notifications to a notification daemon + pkgs.notify-desktop # Little application that lets you send desktop notifications with one command + pkgs.wl-clipboard # Command-line copy/paste utilities for Wayland + pkgs.maim # A command-line screenshot utility + pkgs.xxdiff # Graphical file and directories comparator and merge tool + pkgs.networkmanagerapplet # need this to configure L2TP ipsec # -- UTILS NEEDED FOR INFO-CENTER -- - clinfo # Print all known information about all available OpenCL platforms and devices in the system - mesa-demos # Test utilities for OpenGL - vulkan-tools # Khronos official Vulkan Tools and Utilities - wayland-utils # Wayland utilities (wayland-info) - aha # ANSI HTML Adapter + pkgs.clinfo # Print all known information about all available OpenCL platforms and devices in the system + pkgs.mesa-demos # Test utilities for OpenGL + pkgs.vulkan-tools # Khronos official Vulkan Tools and Utilities + pkgs.wayland-utils # Wayland utilities (wayland-info) + pkgs.aha # ANSI HTML Adapter # -- DMS + NIRI stuff -- - i2c-tools # Set of I2C tools for Linux - seahorse # Application for managing encryption keys and passwords in the GnomeKeyring + pkgs.i2c-tools # Set of I2C tools for Linux + pkgs.seahorse # Application for managing encryption keys and passwords in the GnomeKeyring # xwayland-satellite # Xwayland outside your Wayland compositor ]; @@ -106,10 +106,9 @@ in pkgs.xdg-desktop-portal-gtk # Desktop integration portals for sandboxed apps pkgs.xdg-desktop-portal-gnome # Backend implementation for xdg-desktop-portal for the GNOME desktop environment ]; - # pick wlr first, fall back to gtk for the misc stuff config.common.default = [ - "gnome" "wlr" + "gtk" ]; }; From 68c3f64586aa31ea55af244400e3e1f21158021e Mon Sep 17 00:00:00 2001 From: tsandrini Date: Sat, 7 Feb 2026 09:05:41 +0100 Subject: [PATCH 08/18] refactor(hosts/flatbundle): clean up commented hardware code --- flake-parts/hosts/flatbundle/hardware-configuration.nix | 2 -- 1 file changed, 2 deletions(-) diff --git a/flake-parts/hosts/flatbundle/hardware-configuration.nix b/flake-parts/hosts/flatbundle/hardware-configuration.nix index ba3171b4..838673a8 100644 --- a/flake-parts/hosts/flatbundle/hardware-configuration.nix +++ b/flake-parts/hosts/flatbundle/hardware-configuration.nix @@ -76,8 +76,6 @@ services.fprintd = { enable = true; - # tod.enable = true; - # tod.driver = pkgs.libfprint-2-tod1-vfs0090; }; services.fstrim = { From 9fd50eddeef24e59fe0616a2f5eaf7cbc2220b3d Mon Sep 17 00:00:00 2001 From: tsandrini Date: Sat, 7 Feb 2026 09:08:56 +0100 Subject: [PATCH 09/18] refactor(hosts/spinorbundle): clean up options after plasma refactor --- flake-parts/hosts/spinorbundle/default.nix | 26 +++++++++------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/flake-parts/hosts/spinorbundle/default.nix b/flake-parts/hosts/spinorbundle/default.nix index f2521193..93df4de1 100644 --- a/flake-parts/hosts/spinorbundle/default.nix +++ b/flake-parts/hosts/spinorbundle/default.nix @@ -29,11 +29,6 @@ inputs.nix-gaming.nixosModules.platformOptimizations (inputs.nix-mineral + "/nix-mineral.nix") - # TODO fails with The option `programs.steam.extraCompatPackages' in - # `/nix/store/nra828scc8qs92b9pxra5csqzffb6hpl-source/nixos/modules/programs/steam.nix' - # is already declared in - # `/nix/store/cqapfi5bvhzvarrbi2h1qrf2dav5r1nd-source/flake.nix#nixosModules.steamCompat'. - # nix-gaming.nixosModules.steamCompat ./hardware-configuration.nix ./disko.nix ./nm-overrides.nix @@ -43,7 +38,7 @@ # | ADDITIONAL SYSTEM PACKAGES | # ------------------------------ environment.systemPackages = [ - pkgs.networkmanagerapplet # need this to configure L2TP ipsec + pkgs.libva-utils # Collection of utilities and examples for VA-API ]; # --------------------- @@ -110,22 +105,23 @@ ]; }; - # virtualisation.docker = { - # enable = true; - # autoPrune.enable = true; - # storageDriver = "btrfs"; - # }; - - # NOTE for wireguard - # networking.wireguard.enable = true; + services.tailscale.enable = true; + networking.wireguard.enable = true; networking.firewall = { allowedUDPPorts = [ + # WG 51820 + 51821 + # Dev ports 8000 8080 5173 ]; allowedTCPPorts = [ + # WG + 51820 + 51821 + # Dev ports 8000 8080 5173 @@ -135,11 +131,9 @@ home-manager.users."tsandrini" = { tensorfiles.hm = { profiles.graphical-plasma.enable = true; - # profiles.accounts.tsandrini.enable = true; security.agenix.enable = true; programs.pywal.enable = true; - # programs.editors.emacs-doom.enable = true; services.keepassxc.enable = true; }; From eea0e449eb9edd0f236c0dbfa81d13e6b1fef919 Mon Sep 17 00:00:00 2001 From: tsandrini Date: Sat, 7 Feb 2026 09:14:49 +0100 Subject: [PATCH 10/18] refactor(modules/nixos/graphical-plasma6): tweak power related services --- flake-parts/hosts/jetbundle/hardware-configuration.nix | 2 -- flake-parts/modules/nixos/profiles/graphical-plasma6.nix | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/flake-parts/hosts/jetbundle/hardware-configuration.nix b/flake-parts/hosts/jetbundle/hardware-configuration.nix index d312f840..5b6e7014 100644 --- a/flake-parts/hosts/jetbundle/hardware-configuration.nix +++ b/flake-parts/hosts/jetbundle/hardware-configuration.nix @@ -46,8 +46,6 @@ enable = true; # cpuFreqGovernor = "schedutil"; }; - services.power-profiles-daemon.enable = true; - services.upower.enable = true; programs.gamemode.enable = true; services.fwupd.enable = true; diff --git a/flake-parts/modules/nixos/profiles/graphical-plasma6.nix b/flake-parts/modules/nixos/profiles/graphical-plasma6.nix index 15ef82b9..7ac3ea0a 100644 --- a/flake-parts/modules/nixos/profiles/graphical-plasma6.nix +++ b/flake-parts/modules/nixos/profiles/graphical-plasma6.nix @@ -132,6 +132,10 @@ in programs.kdeconnect.enable = _ true; services.pcscd.enable = _ true; # needed for gpg pinentry + # Power management and additional power statistics + services.power-profiles-daemon.enable = _ true; + services.upower.enable = _ true; + services.pipewire = { enable = _ true; alsa.enable = _ true; From 4192d6ad2e17a0fa94624a4ae7a5e85ffb9c7f62 Mon Sep 17 00:00:00 2001 From: tsandrini Date: Tue, 10 Feb 2026 17:39:12 +0100 Subject: [PATCH 11/18] feat(modules/nixos/subnets-firewall): add defaultSubnets + nixosPassthrough --- .../networking/firewall/subnets-firewall.nix | 176 +++++++++++++----- 1 file changed, 130 insertions(+), 46 deletions(-) diff --git a/flake-parts/modules/nixos/networking/firewall/subnets-firewall.nix b/flake-parts/modules/nixos/networking/firewall/subnets-firewall.nix index 39067e47..1ae88c2e 100644 --- a/flake-parts/modules/nixos/networking/firewall/subnets-firewall.nix +++ b/flake-parts/modules/nixos/networking/firewall/subnets-firewall.nix @@ -37,16 +37,61 @@ let cfg = config.tensorfiles.networking.firewall.subnets-firewall; _ = mkOverrideAtModuleLevel; + policyType = types.submodule (_: { + options = { + allowedTCPPorts = mkOption { + type = types.listOf types.port; + default = [ ]; + }; + allowedUDPPorts = mkOption { + type = types.listOf types.port; + default = [ ]; + }; + + allowedTCPPortRanges = mkOption { + type = types.listOf ( + types.submodule (_: { + options = { + from = mkOption { type = types.port; }; + to = mkOption { type = types.port; }; + }; + }) + ); + default = [ ]; + }; + + allowedUDPPortRanges = mkOption { + type = types.listOf ( + types.submodule (_: { + options = { + from = mkOption { type = types.port; }; + to = mkOption { type = types.port; }; + }; + }) + ); + default = [ ]; + }; + }; + }); + isV6 = cidr: hasInfix ":" cidr; - subnetsV4 = filterAttrs (cidr: _: !isV6 cidr) cfg.subnets; - subnetsV6 = filterAttrs (cidr: _: isV6 cidr) cfg.subnets; + defaultSubnetsRendered = builtins.listToAttrs ( + map (cidr: { + name = cidr; + value = cfg.defaultSubnets; + }) cfg.defaultSubnetsList + ); + + effectiveSubnets = defaultSubnetsRendered // cfg.subnets; + + subnetsV4 = filterAttrs (cidr: _: !isV6 cidr) effectiveSubnets; + subnetsV6 = filterAttrs (cidr: _: isV6 cidr) effectiveSubnets; # ----- helpers to collect unions across *all* subnets (v4+v6) ----- - allPolicies = attrValues cfg.subnets; + allPolicies = attrValues effectiveSubnets; unionPorts = protoKey: unique (flatten (map (p: p.${protoKey}) allPolicies)); - unionRanges = rangeKey: unique (flatten (map (p: p.${rangeKey}) allPolicies)); allTcpPorts = unionPorts "allowedTCPPorts"; @@ -56,12 +101,9 @@ let # ----- iptables rendering ----- iptActionTcp4 = if cfg.defaultAction == "reject" then "REJECT --reject-with tcp-reset" else "DROP"; - iptActionUdp4 = if cfg.defaultAction == "reject" then "REJECT --reject-with icmp-port-unreachable" else "DROP"; - iptActionTcp6 = if cfg.defaultAction == "reject" then "REJECT --reject-with tcp-reset" else "DROP"; - iptActionUdp6 = if cfg.defaultAction == "reject" then "REJECT --reject-with icmp6-port-unreachable" else "DROP"; @@ -224,49 +266,78 @@ in description = "What to do with non-allowlisted traffic for the declared ports/ranges."; }; - subnets = mkOption { - type = types.attrsOf ( - types.submodule (_: { - options = { - allowedTCPPorts = mkOption { - type = types.listOf types.port; - default = [ ]; - }; - allowedUDPPorts = mkOption { - type = types.listOf types.port; - default = [ ]; - }; + # passthrough to NixOS networking.firewall allowed* ports/ranges + nixosPassthrough = mkOption { + type = policyType; + default = { }; + description = '' + Pass-through for NixOS `networking.firewall.allowed*` options (global, non-subnet-scoped). + Useful to keep all firewall declarations under this module. + ''; + example = { + allowedTCPPorts = [ + 22 + 443 + ]; + allowedUDPPorts = [ 53 ]; + allowedTCPPortRanges = [ + { + from = 8000; + to = 8080; + } + ]; + }; + }; - allowedTCPPortRanges = mkOption { - type = types.listOf ( - types.submodule (_: { - options = { - from = mkOption { type = types.port; }; - to = mkOption { type = types.port; }; - }; - }) - ); - default = [ ]; - }; + defaultSubnetsList = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + List of CIDRs that should automatically receive the policy defined in + `defaultSubnets`. These are materialized into `subnets` at eval time, + and any explicitly defined `subnets.` entry overrides the default. + ''; + example = [ + "10.5.0.0/24" + "10.0.33.13/32" + "10.0.0.0/24" + ]; + }; - allowedUDPPortRanges = mkOption { - type = types.listOf ( - types.submodule (_: { - options = { - from = mkOption { type = types.port; }; - to = mkOption { type = types.port; }; - }; - }) - ); - default = [ ]; - }; - }; - }) - ); + defaultSubnets = mkOption { + type = policyType; + default = { }; + description = '' + The policy applied to every CIDR in `defaultSubnetsList`. + (Same schema as a single `subnets.` entry.) + ''; + example = { + allowedTCPPorts = [ + 22 + 2222 + ]; + allowedUDPPorts = [ + 80 + 443 + ]; + allowedTCPPortRanges = [ + { + from = 8000; + to = 8080; + } + ]; + }; + }; + + subnets = mkOption { + type = types.attrsOf policyType; default = { }; description = '' Attrset keyed by CIDR (IPv4 or IPv6). Each entry defines ports (and port ranges) that are reachable *only* from that CIDR. + + Note: defaults from `defaultSubnetsList/defaultSubnets` are merged in automatically, + and explicit entries here override those defaults on key collision. ''; example = { "10.10.0.0/24" = { @@ -289,6 +360,17 @@ in networking.firewall.enable = _ true; } # |----------------------------------------------------------------------| # + { + networking.firewall = { + inherit (cfg.nixosPassthrough) + allowedTCPPorts + allowedUDPPorts + allowedTCPPortRanges + allowedUDPPortRanges + ; + }; + } + # |----------------------------------------------------------------------| # (mkIf (!config.networking.nftables.enable) { networking.firewall.extraCommands = lib.mkAfter iptablesBlock; networking.firewall.extraStopCommands = lib.mkAfter iptablesStopBlock; @@ -300,7 +382,7 @@ in ${lib.concatMapStringsSep "\n" ( cidr: let - pol = cfg.subnets.${cidr}; + pol = effectiveSubnets.${cidr}; tcpPorts = pol.allowedTCPPorts or [ ]; udpPorts = pol.allowedUDPPorts or [ ]; tcpRanges = pol.allowedTCPPortRanges or [ ]; @@ -327,9 +409,11 @@ in insert rule inet nixos-fw input-allow ${saddr} ${cidr} udp dport ${mkPortSet udpPorts udpRanges} accept ''} '' - ) (lib.attrNames cfg.subnets)} + ) (lib.attrNames effectiveSubnets)} ''; }) # |----------------------------------------------------------------------| # ]); + + meta.maintainers = with localFlake.lib.maintainers; [ tsandrini ]; } From 94776f2f2d4482aa9d8015d350a21a5547d38554 Mon Sep 17 00:00:00 2001 From: tsandrini Date: Tue, 10 Feb 2026 17:41:09 +0100 Subject: [PATCH 12/18] feat(modules/hm/dank-material-shell): init general dms module --- flake-parts/modules/home-manager/default.nix | 5 +- .../programs/dank-material-shell.nix | 99 +++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 flake-parts/modules/home-manager/programs/dank-material-shell.nix diff --git a/flake-parts/modules/home-manager/default.nix b/flake-parts/modules/home-manager/default.nix index 94b2e008..7dc6c42b 100644 --- a/flake-parts/modules/home-manager/default.nix +++ b/flake-parts/modules/home-manager/default.nix @@ -47,7 +47,7 @@ in profiles_base = importApply ./profiles/base.nix { inherit localFlake; }; profiles_graphical-plasma = importApply ./profiles/graphical-plasma { inherit localFlake; }; profiles_graphical-dms-niri = importApply ./profiles/graphical-dms-niri.nix { - inherit localFlake inputs; + inherit localFlake; }; profiles_graphical-xmonad = importApply ./profiles/graphical-xmonad.nix { inherit localFlake; }; profiles_headless = importApply ./profiles/headless.nix { inherit localFlake; }; @@ -66,6 +66,9 @@ in programs_file-managers_lf = importApply ./programs/file-managers/lf { inherit localFlake; }; programs_file-managers_yazi = importApply ./programs/file-managers/yazi.nix { inherit localFlake; }; programs_git = importApply ./programs/git.nix { inherit localFlake; }; + programs_dank-material-shell = importApply ./programs/dank-material-shell.nix { + inherit localFlake inputs; + }; programs_delta = importApply ./programs/delta.nix { inherit localFlake; }; programs_dsearch = importApply ./programs/dsearch.nix { inherit localFlake inputs; }; programs_gpg = importApply ./programs/gpg.nix { inherit localFlake; }; diff --git a/flake-parts/modules/home-manager/programs/dank-material-shell.nix b/flake-parts/modules/home-manager/programs/dank-material-shell.nix new file mode 100644 index 00000000..0772ea6a --- /dev/null +++ b/flake-parts/modules/home-manager/programs/dank-material-shell.nix @@ -0,0 +1,99 @@ +# --- flake-parts/modules/home-manager/programs/dank-material-shell.nix +# +# Author: tsandrini +# URL: https://github.com/tsandrini/tensorfiles +# License: MIT +# +# 888 .d888 d8b 888 +# 888 d88P" Y8P 888 +# 888 888 888 +# 888888 .d88b. 88888b. .d8888b .d88b. 888d888 888888 888 888 .d88b. .d8888b +# 888 d8P Y8b 888 "88b 88K d88""88b 888P" 888 888 888 d8P Y8b 88K +# 888 88888888 888 888 "Y8888b. 888 888 888 888 888 888 88888888 "Y8888b. +# Y88b. Y8b. 888 888 X88 Y88..88P 888 888 888 888 Y8b. X88 +# "Y888 "Y8888 888 888 88888P' "Y88P" 888 888 888 888 "Y8888 88888P' +{ localFlake, inputs }: +{ config, lib, ... }: +let + inherit (lib) mkIf mkMerge mkEnableOption; + inherit (localFlake.lib.modules) mkOverrideAtHmModuleLevel; + inherit (localFlake.lib.modules) isModuleLoadedAndEnabled; + inherit (localFlake.lib.options) mkPywalEnableOption; + + cfg = config.tensorfiles.hm.programs.dank-material-shell; + _ = mkOverrideAtHmModuleLevel; + + pywalCheck = (isModuleLoadedAndEnabled config "tensorfiles.hm.programs.pywal") && cfg.pywal.enable; + niri-flakeCheck = + (isModuleLoadedAndEnabled config "tensorfiles.hm.programs.niri-flake") && cfg.niri-flake.enable; +in +{ + options.tensorfiles.hm.programs.dank-material-shell = { + enable = mkEnableOption '' + + ''; + + niri-flake = { + enable = mkEnableOption "Enables binding for the niri-flake project"; + }; + + pywal = { + enable = mkPywalEnableOption; + }; + }; + + imports = [ + inputs.dms.homeModules.dank-material-shell + inputs.dms.homeModules.niri # TODO No better place to have this unfortunately + ]; + + config = mkIf cfg.enable (mkMerge [ + # |----------------------------------------------------------------------| # + { + programs.dank-material-shell = { + enable = _ true; + systemd = { + enable = _ (!niri-flakeCheck); + restartIfChanged = _ true; + }; + + enableSystemMonitoring = _ true; + enableVPN = _ true; + enableDynamicTheming = _ true; + enableAudioWavelength = _ true; + enableCalendarEvents = _ true; + enableClipboardPaste = _ true; + }; + } + # |----------------------------------------------------------------------| # + (mkIf pywalCheck { + systemd.user.tmpfiles.rules = [ + "d ${config.xdg.cacheHome}/wal 0700 - - -" + "L+ ${config.xdg.cacheHome}/wal/colors.json - - - - ${config.xdg.cacheHome}/wal/dank-pywalfox.json" + ]; + }) + # |----------------------------------------------------------------------| # + (mkIf niri-flakeCheck { + programs.dank-material-shell.niri = { + enableSpawn = _ true; + enableKeybinds = _ false; + includes = { + enable = _ true; + override = _ true; + filesToInclude = [ + "alttab" + "binds" + "cursor" + "colors" + "layout" + "outputs" + "wpblur" + ]; + }; + }; + }) + # |----------------------------------------------------------------------| # + ]); + + meta.maintainers = with localFlake.lib.maintainers; [ tsandrini ]; +} From 2fce67df6a5f7c148dbce2d667b03c023497fbbd Mon Sep 17 00:00:00 2001 From: tsandrini Date: Tue, 10 Feb 2026 17:41:35 +0100 Subject: [PATCH 13/18] fix(modules/hm/niri-flake): permit using toggle-edp while locked --- .../modules/home-manager/programs/niri-flake.nix | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/flake-parts/modules/home-manager/programs/niri-flake.nix b/flake-parts/modules/home-manager/programs/niri-flake.nix index 81eeef37..88e0b3fe 100644 --- a/flake-parts/modules/home-manager/programs/niri-flake.nix +++ b/flake-parts/modules/home-manager/programs/niri-flake.nix @@ -48,7 +48,6 @@ let ' )" - # If we didn't find the block at all, bail out (or you could default to "on"). if [ -z "$block" ]; then exit 0 fi @@ -204,8 +203,14 @@ in "${cfg.binds.mod}+W".action = _ a.toggle-overview; "${cfg.binds.mod}+Return".action = _ (a.spawn config.home.sessionVariables.TERMINAL); - "XF86Display".action = _ (a.spawn [ "toggle-edp" ]); - "Mod+F7".action = _ (a.spawn [ "toggle-edp" ]); + "XF86Display" = { + action = _ (a.spawn [ "toggle-edp" ]); + allow-when-locked = _ true; + }; + "Mod+F7" = { + action = _ (a.spawn [ "toggle-edp" ]); + allow-when-locked = _ true; + }; }; }; }; From 6b1f0d02ecf5fe5ed6722773306795f142749237 Mon Sep 17 00:00:00 2001 From: tsandrini Date: Tue, 10 Feb 2026 17:42:26 +0100 Subject: [PATCH 14/18] refactor(modules/hm/graphical-dms-niri): switch to general dms module --- .../profiles/graphical-dms-niri.nix | 64 +++++-------------- .../nixos/profiles/graphical-dms-niri.nix | 7 +- .../nixos/profiles/graphical-plasma6.nix | 1 + 3 files changed, 20 insertions(+), 52 deletions(-) diff --git a/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix b/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix index af171ecd..ef3d253f 100644 --- a/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix +++ b/flake-parts/modules/home-manager/profiles/graphical-dms-niri.nix @@ -12,7 +12,7 @@ # 888 88888888 888 888 "Y8888b. 888 888 888 888 888 888 88888888 "Y8888b. # Y88b. Y8b. 888 888 X88 Y88..88P 888 888 888 888 Y8b. X88 # "Y888 "Y8888 888 888 88888P' "Y88P" 888 888 888 888 "Y8888 88888P' -{ localFlake, inputs }: +{ localFlake }: { pkgs, config, @@ -29,10 +29,10 @@ let optional ; inherit (localFlake.lib.modules) mkOverrideAtHmProfileLevel; + inherit (localFlake.lib.options) mkPywalEnableOption; cfg = config.tensorfiles.hm.profiles.graphical-dms-niri; _ = mkOverrideAtHmProfileLevel; - in { options.tensorfiles.hm.profiles.graphical-dms-niri = { @@ -47,12 +47,11 @@ in // { default = true; }; - }; - imports = [ - inputs.dms.homeModules.dank-material-shell - # inputs.dms.homeModules.niri - ]; + pywal = { + enable = mkPywalEnableOption; + }; + }; config = mkIf cfg.enable (mkMerge [ # |----------------------------------------------------------------------| # @@ -74,6 +73,10 @@ in binds.dms.enable = _ true; binds.flameshot.enable = _ true; }; + dank-material-shell = { + enable = _ true; + niri-flake.enable = _ true; + }; dsearch.enable = _ true; }; }; @@ -164,7 +167,7 @@ in TERMINAL = _ "wezterm"; IDE = _ "nvim"; EMAIL = _ "thunderbird"; - QT_QPA_PLATFORMTHEME = "qt6ct"; + QT_QPA_PLATFORMTHEME = _ "qt6ct"; }; fonts.fontconfig.enable = _ true; @@ -173,13 +176,13 @@ in enable = _ true; theme = { - name = "adw-gtk3-dark"; - package = pkgs.adw-gtk3; + name = _ "adw-gtk3-dark"; + package = _ pkgs.adw-gtk3; }; iconTheme = { - name = "Papirus-Dark"; - package = pkgs.papirus-icon-theme; + name = _ "Papirus-Dark"; + package = _ pkgs.papirus-icon-theme; }; }; @@ -198,10 +201,6 @@ in }; # TODO move this elsewhere - systemd.user.tmpfiles.rules = [ - "d %h/.cache/wal 0700 - - -" - "L+ %h/.cache/wal/colors.json - - - - %h/.cache/wal/dank-pywalfox.json" - ]; xdg.mimeApps.defaultApplications = { "application/pdf" = [ "org.gnome.Evince.desktop" ]; @@ -263,39 +262,6 @@ in "application/xml" = [ "neovide.desktop" ]; "text/xml" = [ "neovide.desktop" ]; }; - - programs.dank-material-shell = { - enable = _ true; - systemd = { - enable = _ false; - restartIfChanged = _ true; - }; - - niri = { - enableSpawn = _ true; - enableKeybinds = _ false; - includes = { - enable = _ true; - override = _ true; - filesToInclude = [ - "alttab" - "binds" - "cursor" - "colors" - "layout" - "outputs" - "wpblur" - ]; - }; - }; - - enableSystemMonitoring = _ true; - enableVPN = _ true; - enableDynamicTheming = _ true; - enableAudioWavelength = _ true; - enableCalendarEvents = _ true; - enableClipboardPaste = _ true; - }; } # |----------------------------------------------------------------------| # ]); diff --git a/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix b/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix index a7e37c0d..26ad0e92 100644 --- a/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix +++ b/flake-parts/modules/nixos/profiles/graphical-dms-niri.nix @@ -83,6 +83,7 @@ in }; programs.ssh.startAgent = _ false; # NOTE: using gnome agent + # NOTE: It's required to have the niri executable in $PATH to populate # the wayland-sessions for the dms-greeter. Niri itself will then # load any configuration provided by HM without any issues, but we @@ -117,12 +118,11 @@ in QT_QPA_PLATFORMTHEME = _ "gtk3"; XDG_CURRENT_DESKTOP = _ "niri"; XDG_SESSION_DESKTOP = _ "niri"; - #DISPLAY = ":0"; - #QT_QPA_PLATFORM = "wayland"; }; hardware.i2c.enable = _ true; # Required to control brightness of external monitors + # Power management and additional power statistics services.power-profiles-daemon.enable = _ true; services.upower.enable = _ true; @@ -132,6 +132,7 @@ in security.pam.services.login.enableGnomeKeyring = _ true; services.pcscd.enable = _ true; # needed for gpg pinentry + # AUDIO stuff services.pipewire = { enable = _ true; @@ -141,7 +142,7 @@ in }; security.rtkit.enable = _ true; # realtime audio scheduling - programs.kdeconnect.enable = _ true; + programs.kdeconnect.enable = _ true; # Required to expose ports systemd.user.services.niri-flake-polkit.enable = _ false; } # |----------------------------------------------------------------------| # diff --git a/flake-parts/modules/nixos/profiles/graphical-plasma6.nix b/flake-parts/modules/nixos/profiles/graphical-plasma6.nix index 7ac3ea0a..17e9d884 100644 --- a/flake-parts/modules/nixos/profiles/graphical-plasma6.nix +++ b/flake-parts/modules/nixos/profiles/graphical-plasma6.nix @@ -136,6 +136,7 @@ in services.power-profiles-daemon.enable = _ true; services.upower.enable = _ true; + # AUDIO stuff services.pipewire = { enable = _ true; alsa.enable = _ true; From 948956c12dab85212713fbd611527280c72a791a Mon Sep 17 00:00:00 2001 From: tsandrini Date: Tue, 17 Feb 2026 10:54:52 +0100 Subject: [PATCH 15/18] fix(modules/nixvim/treesitter): disable highlighting --- .../modules/nixvim/plugins/editor/treesitter.nix | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/flake-parts/modules/nixvim/plugins/editor/treesitter.nix b/flake-parts/modules/nixvim/plugins/editor/treesitter.nix index 82e3fcb1..8fae8df2 100644 --- a/flake-parts/modules/nixvim/plugins/editor/treesitter.nix +++ b/flake-parts/modules/nixvim/plugins/editor/treesitter.nix @@ -37,20 +37,21 @@ in { plugins.treesitter = { enable = _ true; - settings = { - indent.enable = _ true; - highlight.enable = _ true; - }; + indent.enable = _ true; + highlight.enable = _ false; folding.enable = _ true; nixvimInjections = _ true; nixGrammars = _ true; - # grammarPackages = _ pkgs.vimPlugins.nvim-treesitter.allGrammars; }; plugins.treesitter-context = { enable = _ true; }; + # plugins.treesitter-refactor = { + # enable = _ true; + # }; + # plugins.treesitter-textobjects = { # enable = _ true; # select = { From f2987f7869368f8cb5427f71cca586a1c2b9efbc Mon Sep 17 00:00:00 2001 From: tsandrini Date: Tue, 17 Feb 2026 10:55:48 +0100 Subject: [PATCH 16/18] feat(modules/nixvim/lsp): set up properly vue lsp --- .../modules/nixvim/plugins/lsp/lsp.nix | 95 ++++++++++++++++++- 1 file changed, 90 insertions(+), 5 deletions(-) diff --git a/flake-parts/modules/nixvim/plugins/lsp/lsp.nix b/flake-parts/modules/nixvim/plugins/lsp/lsp.nix index f3fbb5e4..818be76a 100644 --- a/flake-parts/modules/nixvim/plugins/lsp/lsp.nix +++ b/flake-parts/modules/nixvim/plugins/lsp/lsp.nix @@ -106,14 +106,99 @@ in r_language_server.package = _ pkgs.rPackages.languageserver; sqls.enable = _ true; # sqls for SQL terraformls.enable = _ true; # terraformls for Terraform - ts_ls.enable = true; # typst_lsp.enable = _ true; # typst-lsp for the Typst language tinymist.enable = _ true; # tinymist for typst texlab.enable = _ true; # texlab for LaTeX - # volar.enable = _ true; # volar for Vue, replaces vuels - vue_ls.enable = _ true; - # vuels.enable = _ true; # vuels for Vue - # vuels.package = _ pkgs.nodePackages_latest.vls; + + vue_ls = { + enable = _ true; + extraOptions.init_options = { + vue = { + hybridMode = _ false; + }; + tracing = _ true; + }; + settings = { + typescript = { + inlayHints = { + enumMemberValues = { + enabled = false; + }; + functionLikeReturnTypes = { + enabled = false; + }; + propertyDeclarationTypes = { + enabled = false; + }; + parameterTypes = { + enabled = false; + suppressWhenArgumentMatchesName = true; + }; + variableTypes = { + enabled = false; + }; + }; + }; + }; + }; + + vtsls = { + enable = _ true; + filetypes = [ + "vue" + "javascript" + "javascriptreact" + "javascript.jsx" + "typescript" + "typescriptreact" + "typescript.tsx" + ]; + settings = { + vtsls.tsserver.globalPlugins = [ + { + name = "@vue/typescript-plugin"; + location = "${lib.getBin pkgs.vue-language-server}/lib/language-tools/packages/language-server"; + languages = [ "vue" ]; + } + ]; + }; + }; + + ts_ls = { + enable = _ true; + filetypes = [ + "vue" + "javascript" + "javascriptreact" + "javascript.jsx" + "typescript" + "typescriptreact" + "typescript.tsx" + ]; + extraOptions.init_options.plugins = lib.mkForce [ + { + name = "@vue/typescript-plugin"; + location = "${lib.getBin pkgs.vue-language-server}/lib/language-tools/packages/language-server"; + languages = [ "vue" ]; + configNamespace = "typescript"; + } + ]; + settings = { + typescript = { + inlayHints = { + includeInlayParameterNameHints = "all"; + includeInlayParameterNameHintsWhenArgumentMatchesName = false; + includeInlayFunctionParameterTypeHints = false; + includeInlayVariableTypeHints = false; + includeInlayVariableTypeHintsWhenTypeMatchesName = false; + includeInlayPropertyDeclarationTypeHints = false; + includeInlayFunctionLikeReturnTypeHints = false; + includeInlayEnumMemberValueHints = false; + }; + }; + }; + }; + zls.enable = _ true; # zls for Zig # rust-analyzer = { # enable = _ true; From 14598aafababd11f6d19ee24247d611db6e81e6c Mon Sep 17 00:00:00 2001 From: tsandrini Date: Wed, 18 Feb 2026 12:30:32 +0100 Subject: [PATCH 17/18] feat(pkgs/cc-switcher): init --- flake-parts/pkgs/cc-switcher.sh | 833 ++++++++++++++++++++++++++++++++ flake-parts/pkgs/default.nix | 12 + 2 files changed, 845 insertions(+) create mode 100644 flake-parts/pkgs/cc-switcher.sh diff --git a/flake-parts/pkgs/cc-switcher.sh b/flake-parts/pkgs/cc-switcher.sh new file mode 100644 index 00000000..ad081cba --- /dev/null +++ b/flake-parts/pkgs/cc-switcher.sh @@ -0,0 +1,833 @@ +#!/usr/bin/env bash + +# Multi-Account Switcher for Claude Code +# Simple tool to manage and switch between multiple Claude Code accounts + +set -euo pipefail + +# Configuration +readonly BACKUP_DIR="$HOME/.claude-switch-backup" +readonly SEQUENCE_FILE="$BACKUP_DIR/sequence.json" + +# Container detection +is_running_in_container() { + # Check for Docker environment file + if [[ -f /.dockerenv ]]; then + return 0 + fi + + # Check cgroup for container indicators + if [[ -f /proc/1/cgroup ]] && grep -q 'docker\|lxc\|containerd\|kubepods' /proc/1/cgroup 2>/dev/null; then + return 0 + fi + + # Check mount info for container filesystems + if [[ -f /proc/self/mountinfo ]] && grep -q 'docker\|overlay' /proc/self/mountinfo 2>/dev/null; then + return 0 + fi + + # Check for common container environment variables + if [[ -n ${CONTAINER:-} ]] || [[ -n ${container:-} ]]; then + return 0 + fi + + return 1 +} + +# Platform detection +detect_platform() { + case "$(uname -s)" in + Darwin) echo "macos" ;; + Linux) + if [[ -n ${WSL_DISTRO_NAME:-} ]]; then + echo "wsl" + else + echo "linux" + fi + ;; + *) echo "unknown" ;; + esac +} + +# Get Claude configuration file path with fallback +get_claude_config_path() { + local primary_config="$HOME/.claude/.claude.json" + local fallback_config="$HOME/.claude.json" + + # Check primary location first + if [[ -f $primary_config ]]; then + # Verify it has valid oauthAccount structure + if jq -e '.oauthAccount' "$primary_config" >/dev/null 2>&1; then + echo "$primary_config" + return + fi + fi + + # Fallback to standard location + echo "$fallback_config" +} + +# Basic validation that JSON is valid +validate_json() { + local file="$1" + if ! jq . "$file" >/dev/null 2>&1; then + echo "Error: Invalid JSON in $file" + return 1 + fi +} + +# Email validation function +validate_email() { + local email="$1" + # Use robust regex for email validation + if [[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then + return 0 + else + return 1 + fi +} + +# Account identifier resolution function +resolve_account_identifier() { + local identifier="$1" + if [[ $identifier =~ ^[0-9]+$ ]]; then + echo "$identifier" # It's a number + else + # Look up account number by email + local account_num + account_num=$(jq -r --arg email "$identifier" '.accounts | to_entries[] | select(.value.email == $email) | .key' "$SEQUENCE_FILE" 2>/dev/null) + if [[ -n $account_num && $account_num != "null" ]]; then + echo "$account_num" + else + echo "" + fi + fi +} + +# Safe JSON write with validation +write_json() { + local file="$1" + local content="$2" + local temp_file + temp_file=$(mktemp "${file}.XXXXXX") + + echo "$content" >"$temp_file" + if ! jq . "$temp_file" >/dev/null 2>&1; then + rm -f "$temp_file" + echo "Error: Generated invalid JSON" + return 1 + fi + + mv "$temp_file" "$file" + chmod 600 "$file" +} + +# Check Bash version (4.4+ required) +check_bash_version() { + local version + version=$(bash --version | head -n1 | grep -oE '[0-9]+\.[0-9]+' | head -n1) + if ! awk -v ver="$version" 'BEGIN { exit (ver >= 4.4 ? 0 : 1) }'; then + echo "Error: Bash 4.4+ required (found $version)" + exit 1 + fi +} + +# Check dependencies +check_dependencies() { + if ! command -v jq >/dev/null 2>&1; then + echo "Error: Required command 'jq' not found" + echo "Install with: apt install jq (Linux) or brew install jq (macOS)" + exit 1 + fi +} + +# Setup backup directories +setup_directories() { + mkdir -p "$BACKUP_DIR"/{configs,credentials} + chmod 700 "$BACKUP_DIR" + chmod 700 "$BACKUP_DIR"/{configs,credentials} +} + +# Claude Code process detection (Node.js app) +is_claude_running() { + ps -eo pid,comm,args | awk '$2 == "claude" || $3 == "claude" {exit 0} END {exit 1}' +} + +# Wait for Claude Code to close (no timeout - user controlled) +wait_for_claude_close() { + if ! is_claude_running; then + return 0 + fi + + echo "Claude Code is running. Please close it first." + echo "Waiting for Claude Code to close..." + + while is_claude_running; do + sleep 1 + done + + echo "Claude Code closed. Continuing..." +} + +# Get current account info from .claude.json +get_current_account() { + if [[ ! -f "$(get_claude_config_path)" ]]; then + echo "none" + return + fi + + if ! validate_json "$(get_claude_config_path)"; then + echo "none" + return + fi + + local email + email=$(jq -r '.oauthAccount.emailAddress // empty' "$(get_claude_config_path)" 2>/dev/null) + echo "${email:-none}" +} + +# Detect which Claude Code service name is used in keychain (macOS only) +get_claude_service_name() { + if security find-generic-password -s "Claude Code-credentials" >/dev/null 2>&1; then + echo "Claude Code-credentials" + elif security find-generic-password -s "Claude Code" >/dev/null 2>&1; then + echo "Claude Code" + else + echo "" + fi +} + +# Read credentials based on platform +read_credentials() { + local platform + platform=$(detect_platform) + + case "$platform" in + macos) + local service_name + service_name=$(get_claude_service_name) + if [[ -n $service_name ]]; then + security find-generic-password -s "$service_name" -w 2>/dev/null || echo "" + else + echo "" + fi + ;; + linux | wsl) + if [[ -f "$HOME/.claude/.credentials.json" ]]; then + cat "$HOME/.claude/.credentials.json" + else + echo "" + fi + ;; + *) + echo "" + ;; + esac +} + +# Write credentials based on platform +write_credentials() { + local credentials="$1" + local platform + platform=$(detect_platform) + + case "$platform" in + macos) + local service_name + service_name=$(get_claude_service_name) + if [[ -z $service_name ]]; then + # Default to -credentials for new installations + service_name="Claude Code-credentials" + fi + security add-generic-password -U -s "$service_name" -a "$USER" -w "$credentials" 2>/dev/null + ;; + linux | wsl) + mkdir -p "$HOME/.claude" + printf '%s' "$credentials" >"$HOME/.claude/.credentials.json" + chmod 600 "$HOME/.claude/.credentials.json" + ;; + esac +} + +# Read account credentials from backup +read_account_credentials() { + local account_num="$1" + local email="$2" + local platform + platform=$(detect_platform) + + case "$platform" in + macos) + security find-generic-password -s "Claude Code-Account-${account_num}-${email}" -w 2>/dev/null || echo "" + ;; + linux | wsl) + local cred_file="$BACKUP_DIR/credentials/.claude-credentials-${account_num}-${email}.json" + if [[ -f $cred_file ]]; then + cat "$cred_file" + else + echo "" + fi + ;; + *) + echo "" + ;; + esac +} + +# Write account credentials to backup +write_account_credentials() { + local account_num="$1" + local email="$2" + local credentials="$3" + local platform + platform=$(detect_platform) + + case "$platform" in + macos) + security add-generic-password -U -s "Claude Code-Account-${account_num}-${email}" -a "$USER" -w "$credentials" 2>/dev/null + ;; + linux | wsl) + local cred_file="$BACKUP_DIR/credentials/.claude-credentials-${account_num}-${email}.json" + printf '%s' "$credentials" >"$cred_file" + chmod 600 "$cred_file" + ;; + esac +} + +# Read account config from backup +read_account_config() { + local account_num="$1" + local email="$2" + local config_file="$BACKUP_DIR/configs/.claude-config-${account_num}-${email}.json" + + if [[ -f $config_file ]]; then + cat "$config_file" + else + echo "" + fi +} + +# Write account config to backup +write_account_config() { + local account_num="$1" + local email="$2" + local config="$3" + local config_file="$BACKUP_DIR/configs/.claude-config-${account_num}-${email}.json" + + echo "$config" >"$config_file" + chmod 600 "$config_file" +} + +# Initialize sequence.json if it doesn't exist +init_sequence_file() { + if [[ ! -f $SEQUENCE_FILE ]]; then + local init_content + init_content='{ + "activeAccountNumber": null, + "lastUpdated": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'", + "sequence": [], + "accounts": {} +}' + write_json "$SEQUENCE_FILE" "$init_content" + fi +} + +# Get next account number +get_next_account_number() { + if [[ ! -f $SEQUENCE_FILE ]]; then + echo "1" + return + fi + + local max_num + max_num=$(jq -r '.accounts | keys | map(tonumber) | max // 0' "$SEQUENCE_FILE") + echo $((max_num + 1)) +} + +# Check if account exists by email +account_exists() { + local email="$1" + if [[ ! -f $SEQUENCE_FILE ]]; then + return 1 + fi + + jq -e --arg email "$email" '.accounts[] | select(.email == $email)' "$SEQUENCE_FILE" >/dev/null 2>&1 +} + +# Add account +cmd_add_account() { + setup_directories + init_sequence_file + + local current_email + current_email=$(get_current_account) + + if [[ $current_email == "none" ]]; then + echo "Error: No active Claude account found. Please log in first." + exit 1 + fi + + if account_exists "$current_email"; then + echo "Account $current_email is already managed." + exit 0 + fi + + local account_num + account_num=$(get_next_account_number) + + local platform + platform=$(detect_platform) + + # Get current service name (macOS only) + credentials/config + local service_name current_creds current_config + service_name="" + if [[ $platform == "macos" ]]; then + service_name=$(get_claude_service_name) + fi + current_creds=$(read_credentials) + current_config=$(cat "$(get_claude_config_path)") + + if [[ -z $current_creds ]]; then + echo "Error: No credentials found for current account" + exit 1 + fi + + if [[ $platform == "macos" && -z $service_name ]]; then + echo "Error: Could not determine Claude Code service name" + exit 1 + fi + + # Get account UUID + local account_uuid + account_uuid=$(jq -r '.oauthAccount.accountUuid' "$(get_claude_config_path)") + + # Store backups + write_account_credentials "$account_num" "$current_email" "$current_creds" + write_account_config "$account_num" "$current_email" "$current_config" + + # Update sequence.json (serviceName only for macOS) + local updated_sequence + updated_sequence=$( + jq --arg num "$account_num" \ + --arg email "$current_email" \ + --arg uuid "$account_uuid" \ + --arg service "$service_name" \ + --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" ' + .accounts[$num] = { + email: $email, + uuid: $uuid, + serviceName: (if $service == "" then null else $service end), + added: $now + } | + .sequence += [$num | tonumber] | + .activeAccountNumber = ($num | tonumber) | + .lastUpdated = $now + ' "$SEQUENCE_FILE" + ) + + write_json "$SEQUENCE_FILE" "$updated_sequence" + + if [[ $platform == "macos" ]]; then + echo "Added Account $account_num: $current_email (service: $service_name)" + else + echo "Added Account $account_num: $current_email" + fi +} + +# Remove account +cmd_remove_account() { + if [[ $# -eq 0 ]]; then + echo "Usage: $0 --remove-account " + exit 1 + fi + + local identifier="$1" + local account_num + + if [[ ! -f $SEQUENCE_FILE ]]; then + echo "Error: No accounts are managed yet" + exit 1 + fi + + # Handle email vs numeric identifier + if [[ $identifier =~ ^[0-9]+$ ]]; then + account_num="$identifier" + else + # Validate email format + if ! validate_email "$identifier"; then + echo "Error: Invalid email format: $identifier" + exit 1 + fi + + # Resolve email to account number + account_num=$(resolve_account_identifier "$identifier") + if [[ -z $account_num ]]; then + echo "Error: No account found with email: $identifier" + exit 1 + fi + fi + + local account_info + account_info=$(jq -r --arg num "$account_num" '.accounts[$num] // empty' "$SEQUENCE_FILE") + + if [[ -z $account_info ]]; then + echo "Error: Account-$account_num does not exist" + exit 1 + fi + + local email + email=$(echo "$account_info" | jq -r '.email') + + local active_account + active_account=$(jq -r '.activeAccountNumber' "$SEQUENCE_FILE") + + if [[ $active_account == "$account_num" ]]; then + echo "Warning: Account-$account_num ($email) is currently active" + fi + + echo -n "Are you sure you want to permanently remove Account-$account_num ($email)? [y/N] " + read -r confirm + + if [[ $confirm != "y" && $confirm != "Y" ]]; then + echo "Cancelled" + exit 0 + fi + + # Remove backup files + local platform + platform=$(detect_platform) + case "$platform" in + macos) + security delete-generic-password -s "Claude Code-Account-${account_num}-${email}" 2>/dev/null || true + ;; + linux | wsl) + rm -f "$BACKUP_DIR/credentials/.claude-credentials-${account_num}-${email}.json" + ;; + esac + rm -f "$BACKUP_DIR/configs/.claude-config-${account_num}-${email}.json" + + # Update sequence.json + local updated_sequence + updated_sequence=$( + jq --arg num "$account_num" --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" ' + del(.accounts[$num]) | + .sequence = (.sequence | map(select(. != ($num | tonumber)))) | + .lastUpdated = $now + ' "$SEQUENCE_FILE" + ) + + write_json "$SEQUENCE_FILE" "$updated_sequence" + + echo "Account-$account_num ($email) has been removed" +} + +# First-run setup workflow +first_run_setup() { + local current_email + current_email=$(get_current_account) + + if [[ $current_email == "none" ]]; then + echo "No active Claude account found. Please log in first." + return 1 + fi + + echo -n "No managed accounts found. Add current account ($current_email) to managed list? [Y/n] " + read -r response + + if [[ $response == "n" || $response == "N" ]]; then + echo "Setup cancelled. You can run '$0 --add-account' later." + return 1 + fi + + cmd_add_account + return 0 +} + +# List accounts +cmd_list() { + if [[ ! -f $SEQUENCE_FILE ]]; then + echo "No accounts are managed yet." + first_run_setup + exit 0 + fi + + # Get current active account from .claude.json + local current_email + current_email=$(get_current_account) + + # Find which account number corresponds to the current email + local active_account_num="" + if [[ $current_email != "none" ]]; then + active_account_num=$(jq -r --arg email "$current_email" '.accounts | to_entries[] | select(.value.email == $email) | .key' "$SEQUENCE_FILE" 2>/dev/null) + fi + + echo "Accounts:" + jq -r --arg active "$active_account_num" ' + .sequence[] as $num | + .accounts["\($num)"] | + if "\($num)" == $active then + " \($num): \(.email) (active)" + else + " \($num): \(.email)" + end + ' "$SEQUENCE_FILE" +} + +# Switch to next account +cmd_switch() { + if [[ ! -f $SEQUENCE_FILE ]]; then + echo "Error: No accounts are managed yet" + exit 1 + fi + + local current_email + current_email=$(get_current_account) + + if [[ $current_email == "none" ]]; then + echo "Error: No active Claude account found" + exit 1 + fi + + # Check if current account is managed + if ! account_exists "$current_email"; then + echo "Notice: Active account '$current_email' was not managed." + cmd_add_account + local account_num + account_num=$(jq -r '.activeAccountNumber' "$SEQUENCE_FILE") + echo "It has been automatically added as Account-$account_num." + echo "Please run './ccswitch.sh --switch' again to switch to the next account." + exit 0 + fi + + # wait_for_claude_close + + local active_account + active_account=$(jq -r '.activeAccountNumber' "$SEQUENCE_FILE") + + local -a sequence=() + mapfile -t sequence < <(jq -r '.sequence[]' "$SEQUENCE_FILE") + + # Find next account in sequence + local next_account current_index=0 + for i in "${!sequence[@]}"; do + if [[ ${sequence[i]} == "$active_account" ]]; then + current_index=$i + break + fi + done + + next_account="${sequence[$(((current_index + 1) % ${#sequence[@]}))]}" + + perform_switch "$next_account" +} + +# Switch to specific account +cmd_switch_to() { + if [[ $# -eq 0 ]]; then + echo "Usage: $0 --switch-to " + exit 1 + fi + + local identifier="$1" + local target_account + + if [[ ! -f $SEQUENCE_FILE ]]; then + echo "Error: No accounts are managed yet" + exit 1 + fi + + # Handle email vs numeric identifier + if [[ $identifier =~ ^[0-9]+$ ]]; then + target_account="$identifier" + else + # Validate email format + if ! validate_email "$identifier"; then + echo "Error: Invalid email format: $identifier" + exit 1 + fi + + # Resolve email to account number + target_account=$(resolve_account_identifier "$identifier") + if [[ -z $target_account ]]; then + echo "Error: No account found with email: $identifier" + exit 1 + fi + fi + + local account_info + account_info=$(jq -r --arg num "$target_account" '.accounts[$num] // empty' "$SEQUENCE_FILE") + + if [[ -z $account_info ]]; then + echo "Error: Account-$target_account does not exist" + exit 1 + fi + + # wait_for_claude_close + perform_switch "$target_account" +} + +# Perform the actual account switch +perform_switch() { + local target_account="$1" + + local platform + platform=$(detect_platform) + + # Get current and target account info + local current_account target_email current_email target_service current_service + current_account=$(jq -r '.activeAccountNumber' "$SEQUENCE_FILE") + target_email=$(jq -r --arg num "$target_account" '.accounts[$num].email' "$SEQUENCE_FILE") + target_service=$(jq -r --arg num "$target_account" '.accounts[$num].serviceName // empty' "$SEQUENCE_FILE") + current_email=$(get_current_account) + + current_service="" + if [[ $platform == "macos" ]]; then + current_service=$(get_claude_service_name) + fi + + if [[ $platform == "macos" && -z $target_service ]]; then + echo "Error: No service name stored for Account-$target_account. Re-add this account." + exit 1 + fi + + # Step 1: Backup current account + local current_creds current_config + current_creds=$(read_credentials) + current_config=$(cat "$(get_claude_config_path)") + + write_account_credentials "$current_account" "$current_email" "$current_creds" + write_account_config "$current_account" "$current_email" "$current_config" + + # Step 2: Retrieve target account + local target_creds target_config + target_creds=$(read_account_credentials "$target_account" "$target_email") + target_config=$(read_account_config "$target_account" "$target_email") + + if [[ -z $target_creds || -z $target_config ]]; then + echo "Error: Missing backup data for Account-$target_account" + exit 1 + fi + + # Step 3: Write credentials (Keychain on macOS, file on Linux) + if [[ $platform == "macos" ]]; then + if [[ -n $current_service && $current_service != "$target_service" ]]; then + security delete-generic-password -s "$current_service" 2>/dev/null || true + echo "Removed old keychain entry: $current_service" + fi + + security add-generic-password -U -s "$target_service" -a "$USER" -w "$target_creds" 2>/dev/null + echo "Added keychain entry: $target_service" + else + write_credentials "$target_creds" + echo "Wrote credentials to $HOME/.claude/.credentials.json" + fi + + # Step 4: Update config file + local oauth_section + oauth_section=$(echo "$target_config" | jq '.oauthAccount' 2>/dev/null) + if [[ -z $oauth_section || $oauth_section == "null" ]]; then + echo "Error: Invalid oauthAccount in backup" + exit 1 + fi + + # Merge oauthAccount into current config file (preserve other local settings) + local merged_config + merged_config=$(jq --argjson oauth "$oauth_section" '.oauthAccount = $oauth' "$(get_claude_config_path)" 2>/dev/null) || { + echo "Error: Failed to merge config" + exit 1 + } + + write_json "$(get_claude_config_path)" "$merged_config" + + # Step 5: Update state + local updated_sequence + updated_sequence=$( + jq --arg num "$target_account" --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" ' + .activeAccountNumber = ($num | tonumber) | + .lastUpdated = $now + ' "$SEQUENCE_FILE" + ) + + write_json "$SEQUENCE_FILE" "$updated_sequence" + + if [[ $platform == "macos" ]]; then + echo "Switched to Account-$target_account ($target_email) using service: $target_service" + else + echo "Switched to Account-$target_account ($target_email)" + fi + + cmd_list + echo "" + echo "Please restart Claude Code to use the new authentication." + echo "" +} + +# Show usage +show_usage() { + echo "Multi-Account Switcher for Claude Code" + echo "Usage: $0 [COMMAND]" + echo "" + echo "Commands:" + echo " --add-account Add current account to managed accounts" + echo " --remove-account Remove account by number or email" + echo " --list List all managed accounts" + echo " --switch Rotate to next account in sequence" + echo " --switch-to Switch to specific account number or email" + echo " --help Show this help message" + echo "" + echo "Examples:" + echo " $0 --add-account" + echo " $0 --list" + echo " $0 --switch" + echo " $0 --switch-to 2" + echo " $0 --switch-to user@example.com" + echo " $0 --remove-account user@example.com" +} + +# Main script logic +main() { + # Basic checks - allow root execution in containers + if [[ $EUID -eq 0 ]] && ! is_running_in_container; then + echo "Error: Do not run this script as root (unless running in a container)" + exit 1 + fi + + check_bash_version + check_dependencies + + case "${1:-}" in + --add-account) + cmd_add_account + ;; + --remove-account) + shift + cmd_remove_account "$@" + ;; + --list) + cmd_list + ;; + --switch) + cmd_switch + ;; + --switch-to) + shift + cmd_switch_to "$@" + ;; + --help) + show_usage + ;; + "") + show_usage + ;; + *) + echo "Error: Unknown command '$1'" + show_usage + exit 1 + ;; + esac +} + +# Check if script is being sourced or executed +if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then + main "$@" +fi diff --git a/flake-parts/pkgs/default.nix b/flake-parts/pkgs/default.nix index 3bd53207..1100a579 100644 --- a/flake-parts/pkgs/default.nix +++ b/flake-parts/pkgs/default.nix @@ -29,6 +29,18 @@ my_cookies = pkgs.callPackage ./my_cookies.nix { }; polonium-nightly = pkgs.libsForQt5.callPackage ./polonium-nightly.nix { inherit lib; }; certbot-dns-wedos = pkgs.callPackage ./certbot-dns-wedos.nix { }; + cc-switcher = pkgs.writeShellApplication { + name = "cc-switcher"; + runtimeInputs = [ + pkgs.jq + pkgs.coreutils + pkgs.gnugrep + pkgs.gawk + pkgs.procps + ]; + + text = builtins.readFile ./cc-switcher.sh; + }; # awatcher = pkgs.callPackage ./awatcher.nix { }; }; }; From bfe6fe180121c2961925e1799f9f9eb2dc097716 Mon Sep 17 00:00:00 2001 From: tsandrini Date: Wed, 18 Feb 2026 12:31:01 +0100 Subject: [PATCH 18/18] feat(hosts/flatbundle): add cc-switcher --- flake-parts/hosts/flatbundle/default.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/flake-parts/hosts/flatbundle/default.nix b/flake-parts/hosts/flatbundle/default.nix index 6ee53b16..fdda2b10 100644 --- a/flake-parts/hosts/flatbundle/default.nix +++ b/flake-parts/hosts/flatbundle/default.nix @@ -176,6 +176,7 @@ in programs.git.signing.key = "3E83AD690FA4F657"; # pragma: allowlist secret home.packages = [ + inputs.self.packages.${system}.cc-switcher pkgs-osu-lazer-bin.osu-lazer-bin pkgs.olympus ];