bedHedd's NixOS Adventure

background

After configuring my dad’s NixOS box in the last post, I decided I wanted to migrate my main desktop from Fedora to nix.

With the holidays approaching and some spare time open up, I decided to get this todo done and start migrating all pcs I own to NixOS.

why leave fedora?

These are my personal reasons for leaving fedora on my main pc. If any new developments occur, I might switch back

  1. Shipping their own versions flatpaks (see details below)
  2. Out of date or incompatible packages (guake and playwright)

duplicate out of date flatpaks

With the recent controversy between OBS and Fedora, I decided I wanted to hop to a new distro that had better packages.
https://www.osnews.com/story/141723/fedora-should-not-push-its-users-to-its-own-flatpak-repository/

out of date or incompatible (guake and playwright)

guake

Controversy aside, I also had a issue with guake not working properly on fedora with wayland. The window would be out of place.

Turns out my issue was solved in a newer version that fedora did not update in the package repo

the rpm is still on version 3.7.0


https://src.fedoraproject.org/rpms/guake

if we look at the github releases, 3.8.5 has wayland fixes and the latest is 3.10.0

playwright

I use my desktop for to create personal development projects as well. For one of my projects I wanted to try out playwright. Unfortunately it is a distro that is not supported

I also don’t feel like creating a vm nor debugging the required dependencies to build playwright

Although flatpak makes a universal package, most software is packaged for ubuntu or debian based systems first.

todos

  • make a backup of my pc
  • get NixOS in a a ready state as fedora
    • setup development environments (multiple ssh keys, git)
    • declaratively have the icons only task manager on all screens
    • have my mass storage drive mounted on login
  • try installing steam and some games, also vesktop
  • Add windows drive as a boot option to the grub boot options
  • update nix os repo instructions
3 Likes

preparing my system

getting a list of packages

I had chatgpt previously summarize dnf’s repoquery output to get the list of packages and software I use

creating a backup

I started by using rescuezilla to backup all my drives to a 18 tb external hard drive. After experiencing data loss, previously

I decided it was worth creating a backup. Also if I got cold feet, I could go back to Fedora

1 Like

get NixOS in a ready state

initial install

After creating a backup with rescuezilla, I downloaded the graphical installer image and used fedora media writer to burn it to a usb.

I set my account credentials enabled unfree packages, and set kde as my default desktop.

Once I got logged into the desktop, I downloaded and unzipped my nix config because nix does not include git by default

I created a new folder called master-of-cooling and copy pasted the configuration.nix and hardware-configuration.nix files from /etc/nixos

After copying the system nix files, I copied the home.nix and users.nix files from jade-tiger. I changed users to just import bedhedd.nix.

I also updated flake.nix to include the new machine. I copied the jade-tiger entry and replaced it with the master-of-cooling paths

nixosConfigurations.jade-tiger = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";

      modules = [
        (import ./machines/jade-tiger/configuration.nix)
        revModule
        localNixpkgsModule

        # ← pull in Home‑Manager as a NixOS module:
        home-manager.nixosModules.home-manager
      ];

      # expose both home-manager and plasma-manager into your
      # machine’s specialArgs so that configuration.nix can see them:
      specialArgs = { inherit nixos-hardware home-manager plasma-manager; };
    };

As I finished applying my changes, I tried doing a rebuild

sudo nixos-rebuild switch --flake .#master-of-cooling --show-trace

I got the following error

warning: Git tree '/home/bedhedd/Documents/nix-config' is dirty
error:
       … while calling the 'seq' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:359:18:
          358|         options = checked options;
          359|         config = checked (removeAttrs config [ "_module" ]);
             |                  ^
          360|         _module = checked (config._module);

       … while evaluating a branch condition
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:295:9:
          294|       checkUnmatched =
          295|         if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [ ] then
             |         ^
          296|           let

       … in the left operand of the AND (&&) operator
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:295:72:
          294|       checkUnmatched =
          295|         if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [ ] then
             |                                                                        ^
          296|           let

       … in the left operand of the AND (&&) operator
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:295:33:
          294|       checkUnmatched =
          295|         if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [ ] then
             |                                 ^
          296|           let

       … while evaluating a branch condition
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:286:9:
          285|         in
          286|         if declaredConfig._module.freeformType == null then
             |         ^
          287|           declaredConfig

       … from call site
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:273:28:
          272|           # For definitions that have an associated option
          273|           declaredConfig = mapAttrsRecursiveCond (v: !isOption v) (_: v: v.value) options;
             |                            ^
          274|

       … while calling 'mapAttrsRecursiveCond'
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/attrsets.nix:1177:14:
         1176|   mapAttrsRecursiveCond =
         1177|     cond: f: set:
             |              ^
         1178|     let

       … while calling the 'mapAttrs' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/attrsets.nix:1186:5:
         1185|     in
         1186|     recurse [ ] set;
             |     ^
         1187|

       … while evaluating the attribute 'matchedOptions'
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:856:15:
          855|     {
          856|       inherit matchedOptions;
             |               ^
          857|

       … while calling the 'mapAttrs' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:846:24:
          845|
          846|       matchedOptions = mapAttrs (n: v: v.matchedOptions) resultsByName;
             |                        ^
          847|

       … while calling the 'mapAttrs' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:794:23:
          793|
          794|       resultsByName = mapAttrs (
             |                       ^
          795|         name: decls:

       … while calling the 'zipAttrsWith' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:710:21:
          709|       # an attrset 'name' => list of submodules that declare β€˜name’.
          710|       declsByName = zipAttrsWith (n: v: v) (
             |                     ^
          711|         map (

       … while calling the 'map' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:711:9:
          710|       declsByName = zipAttrsWith (n: v: v) (
          711|         map (
             |         ^
          712|           module:

       … from call site
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:265:30:
          264|         in
          265|         mergeModules prefix (reverseList collected);
             |                              ^
          266|

       … while calling 'reverseList'
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/lists.nix:1103:5:
         1102|   reverseList =
         1103|     xs:
             |     ^
         1104|     let

       … while calling the 'genList' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/lists.nix:1107:5:
         1106|     in
         1107|     genList (n: elemAt xs (l - n - 1)) l;
             |     ^
         1108|

       … while evaluating the second argument passed to builtins.genList

       … while calling the 'length' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/lists.nix:1105:11:
         1104|     let
         1105|       l = length xs;
             |           ^
         1106|     in

       … from call site
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:251:13:
          250|           collected =
          251|             collectModules class (specialArgs.modulesPath or "") (regularModules ++ [ internalModule ])
             |             ^
          252|               (

       … while calling anonymous lambda
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:522:34:
          521|     in
          522|     modulesPath: initialModules: args:
             |                                  ^
          523|     filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args);

       … from call site
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:523:5:
          522|     modulesPath: initialModules: args:
          523|     filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args);
             |     ^
          524|

       … while calling 'filterModules'
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:492:9:
          491|         modulesPath:
          492|         { disabled, modules }:
             |         ^
          493|         let

       … while calling the 'map' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:516:9:
          515|         in
          516|         map (attrs: attrs.module) (genericClosure {
             |         ^
          517|           startSet = keyFilter modules;

       … while calling the 'genericClosure' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:516:36:
          515|         in
          516|         map (attrs: attrs.module) (genericClosure {
             |                                    ^
          517|           startSet = keyFilter modules;

       … while calling the 'filter' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:517:22:
          516|         map (attrs: attrs.module) (genericClosure {
          517|           startSet = keyFilter modules;
             |                      ^
          518|           operator = attrs: keyFilter attrs.modules;

       … while calling anonymous lambda
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:514:31:
          513|           disabledKeys = concatMap ({ file, disabled }: map (moduleKey file) disabled) disabled;
          514|           keyFilter = filter (attrs: !elem attrs.key disabledKeys);
             |                               ^
          515|         in

       … in the argument of the not operator
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:514:39:
          513|           disabledKeys = concatMap ({ file, disabled }: map (moduleKey file) disabled) disabled;
          514|           keyFilter = filter (attrs: !elem attrs.key disabledKeys);
             |                                       ^
          515|         in

       … while calling the 'elem' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:514:39:
          513|           disabledKeys = concatMap ({ file, disabled }: map (moduleKey file) disabled) disabled;
          514|           keyFilter = filter (attrs: !elem attrs.key disabledKeys);
             |                                       ^
          515|         in

       … while calling the 'concatMap' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:513:26:
          512|
          513|           disabledKeys = concatMap ({ file, disabled }: map (moduleKey file) disabled) disabled;
             |                          ^
          514|           keyFilter = filter (attrs: !elem attrs.key disabledKeys);

       … while calling the 'concatLists' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:453:24:
          452|           collectResults = modules: {
          453|             disabled = concatLists (catAttrs "disabled" modules);
             |                        ^
          454|             inherit modules;

       … while evaluating the attribute 'disabled'
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:453:13:
          452|           collectResults = modules: {
          453|             disabled = concatLists (catAttrs "disabled" modules);
             |             ^
          454|             inherit modules;

       … while calling the 'concatLists' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:453:24:
          452|           collectResults = modules: {
          453|             disabled = concatLists (catAttrs "disabled" modules);
             |                        ^
          454|             inherit modules;

       … while evaluating a branch condition
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:471:19:
          470|                 (
          471|                   if module.disabledModules != [ ] then
             |                   ^
          472|                     [

       … from call site
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:462:24:
          461|             let
          462|               module = checkModule (loadModule args parentFile "${parentKey}:anon-${toString n}" x);
             |                        ^
          463|               collectedImports = collectStructuredModules module._file module.key module.imports args;

       … while calling anonymous lambda
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:410:11:
          409|         if class != null then
          410|           m:
             |           ^
          411|           if m._class == null || m._class == class then

       … while evaluating a branch condition
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:411:11:
          410|           m:
          411|           if m._class == null || m._class == class then
             |           ^
          412|             m

       … in the left operand of the OR (||) operator
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:411:31:
          410|           m:
          411|           if m._class == null || m._class == class then
             |                               ^
          412|             m

       … from call site
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:462:37:
          461|             let
          462|               module = checkModule (loadModule args parentFile "${parentKey}:anon-${toString n}" x);
             |                                     ^
          463|               collectedImports = collectStructuredModules module._file module.key module.imports args;

       … while calling 'loadModule'
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:376:42:
          375|       loadModule =
          376|         args: fallbackFile: fallbackKey: m:
             |                                          ^
          377|         if isFunction m then

       … while evaluating a branch condition
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:377:9:
          376|         args: fallbackFile: fallbackKey: m:
          377|         if isFunction m then
             |         ^
          378|           unifyModuleSyntax fallbackFile fallbackKey (applyModuleArgs fallbackKey m args)

       … from call site
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/modules.nix:377:12:
          376|         args: fallbackFile: fallbackKey: m:
          377|         if isFunction m then
             |            ^
          378|           unifyModuleSyntax fallbackFile fallbackKey (applyModuleArgs fallbackKey m args)

       … while calling 'isFunction'
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/trivial.nix:1023:16:
         1022|   */
         1023|   isFunction = f: builtins.isFunction f || (f ? __functor && isFunction (f.__functor f));
             |                ^
         1024|

       … in the left operand of the OR (||) operator
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/trivial.nix:1023:41:
         1022|   */
         1023|   isFunction = f: builtins.isFunction f || (f ? __functor && isFunction (f.__functor f));
             |                                         ^
         1024|

       … while calling the 'isFunction' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/trivial.nix:1023:19:
         1022|   */
         1023|   isFunction = f: builtins.isFunction f || (f ? __functor && isFunction (f.__functor f));
             |                   ^
         1024|

       … while calling the 'elemAt' builtin
         at /nix/store/x9wnkly3k1gkq580m90jjn32q9f05q2v-source/lib/lists.nix:345:43:
          344|   */
          345|   imap1 = f: list: genList (n: f (n + 1) (elemAt list n)) (length list);
             |                                           ^
          346|

       … while calling the 'import' builtin
         at /nix/store/p5qbm57cr6ylbp6s5anyhfc0298k2wbw-source/flake.nix:57:10:
           56|       modules = [
           57|         (import ./machines/master-of-cooling/configuration.nix)
             |          ^
           58|         revModule

       error: path '/nix/store/p5qbm57cr6ylbp6s5anyhfc0298k2wbw-source/machines/master-of-cooling/configuration.nix' does not exist

turns out the issue was that I made uncommitted changes. Because I hadn’t setup git yet, I didn’t commit the changes.

To resolve this, I made a copy of my directory and deleted the .git folder from the repo. Afterwards the error disappeared. I kept using the temp directory until I could setup git and my credentials.

After the system rebuilt, I logged in and out to refresh the desktop

setting development environments

As mentioned in the previous section, I needed to setup my credentials so that i can commit. Before switching to Nix, I had a custom fish script that would autostart a ssh manager and set a ssh key for a given directory.

Finding the Nix equivalent was a challenge, so let’s start with completed config

# dotfiles/multiple-ssh.nix
{ config, pkgs, lib, ... }:

let
  primaryFolder   = "folder_for_account_1";
  secondaryFolder = "folder_for_account_2";
  primaryKeyFile      = "filename_for_account_1";
  secondaryKeyFile    = "filename_for_account_2";
in
{
  # ──────────────────────────────────────────────────────────────────────────
  # 1.  OpenSSH config (still needed for Host aliases)
  # ──────────────────────────────────────────────────────────────────────────
  programs.ssh = {
    enable = true;
    addKeysToAgent = "yes"; 

    matchBlocks = {
      "github.com-primary" = {
        hostname       = "github.com";
        user           = "git";
        identityFile   = "~/.ssh/${primaryKeyFile}";
        identitiesOnly = true;       # force this key only
      };
      "github.com-secondary" = {
        hostname       = "github.com";
        user           = "git";
        identityFile   = "~/.ssh/${secondaryKeyFile}";
        identitiesOnly = true;
      };
    };

    # optional: fix pinentry TTY when using gpg-agent
    extraConfig = ''
      Match host * exec "gpg-connect-agent UPDATESTARTUPTTY /bye"
    '';
  };

  # ──────────────────────────────────────────────────────────────────────────
  # 2.  Keychain β†’ one shared ssh-agent per login session
  # ──────────────────────────────────────────────────────────────────────────
  programs.keychain = {
    enable                = true;
    agents                = [ "ssh" ];      # also "gpg" if you like
    keys                  = [
      # "${primaryKeyFile}"
      # "${secondaryKeyFile}"
    ];
    extraFlags = [ "--noask" "--quiet" ];
    enableFishIntegration = true;          # writes eval-line to config.fish
    # For Bash/Z-sh, use enableBashIntegration / enableZshIntegration.
  };

  # ──────────────────────────────────────────────────────────────────────────
  # 3.  Git conditional includes for per-folder identities
  # ──────────────────────────────────────────────────────────────────────────
  programs.git = {
    enable = true;

    includes = [
      {
        condition = "gitdir:*/${primaryFolder}/";
        contents = {
          user.email = "[email protected]";
          url."[email protected]:".insteadOf = [
            "https://github.com/"
            "[email protected]:"
          ];
        };
      }
      {
        condition = "gitdir:*/${secondaryFolder}/";
        contents = {
          user.name = "github_username";
          user.email = "[email protected]";
          url."[email protected]:".insteadOf = [
            "https://github.com/"
            "[email protected]:"
          ];
        };
      }
    ];
  };

  # ──────────────────────────────────────────────────────────────────────────
  # 4.  (Optional) gpg-agent with SSH support for GUI pass-phrase caching
  # ──────────────────────────────────────────────────────────────────────────
  services.gpg-agent = {
    enable = true;
     enableSshSupport = true;
     # one day
     maxCacheTtl = 86400;
     # six hours
     defaultCacheTtl = 21600;
     pinentry.package  = pkgs.pinentry-qt;
  };
}

I created a .gitignore file to hide the multiple-ssh.nix file becuse I would like to keep my email private. Instead I give a example that you can modify for your own use.

Here’s the flow of the script

  1. define the folder paths (folder for a given ssh key) and ssh key file paths
    •  let
         primaryFolder   = "folder_for_account_1";
         secondaryFolder = "folder_for_account_2";
         primaryKeyFile      = "filename_for_account_1";
         secondaryKeyFile    = "filename_for_account_2";
      
  2. define the ssh config structure
    •    programs.ssh = {
           enable = true;
           addKeysToAgent = "yes"; 
      
           matchBlocks = {
             "github.com-primary" = {
               hostname       = "github.com";
               user           = "git";
               identityFile   = "~/.ssh/${primaryKeyFile}";
               identitiesOnly = true;       # force this key only
             };
             "github.com-secondary" = {
               hostname       = "github.com";
               user           = "git";
               identityFile   = "~/.ssh/${secondaryKeyFile}";
               identitiesOnly = true;
             };
           };
      
           # optional: fix pinentry TTY when using gpg-agent
           extraConfig = ''
             Match host * exec "gpg-connect-agent UPDATESTARTUPTTY /bye"
           '';
         };
      
  3. initialize the keychain to cache key values
    •    programs.keychain = {
           enable                = true;
           agents                = [ "ssh" ];      # also "gpg" if you like
           keys                  = [
             # "${primaryKeyFile}"
             # "${secondaryKeyFile}"
           ];
           extraFlags = [ "--noask" "--quiet" ];
           enableFishIntegration = true;          # writes eval-line to config.fish
           # For Bash/Z-sh, use enableBashIntegration / enableZshIntegration.
                 };
      
  4. initialize git to use the set the email and url customizations, replace user.name, user.email, with the name and email for github
  5. Enable GPG agent to handle ssh requests
    •    services.gpg-agent = {
           enable = true;
            enableSshSupport = true;
            # one day
            maxCacheTtl = 86400;
            # six hours
            defaultCacheTtl = 21600;
            pinentry.package  = pkgs.pinentry-qt;
         };
      

After setting up multiple-ssh.nix, I commented out the gpg agent service within home.nix. I also added keychain to the packages section as well.

I also made sure to add the filepath to my import in the user directory.

coming up with the solution

I was fortunate that chatgpt was able to help explain and figure out how to generate this config

I initially tried creating a config in home/.ssh, but i found that nix’s ssh would conflict with it. Furthermore, when I ran ssh-add -L, I kept a output of

ssh-add -L
The agent has no identities.

When I inspected the symlinked config file I found the following

Host *
  ForwardAgent no
  AddKeysToAgent no
  Compression no
  ServerAliveInterval 0
  ServerAliveCountMax 3
  HashKnownHosts no
  UserKnownHostsFile ~/.ssh/known_hosts
  ControlMaster no
  ControlPath ~/.ssh/master-%r@%n:%p
  ControlPersist no

Apparently from chatgpt, with this config, it would automatically ignore any entries I added after the pre generated nix config.

Since I based my fedora config on this section guide, I decided to feed it into chatgpt

I gave it the following prompt

Chatgpt generated a config, but when I tried it out, I still got the same error of no keys listed. It was a combination of that and the git config, so I decided to feed in michael’s git section

Chatgpt’s output resolved the git issue, but for whatever reason the keys wouldn’t show up. While browsing the web for solutions, I came across this post, which suggested using keychain

I fed it into chatgpt with the past context

chatgpt gave me a suggested snippet

This worked, but I didn’t want to login to my ssh keys every time I started a new terminal. Chatgpt suggested

# OLD: pre-loads keys, so you get a prompt on every terminal
programs.keychain = {
  enable  = true;
  agents  = [ "ssh" ];
  keys    = [ ];                 # but keychain still tries the defaults
  enableFishIntegration = true;
};

# NEW: starts an ssh-agent, exports SSH_AUTH_SOCK,
#      but NEVER asks until the key is first used.
programs.keychain = {
  enable  = true;
  agents  = [ "ssh" ];
  keys    = [ ];                       # empty list β€” we’ll add on demand
  extraFlags = [ "--noask" "--quiet" ];# ← prevents automatic ssh-add
  enableFishIntegration = true;        # keep the Fish hook
};

I then told it to update the past config it suggested.

The last issue I ran into was that the logic for a given path didn’t work. The initial suggestion was to use a absolute path, but I didn’t want it to be hard coded, fortunately the second suggestion to use a wildcard (*) got it working

        condition = "gitdir:*/${secondaryFolder}/";

After that I had it generate the version example above

declaratively have the icons only task manager on all screens

I had chatgpt look it up and it found that I can add it to all screens with plasma manager

        screen = "all";

here it is in the config

{
  programs.plasma = {
    enable = true;
    workspace.lookAndFeel = lib.mkForce "com.valve.vapor.deck.desktop";

    panels = lib.mkForce [
      {
        screen = "all";
        location = "bottom";
        height   = 64;
        floating = false;
        # hiding   = "autoHide";
        # visibility = "autohide";
        hiding   = "dodgewindows";

        widgets = [
          "org.kde.plasma.kickoff"
          "org.kde.plasma.pager"
          "org.kde.plasma.icontasks"
          "org.kde.plasma.marginsseparator"
          "org.kde.plasma.systemtray"
          "org.kde.plasma.digitalclock"
          "org.kde.plasma.showdesktop"
        ];
      }
    ];
  };

  # Override Kickoff’s icon at the KConfig level
}

automount my mass storage drive and symlink with home

I asked chatgpt to help me create a entry for automounting my drive. It started by telling me to run

lsblk -f
NAME        FSTYPE FSVER LABEL       UUID                                 FSAVAIL FSUSE% MOUNTPOINTS
sda                                                                                      
└─sda1      btrfs        MassStorage 027f2550-4813-20d9-ac54-fc87dc4612eb    2.4T 

To get the drive’s uuid. After giving the output, it suggested the following entry

{
  fileSystems."/mnt/sda1" = {
    device  = "/dev/disk/by-uuid/027f2550-4813-20d9-ac54-fc87dc4612eb";
    fsType  = "btrfs";

    # Fine-tune options to taste.  Good defaults for a personal btrfs data disk:
    options = [
      "compress=zstd"    # transparent compression
      "noatime"          # don’t update atime on every read
      "ssd"              # if the drive is actually an SSD
      # For a plug-in USB disk add:
      # "noauto" "x-systemd.automount"
    ];
  };
}

I then asked it to help me create a symlink for my Documents, Downloads, Music, Pictures, and Videos folders.

chatgpt gave this output

  fileSystems."/home/youruser/Documents" = {
    device  = "/mnt/sda1/Documents";
    fsType  = "none";            # ← required for bind mounts
    options = [ "bind" ];
    depends = [ "/mnt/sda1" ];   # be sure the disk is mounted first
  };

Chatgpt cited these two sources
https://superuser.com/questions/1271645/how-do-you-declare-a-bind-mount-in-nixos
https://nixos.wiki/wiki/Filesystems
I think it used the older nixos.wiki because when i checked the newer wiki it removed the fsType

the newer wiki omits the fsType

So I ended up using this config

  fileSystems."/home/bedhedd/Documents" = {
    device  = "/mnt/sda1/Documents";
    options = [ "bind" ];
    depends = [ "/mnt/sda1" ];   # be sure the disk is mounted first
  };

  fileSystems."/home/bedhedd/Downloads" = {
    device  = "/mnt/sda1/Downloads";
    options = [ "bind" ];
    depends = [ "/mnt/sda1" ];
  };

  fileSystems."/home/bedhedd/Music" = {
    device  = "/mnt/sda1/Music";
    options = [ "bind" ];
    depends = [ "/mnt/sda1" ];
  };
  
  fileSystems."/home/bedhedd/Pictures" = {
    device  = "/mnt/sda1/Pictures";
    options = [ "bind" ];
    depends = [ "/mnt/sda1" ];
  };

  fileSystems."/home/bedhedd/Videos" = {
    device  = "/mnt/sda1/Videos";
    options = [ "bind" ];
    depends = [ "/mnt/sda1" ];
  };
1 Like

install steam and vesktop

steam was installed with the steamos flake

{ lib, pkgs, ... }:

let
  steamdeckPlasma = pkgs.stdenv.mkDerivation rec {
    pname = "steamdeck-plasma-assets";
    version = "0.28";
    src = pkgs.fetchurl {
      url    = "https://github.com/Jovian-Experiments/steamdeck-kde-presets/archive/refs/tags/${version}.tar.gz";
      sha256 = "sha256-g8frzdFqj1ydZNDLrZ9S3Pe1KCHp3B/XaRO+dKjIOKQ=";
    };
    installPhase = ''
      mkdir -p $out
      cp -r usr/share $out/
    '';
  };
in {
  environment.systemPackages = [ steamdeckPlasma ];
  environment.pathsToLink    = [ "/usr/share" ];
}

For vesktop, I just added it to the user packages

  userPackages = with pkgs; [
    # other entries above
    # social apps
    vesktop
  ];

add windows drive as a boot option to boot options

I added the following entry

  # Bootloader.
  boot.loader = {
    systemd-boot.enable = true;
    efi.canTouchEfiVariables = true;

    # ONE line β†’ menu entry called β€œ11” pointing at fs2:
    systemd-boot.windows."10".efiDeviceHandle = "FS2";
  };

when I used this config, it booted me into


  boot.loader = {
    efi.canTouchEfiVariables = true;
    systemd-boot = {
      enable = true;

      # helper to find the handle: set edk2-uefi-shell.enable = true,
      # rebuild, boot the shell, run  `map -c`, then `ls HD0c3:\EFI`
      windows."11" = {
        title           = "Windows 11";
        efiDeviceHandle = "HD0c3";   # whatever handle lists the Microsoft dir
        sortKey         = "o_windows";
      };
    };
  };

this shell


after some further debugging with chatgpt I found out I could use

Shell> map -r -b

to get the device alias. I tested it by running the following command

Shell> fs2:
fs2:\> dir EFI

Once I am in the directory, I verified that the microsoft and boot folders were there. Then I had chatgpt update the entry

update nix os repo instructions

I added highlevel steps based on my experience working on this

2 Likes

setting development environments 2: uv strikes back

While finishing the setup on my desktop, I had some issues getting newer versions of python and getting playwright setup. I had chatgpt create a new version of the tweaks I made to get uv and playwright working

Here’s the new version

# modules/development.nix  ── always-on Home-Manager module
{ pkgs, lib, ... }:

let
  # Pick your default interpreter here (swap for python312, python310, …)
  python = pkgs.python311;
in
{
  ##########################################################################
  # Packages added to the user profile
  ##########################################################################
  home.packages = with pkgs; [
    uv
    nodejs_20                       # helper scripts used by Playwright
    playwright-driver.browsers      # patched Chromium / Firefox / WebKit
    gcc                             # compile native wheels
    fastfetch                       # misc CLI goodies

    # Wrapper so wheels can find libstdc++, libgcc_s, etc. at runtime
    (pkgs.writeShellScriptBin "python3" ''
      export LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH
      exec ${python}/bin/python3 "$@"
    '')
  ];

  ##########################################################################
  # Environment variables for every shell (bash, zsh, fish …)
  ##########################################################################
  home.sessionVariables = {
    LD_LIBRARY_PATH =
      "${pkgs.lib.makeLibraryPath [ pkgs.stdenv.cc.cc.lib ]}:$LD_LIBRARY_PATH";
    PLAYWRIGHT_BROWSERS_PATH =
      "${pkgs.playwright-driver.browsers}";
    PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = "true";
  };

  ##########################################################################
  # Extra initialisation for fish (runs only when the interactive shell is fish)
  ##########################################################################
  programs.fish.shellInit = ''
    # Playwright on NixOS
    set -gx PLAYWRIGHT_BROWSERS_PATH ${pkgs.playwright-driver.browsers}
    set -gx PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS true

    # Tell uv to prefer its own managed runtimes; unset hard pins
    if not set -q UV_PYTHON_PREFERENCE
        set -Ux UV_PYTHON_PREFERENCE only-managed
    end
    set -e UV_PYTHON
  '';
}

uv tweaks

This worked when the pinned python version aligned with the version defined, but didn’t work when I needed it for a newer version that wasn’t python 3.12.

I needed to add this line to my flake so that nixld would get installed at a system level

nixLdModule = { pkgs, ... }: { programs.nix-ld.enable = true; };

I also needed to set a fish environment variable UV_PYTHON_PREFERENCE to only-managed as well

set -Ux UV_PYTHON_PREFERENCE only-managed

playwright tweaks

As mentioned in the OP playwright is a nightmare to install on other distros
When I tried running this python code

# ⬇️ First time only: install nest_asyncio
# !pip install nest_asyncio   # uncomment if you haven't already

import nest_asyncio, asyncio
from playwright.async_api import async_playwright

nest_asyncio.apply()          # ← lets us run another event loop inside Jupyter

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)  # set False to watch the UI
        page = await browser.new_page()

        await page.goto("https://nixos.org")
        print("Page title:", await page.title())

        # Grab a screenshot into the notebook’s working dir
        await page.screenshot(path="example.png", full_page=True)
        await browser.close()

# In a notebook cell you can await directly
await main()

# If you're in a plain .py file instead, use:
# asyncio.run(main())

the following error occured

Error: BrowserType.launch: 
╔══════════════════════════════════════════════════════╗
β•‘ Host system is missing dependencies to run browsers. β•‘
β•‘ Missing libraries:                                   β•‘
β•‘     libglib-2.0.so.0                                 β•‘
β•‘     libgobject-2.0.so.0                              β•‘
β•‘     libnss3.so                                       β•‘
β•‘     libnssutil3.so                                   β•‘
β•‘     libnspr4.so                                      β•‘
β•‘     libdbus-1.so.3                                   β•‘
β•‘     libatk-1.0.so.0                                  β•‘
β•‘     libatk-bridge-2.0.so.0                           β•‘
β•‘     libgio-2.0.so.0                                  β•‘
β•‘     libexpat.so.1                                    β•‘
β•‘     libatspi.so.0                                    β•‘
β•‘     libX11.so.6                                      β•‘
β•‘     libXcomposite.so.1                               β•‘
β•‘     libXdamage.so.1                                  β•‘
β•‘     libXext.so.6                                     β•‘
β•‘     libXfixes.so.3                                   β•‘
β•‘     libXrandr.so.2                                   β•‘
β•‘     libgbm.so.1                                      β•‘
β•‘     libxcb.so.1                                      β•‘
β•‘     libxkbcommon.so.0                                β•‘
β•‘     libudev.so.1                                     β•‘
β•‘     libasound.so.2                                   β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

the solution that ultimately worked for me was the entry from the nix os wiki.
https://wiki.nixos.org/wiki/Playwright#Installing_browsers_for_playwright_under_NixOS
Which had the following entry

I did not want to have separate shell because I use uv to manage my environments, so I needed the playwright webdriver dependencies to work independently.

I fed the section from the wiki and told chatgpt I needed this config to be adapted to work with fish, and consolidate my python/uv tweaks into a single file.

Effectively the wiki page adds the playwright-driver.browsers package to my environment and sets 2 environment variables PLAYWRIGHT_BROWSERS_PATH to the playwright-driver.browsers path as well as PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS to true.

I then added development.nix to my imports in the bedhedd.nix file

Once nix finished rebuilding, I ran this which indicates the config was successfully applied

> echo $PLAYWRIGHT_BROWSERS_PATH
/nix/store/42axmwmwydsh8kzznd1mz7lnmg0a1zkk-playwright-browsers
```
1 Like

Now that the desktop is usable, I won’t be writing the small updates. If anyone has any questions going through the commit history, feel free to ask here.

guake customizations

Today I added some guake customizations to my nix config

Specifically I wanted to set the position, transparency, font and height of the terminal.

fonts

the font spacing was particularly annoying.
image
So I asked chatgpt which suggested disabling Use the system fixed width font. Clicking from the preferences window fixed it, but i needed to figure out how to declaratively set this for nix

Chatgpt suggested I run gsettings list-schemas | grep guake

gsettings list-schemas | grep guake
guake
guake.general
guake.hooks
guake.keybindings
guake.keybindings.global
guake.keybindings.local
guake.style
guake.style.background
guake.style.font

This output gets me the config files

Running gsettings list-keys guake.general

gsettings list-keys guake.general
background-image-file
background-image-layout-mode
compat-backspace
compat-delete
copy-on-select
custom-command-file
custom-search-engine
debug-mode
default-shell
display-n
display-tab-names
focus-if-open
fullscreen-hide-tabbar
gtk-prefer-dark-theme
gtk-theme-name
gtk-use-system-default-theme
hide-tabs-if-one-tab
history-size
infinite-history
lazy-losefocus
load-guake-yml
max-tab-name-length
mouse-display
new-tab-after
open-tab-cwd
prompt-on-close-tab
prompt-on-quit
quick-open-command-line
quick-open-enable
quick-open-in-current-terminal
restore-tabs-notify
restore-tabs-startup
save-tabs-when-changed
schema-version
scroll-keystroke
scroll-output
search-engine
set-window-title
start-at-login
start-fullscreen
startup-script
tab-close-buttons
tab-ontop
toolbar-visible-in-fullscreen
use-audible-bell
use-default-font
use-login-shell
use-popup
use-scrollbar
use-trayicon
use-vte-titles
window-halignment
window-height
window-horizontal-displacement
window-losefocus
window-ontop
window-refocus
window-tabbar
window-valignment
window-vertical-displacement
window-width
word-chars
workspace-specific-tab-sets

I can set the window-width, window-height (this makes it take less of my vertical spac) window-valignment (sets the terminal to appear from the bottom

running this fish script helped me get the values I set in the preferences window

for key in \
                                                           window-width \
                                                           window-height \
                                                           window-halignment \
                                                           window-valignment \
                                                           window-horizontal-displacement \
                                                           window-vertical-displacement
                                                       set val (gsettings get guake.general $key)
                                                       printf "%-35s %s\n" "$key:" $val
                                                   end

window-width:                       100
window-height:                      36
window-halignment:                  0
window-valignment:                  1
window-horizontal-displacement:     0
window-vertical-displacement:       0

running gsettings get guake.general $KEY-NAME will get the value of a given $KEY-NAME, you can replace it with any of the outputs from list-keys (use-default-font, window-width, `window-height or etc.)

1 Like

selenium

Since I encountered issues with playwright, I decided to get selenium setup as a backup

playwright debugging

This build previously worked, but for whatever reason when I try to run code that imports playwright, it fails to grab the correct chromium version.

Error: BrowserType.launch: Executable doesn't exist at /nix/store/42axmwmwydsh8kzznd1mz7lnmg0a1zkk-playwright-browsers/chromium-1179/chrome-linux/chrome
╔════════════════════════════════════════════════════════════╗
β•‘ Looks like Playwright was just installed or updated.       β•‘
β•‘ Please run the following command to download new browsers: β•‘
β•‘                                                            β•‘
β•‘     playwright install                                     β•‘
β•‘                                                            β•‘
β•‘ <3 Playwright Team                                         β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

The error message references a newer version of chromium 1179 /chromium-1179/chrome-linux/chrome
If I go to that directory, and list the files, it doesn’t exist

ls
chromium-1169@  chromium_headless_shell-1169@  ffmpeg-1011@  firefox-1482@  webkit-2158@

I replied to the existing thread

getting selenium working

with the help of chatgpt, I updated my development.nix to include the settings needed to use selenium

Here are the specific changes

adding chromium and chrome-driver

Home manager variables

I added the following variables

adding SSL_CERT_FILE

Apparently if I didn’t include the SSL_CERT_FILE I would get this error

  File "/home/bedhedd/.local/share/uv/python/cpython-3.13.3-linux-x86_64-gnu/lib/python3.13/urllib/request.py", line 1322, in do_open
    raise URLError(err)
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028)>
adding SE_CHROMEDRIVER

Apparently if I didn’t include SE_CHROMEDRIVER, I would get a value error

    raise ValueError("No chrome executable found on PATH")
ValueError: No chrome executable found on PATH

for whatever reason, fish couldn’t pull the variables from home manager, so I had to include it within the shell start

2 Likes

Hi, thanks for a detailed log of migrating to NixOS - I am considering it also, so a detailed log of Your experience is valuable.

Out of curiosity: why guake? KDE has yakuake which works in a very similar way. I’ve used it for years and never had a problem with it - even under wayland (fedora 41).

3 Likes

I am used to guake. The last time I tested yakuake, on KDE with Wayland on fedora, it kept showing up on the wrong screen

Probably fixed, but I didn’t feel like configuring yakuake

2 Likes

adding my laptop config

After setting up my desktop, I wanted to add my laptop
I followed my instructions from my readme

I duplicated the generic-machine folder and renamed it to think-nix. I also added tlp to the configuration.nix to manage my battery better.

After that I decided to get my nvidia 950m working. I found that the suggested nixos-hardware was the solution

Fortunately for me it turns out the lenovo think pad e470 had the same setup as my laptop.
https://www.lenovo.com/us/en/p/laptops/thinkpad/thinkpade/thinkpad-e470/22tp2tee470

I had to check if the 7500u was kaby lake because the name didn’t look right
https://www.intel.com/content/www/us/en/products/sku/95451/intel-core-i77500u-processor-4m-cache-up-to-3-50-ghz/specifications.html

so it was as simple as adding a import

After it finished building, I rebooted my system, once I logged in I ran nvdia-smi to check if the driver was detected

nvidia-smi
Wed Jul  9 19:22:27 2025       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 570.144                Driver Version: 570.144        CUDA Version: 12.8     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce GTX 950M        Off |   00000000:01:00.0 Off |                  N/A |
| N/A   52C    P8            N/A  /  200W |       0MiB /   2048MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
                                                                                         
+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|  No running processes found                                                             |
+-----------------------------------------------------------------------------------------+

adding open with vscodium to dolphin’s context menu

I use vscodium for development projects. A feature that saves me time is opening a project directory from the file explorer with vscodium.

By default in nix and kde, I could click the checkbox Always open Folder files with the chosen app and the app would get added.


However, it did not remember between sessions.

I got it working using this entry in plasma manager

If you want to view the other changes I made, the commit is here

how I figured it out with chatgpt

I started by configuring it using the settings menu.


Afterwards I asked chatgpt how to find the configuration. Chatgpt suggested I use

gio mime inode/directory
Default application for β€œinode/directory”: org.kde.dolphin.desktop
Registered applications:
        org.kde.kate.desktop
        org.kde.dolphin.desktop
        org.kde.gwenview.desktop
Recommended applications:
        org.kde.kate.desktop
        org.kde.dolphin.desktop
        org.kde.gwenview.desktop

 xdg-mime query default 
inode/directory
org.kde.dolphin.desktop

However, vscodium didn’t show up. Chatgpt suggested this snippet

{
  xdg.desktopEntries.vscodium-folder = {
    name        = "VSCodium (Folder)";
    genericName = "Source-code Editor";
    exec        = "codium %F";
    icon        = "codium";
    mimeType    = [ "inode/directory" ];
    categories  = [ "Utility" "Development" "TextEditor" "IDE" ];
    terminal    = false;
  };
}

It worked, but I wanted to use the vscodium icon. After some further debugging to figure out where the vscodium icon is stored. The breakthrough was to attach a screenshot of the vscodium entry
image

Chatgpt then suggested I run this command

grep '^Icon=' /nix/stor
e/*codium*/share/applications/codium.desktop
Icon=vscodium
Icon=vscodium

I was able to add the entry vscodium to use the icon as well


I had chatgpt summarize the session
1 Like

This worked for my dad’s config but when I tried switching this out for other themes such as vgui, it didn’t work.

chatgpt suggested running this command to see what theme kdeglobals sets

grep -Po '(?<=LookAndFeelPackage=).*' ~/.config/kdeglobals
com.valve.vapor.desktop

as well as get the valid set of themes using the command

lookandfeeltool --list
org.kde.breezetwilight.desktop
org.kde.breeze.desktop
org.kde.breezedark.desktop
com.valve.vgui.desktop
com.valve.vapor.desktop

When I set the theme from the system menu, the output changed to the other selected theme. However, when I declared it in my kde-home, I got inconsistent behavior.

some further debugging with chatgpt
where it suggested restarting the plasma shell service for plasma 6

systemctl --user restart plasma-plasmashell.service

and

readlink -f ~/.config/kdeglobals

to get where it was stored. To figure out if flakes rebuild the kdeglobals file, I deleted the it. Unfortunately, a rebuild did not solve re-link the file. When I set another theme in the system panel it regenerated the file.

When I asked Chatgpt to help me figure out how to regenerate the file it got really hung up with suggesting home-manager switch which wasn’t a valid command in my setup.

Eventually Chatgpt finally suggested messing with the overrideConfig value. Somehow the training knowledge included this parameter because when I asked it to explain the variable, it didn’t include any linked sources

Adding overrideConfig seemed to manually force a relinking. It seems kinda odd that home manager doesn’t have this automatically applied, but I didn’t feel like researching the reason

I’ve been lurking and reading your updates.

Based on your config and using Chatgpt I decided to write a setup wizard to make it easier to share your config

lmk if you want me to start a pr

1 Like

Nice start a PR and I’ll look later

I ran into some git issues around history, but here is the pr

also updated your instructions

1 Like

merged

getting kde to have consistent panel configuration

Today I finally got around to declaratively configure the taskbar. Everytime I would login, it would always reset to the kde default of settings, files explorer, and browser. I want it so that it is browser, files, strawberry, and steam.
image
Ultimately what worked was this section

          {
            name = "org.kde.plasma.icontasks";
            config = {
              "launchers" = [
                "preferred://browser"
                "preferred://filemanager"
                "applications:org.strawberrymusicplayer.strawberry.desktop"
                "applications:steam.desktop"
              ];

              "General/sortingStrategy"   = 0;
              "General/separateLaunchers" = true;
            };
          }

for context it needs the panels section

    panels = lib.mkForce [
      {
        screen   = "all";
        location = "bottom";
        height   = 64;
        floating = false;
        hiding   = "dodgewindows";

        widgets = [
          # Kickoff
          {
            name = "org.kde.plasma.kickoff";
            config = {
              PreloadWeight   = 100;
              popupHeight     = 508;
              popupWidth      = 647;
              "General/icon"  = "distributor-logo-steamdeck";
            };
          }

          # Pager
          "org.kde.plasma.pager"

          # Icon Tasks
          {
            name = "org.kde.plasma.icontasks";
            config = {
              "launchers" = [
                "preferred://browser"
                "preferred://filemanager"
                "applications:org.strawberrymusicplayer.strawberry.desktop"
                "applications:steam.desktop"
              ];

              "General/sortingStrategy"   = 0;
              "General/separateLaunchers" = true;
            };
          }

I tried minimal prompting from chatgpt, but it kept trying to use general launchers. The process to getting a better question, I asked where kde stores panel/task manager, getting the steam/strawberry config block, and put it into nix.

Once I got it working i committed it here

I had chatgpt summarize the session


customizing kde panel to include stats on the panel

image

I used a similar process for network activity, cpu and memory usage. I ran the command
cat ~/.config/plasma-org.kde.plasma.desktop-appletsrc to get the configurations for the panels. Unfortunately, the customizations I made to duplicate the cpu did not work for the gpu when I had chatgpt translate it into a plasma manager config so I excluded it.

I passed the full config, but you might want to be a bit more selective and get the relevant configs for the system monitor section

[Containments][726][Applets][730]
immutability=1
plugin=org.kde.plasma.systemmonitor.net

[Containments][726][Applets][730][Configuration]
Appearance/chartFace=org.kde.ksysguard.linechart
Appearance/title=Network Speed
CurrentPreset=org.kde.plasma.systemmonitor
PreloadWeight=90
SensorColors/network/all/download=0,255,255
SensorColors/network/all/upload=170,0,255
Sensors/highPrioritySensorIds=network/all/download,network/all/upload
popupHeight=200
popupWidth=210

[Containments][726][Applets][730][Configuration][Appearance]
chartFace=org.kde.ksysguard.linechart
title=Network Speed

[Containments][726][Applets][730][Configuration][SensorColors]
network/all/download=149,136,49
network/all/upload=49,62,149

[Containments][726][Applets][730][Configuration][Sensors]
highPrioritySensorIds=["network/all/download","network/all/upload"]

[Containments][726][Applets][731]
immutability=1
plugin=org.kde.plasma.systemmonitor.cpucore

[Containments][726][Applets][731][Configuration]
Appearance/chartFace=org.kde.ksysguard.barchart
Appearance/title=Individual Core Usage
CurrentPreset=org.kde.plasma.systemmonitor
PreloadWeight=85
Sensors/highPrioritySensorIds=cpu/cpu.*/usage
Sensors/totalSensors=cpu/all/usage
popupHeight=509
popupWidth=306

[Containments][726][Applets][731][Configuration][Appearance]
chartFace=org.kde.ksysguard.barchart
title=Individual Core Usage

[Containments][726][Applets][731][Configuration][SensorColors]
cpu/cpu.*/usage=149,136,49
cpu/cpu0/usage=149,136,49
cpu/cpu1/usage=125,149,49
cpu/cpu10/usage=111,49,149
cpu/cpu11/usage=149,49,149
cpu/cpu12/usage=149,49,112
cpu/cpu13/usage=149,49,75
cpu/cpu14/usage=149,61,49
cpu/cpu15/usage=149,99,49
cpu/cpu2/usage=87,149,49
cpu/cpu3/usage=50,149,49
cpu/cpu4/usage=49,149,86
cpu/cpu5/usage=49,149,124
cpu/cpu6/usage=49,137,149
cpu/cpu7/usage=49,100,149
cpu/cpu8/usage=49,62,149
cpu/cpu9/usage=74,49,149

[Containments][726][Applets][731][Configuration][Sensors]
highPrioritySensorIds=["cpu/cpu.*/usage"]
totalSensors=["cpu/all/usage"]

[Containments][726][Applets][732]
immutability=1
plugin=org.kde.plasma.systemmonitor.memory

[Containments][726][Applets][732][Configuration]
Appearance/chartFace=org.kde.ksysguard.piechart
Appearance/title=Memory Usage
CurrentPreset=org.kde.plasma.systemmonitor
PreloadWeight=100
SensorColors/memory/physical/used=0,0,255
Sensors/highPrioritySensorIds=memory/physical/used
Sensors/lowPrioritySensorIds=memory/physical/total
Sensors/totalSensors=memory/physical/usedPercent
popupHeight=240
popupWidth=244

[Containments][726][Applets][732][Configuration][Appearance]
chartFace=org.kde.ksysguard.piechart
title=Memory Usage

[Containments][726][Applets][732][Configuration][SensorColors]
memory/physical/used=149,136,49

[Containments][726][Applets][732][Configuration][Sensors]
highPrioritySensorIds=["memory/physical/used"]
lowPrioritySensorIds=["memory/physical/total"]
totalSensors=["memory/physical/usedPercent"]

chatgpt returned this config within the panels.widgets list

          # System Monitor: Network
          {
            name = "org.kde.plasma.systemmonitor.net";
            config = {
              CurrentPreset = "org.kde.plasma.systemmonitor";
              PreloadWeight = 90;
              popupHeight   = 200;
              popupWidth    = 210;

              "Appearance/chartFace" = "org.kde.ksysguard.linechart";
              "Appearance/title"     = "Network Speed";

              "Sensors/highPrioritySensorIds" = [
                "network/all/download"
                "network/all/upload"
              ];

              "SensorColors/network/all/download" = "0,255,255";
              "SensorColors/network/all/upload"   = "170,0,255";
            };
          }

          # System Monitor: CPU cores
          {
            name = "org.kde.plasma.systemmonitor.cpucore";
            config = {
              CurrentPreset = "org.kde.plasma.systemmonitor";
              PreloadWeight = 65;
              popupHeight   = 386;
              popupWidth    = 306;

              "Appearance/chartFace" = "org.kde.ksysguard.barchart";
              "Appearance/title"     = "Individual Core Usage";

              "Sensors/highPrioritySensorIds" = [ "cpu/cpu.*/usage" ];
              "Sensors/totalSensors"          = [ "cpu/all/usage" ];
            };
          }

          # System Monitor: Memory
          {
            name = "org.kde.plasma.systemmonitor.memory";
            config = {
              CurrentPreset = "org.kde.plasma.systemmonitor";
              PreloadWeight = 95;
              popupHeight   = 240;
              popupWidth    = 244;

              "Appearance/chartFace" = "org.kde.ksysguard.piechart";
              "Appearance/title"     = "Memory Usage";

              "Sensors/highPrioritySensorIds" = [ "memory/physical/used" ];
              "Sensors/lowPrioritySensorIds"  = [ "memory/physical/total" ];
              "Sensors/totalSensors"          = [ "memory/physical/usedPercent" ];

              "SensorColors/memory/physical/used" = "0,0,255";
            };
          }

          "org.kde.plasma.marginsseparator"
          "org.kde.plasma.systemtray"

          {
            name = "org.kde.plasma.digitalclock";
            config = {
              popupHeight = 400;
              popupWidth  = 560;
              "Appearance/fontWeight" = 400;
            };
          }

          "org.kde.plasma.showdesktop"
        ];
      }

you can view it from my config here

Once I got the config working, I committed it here

getting synced wallpapers for desktop and lock screen

The last thing I wanted was to have my kde cycle through a directory of desktop backgrounds.

I started by asking chatgpt, but it wanted to use a custom xdg config entry. I then had it look it up instead.
It suggested this, but I wanted it to integrate back into my existing config

# modules/kde-home.nix
{ config, lib, pkgs, plasma-manager, ... }:

let
  wpDir = "/home/${config.home.username}/Pictures/desktop-backgrounds";
in {
  programs.plasma = {
    enable = true;
    overrideConfig = true;

    # Desktop slideshow (Plasma Manager "workspace" module)
    workspace.wallpaperSlideShow = {
      path     = wpDir;   # can be a string or a list of dirs
      interval = 900;     # seconds = 15 minutes
      # optional:
      # randomize = true;
      # fillMode  = "zoom";  # other values include: "fit", "stretch", "center", etc.
    };

    # Lock screen slideshow (Plasma Manager "kscreenlocker" module)
    kscreenlocker.appearance.wallpaperSlideShow = {
      path     = wpDir;
      interval = 900;
      # randomize = true;
      # fillMode  = "zoom";
    };
  };
}

Chatgpt based it on the following appendix items from plasma manager
wallpaper: Appendix A. Plasma-Manager Options
lockscreen: Appendix A. Plasma-Manager Options

programs.plasma.workspace.wallpaperSlideShow

    Submodule for configuring the wallpaper slideshow. Needs a directory with wallpapers and an interval length.

    Type: null or (submodule)

    Default: null

    Example: { path = "${pkgs.kdePackages.plasma-workspace-wallpapers}/share/wallpapers/"; }

    Declared by:
    <plasma-manager/modules/workspace.nix>
programs.plasma.workspace.wallpaperSlideShow.interval

    The length between wallpaper switches.

    Type: signed integer

    Default: 300

    Declared by:
    <plasma-manager/modules/workspace.nix> 

programs.plasma.kscreenlocker.appearance.wallpaper

    The wallpaper for the lockscreen. Can be either the path to an image file or a KPackage.

    Type: null or absolute path

    Default: null

    Example: "${pkgs.kdePackages.plasma-workspace-wallpapers}/share/wallpapers/Kay/contents/images/1080x1920.png"

    Declared by:
    <plasma-manager/modules/kscreenlocker.nix> 

This works if I only had a single monitor, but for multi-monitor setups it doesn’t ensure that the same image is on every monitor. I asked chatgpt to look for a way to synchronize it across all monitors. It came up with some javascript and a systemd user timer to make a slideshow

after building the config and getting it working smoothly, I committed it to my repo

rebinding guake’s F12 toggle window to super + `

I wanted to rebind the Guake default F12 to super + ` , so I added these entries.

unbind kde’s default super + `

Kde has the same bind for cycling through windows, so I had chatgpt create the entry to unbind it

shortcuts = {
      kwin = {
        # Keep only Alt+` and drop Meta+`
        "Walk Through Windows of Current Application" = [ "Alt+`" ];
        "Walk Through Windows of Current Application (Reverse)" = [ "Alt+~" ];

        # If these β€œAlternative” bindings exist on your system,
        # clearing them ensures Super/Meta isn't grabbed anywhere:
        "Walk Through Windows Alternative" = [ ];
        "Walk Through Windows Alternative (Reverse)" = [ ];
        };
    };

see the context here

commit is here, with some typos lol

bind guake toggle window to super + `

Once I got unbound kde, I had chatgpt come up with the entry for guake. I fed it my guake module and this was a entry it suggested to the dconf.settings dictionary

    "org/guake/keybindings/global" = {
      show-hide = "<Super>grave";
    };

see the full context here

commit is here

resolving ssh.enableDefaultConfig

I encountered this warning

bedhedd profile: `programs.ssh` default values will be removed in the future.
                    Consider setting `programs.ssh.enableDefaultConfig` to false,
                    and manually set the default values you want to keep at
                    `programs.ssh.matchBlocks."*"`.

I passed the error and multiple ssh file to chatgpt, it suggested these changes

Here’s the diff

then I had it check online

declaring kzones within kwin

After doing some exploration with Kwin and Kzones, I wanted to make it apart of my public nix config

the git diff is here

I started by applying the kwin and kzone by using the gui from the previous post

I copied the file output of the script and pasted it in my dotfiles folder within the nix config

I started by asking chatgpt how to check for kwin and kzone config file, which suggested I run this command

 cat ~/.config/kwinrc | grep -A 15 "Script-kzones"

which returned

[Script-kzones]
autoSnapNewWindows=false
displayOsdMessages=true
edgeSnapTriggerDistance=Medium
enableEdgeSnapping=false
enableZoneOverlay=true
enableZoneSelector=false
layouts=\x5b{"name":"xiphergrid2 (converted)","padding":0,"zones":\x5b{"height":16.667,"width":33.333,"x":33.333,"y":0.0},{"height":33.333,"width":33.333,"x":33.333,"y":0.0},{"height":50.0,"width":33.333,"x":33.333,"y":0.0},{"height":66.667,"width":33.333,"x":33.333,"y":0.0},{"height":83.333,"width":33.333,"x":33.333,"y":0.0},{"height":100.0,"width":33.333,"x":33.333,"y":0.0},{"height":16.667,"width":33.333,"x":33.333,"y":83.333},{"height":33.333,"width":33.333,"x":33.333,"y":66.667},{"height":50.0,"width":33.333,"x":33.333,"y":50.0},{"height":66.667,"width":33.333,"x":33.333,"y":33.333},{"height":83.333,"width":33.333,"x":33.333,"y":16.667},{"height":33.333,"width":16.667,"x":0.0,"y":33.333},{"height":33.333,"width":33.333,"x":0.0,"y":33.333},{"height":33.333,"width":50.0,"x":0.0,"y":33.333},{"height":33.333,"width":66.667,"x":0.0,"y":33.333},{"height":33.333,"width":83.333,"x":0.0,"y":33.333},{"height":33.333,"width":100.0,"x":0.0,"y":33.333},{"height":33.333,"width":16.667,"x":83.333,"y":33.333},{"height":33.333,"width":33.333,"x":66.667,"y":33.333},{"height":33.333,"width":50.0,"x":50.0,"y":33.333},{"height":33.333,"width":66.667,"x":33.333,"y":33.333},{"height":33.333,"width":83.333,"x":16.667,"y":33.333},{"height":16.667,"width":16.667,"x":0.0,"y":0.0},{"height":16.667,"width":33.333,"x":0.0,"y":0.0},{"height":16.667,"width":50.0,"x":0.0,"y":0.0},{"height":16.667,"width":66.667,"x":0.0,"y":0.0},{"height":16.667,"width":83.333,"x":0.0,"y":0.0},{"height":33.333,"width":16.667,"x":0.0,"y":0.0},{"height":33.333,"width":33.333,"x":0.0,"y":0.0},{"height":33.333,"width":50.0,"x":0.0,"y":0.0},{"height":33.333,"width":66.667,"x":0.0,"y":0.0},{"height":33.333,"width":83.333,"x":0.0,"y":0.0},{"height":50.0,"width":16.667,"x":0.0,"y":0.0},{"height":50.0,"width":33.333,"x":0.0,"y":0.0},{"height":50.0,"width":50.0,"x":0.0,"y":0.0},{"height":50.0,"width":66.667,"x":0.0,"y":0.0},{"height":50.0,"width":83.333,"x":0.0,"y":0.0},{"height":66.667,"width":16.667,"x":0.0,"y":0.0},{"height":66.667,"width":33.333,"x":0.0,"y":0.0},{"height":66.667,"width":50.0,"x":0.0,"y":0.0},{"height":66.667,"width":66.667,"x":0.0,"y":0.0},{"height":66.667,"width":83.333,"x":0.0,"y":0.0},{"height":83.333,"width":16.667,"x":0.0,"y":0.0},{"height":83.333,"width":33.333,"x":0.0,"y":0.0},{"height":83.333,"width":50.0,"x":0.0,"y":0.0},{"height":83.333,"width":66.667,"x":0.0,"y":0.0},{"height":83.333,"width":83.333,"x":0.0,"y":0.0},{"height":16.667,"width":100.0,"x":0.0,"y":0.0},{"height":33.333,"width":100.0,"x":0.0,"y":0.0},{"height":50.0,"width":100.0,"x":0.0,"y":0.0},{"height":66.667,"width":100.0,"x":0.0,"y":0.0},{"height":83.333,"width":100.0,"x":0.0,"y":0.0},{"height":16.667,"width":16.667,"x":83.333,"y":0.0},{"height":16.667,"width":33.333,"x":66.667,"y":0.0},{"height":16.667,"width":50.0,"x":50.0,"y":0.0},{"height":16.667,"width":66.667,"x":33.333,"y":0.0},{"height":16.667,"width":83.333,"x":16.667,"y":0.0},{"height":33.333,"width":16.667,"x":83.333,"y":0.0},{"height":33.333,"width":33.333,"x":66.667,"y":0.0},{"height":33.333,"width":50.0,"x":50.0,"y":0.0},{"height":33.333,"width":66.667,"x":33.333,"y":0.0},{"height":33.333,"width":83.333,"x":16.667,"y":0.0},{"height":50.0,"width":16.667,"x":83.333,"y":0.0},{"height":50.0,"width":33.333,"x":66.667,"y":0.0},{"height":50.0,"width":50.0,"x":50.0,"y":0.0},{"height":50.0,"width":66.667,"x":33.333,"y":0.0},{"height":50.0,"width":83.333,"x":16.667,"y":0.0},{"height":66.667,"width":16.667,"x":83.333,"y":0.0},{"height":66.667,"width":33.333,"x":66.667,"y":0.0},{"height":66.667,"width":50.0,"x":50.0,"y":0.0},{"height":66.667,"width":66.667,"x":33.333,"y":0.0},{"height":66.667,"width":83.333,"x":16.667,"y":0.0},{"height":83.333,"width":16.667,"x":83.333,"y":0.0},{"height":83.333,"width":33.333,"x":66.667,"y":0.0},{"height":83.333,"width":50.0,"x":50.0,"y":0.0},{"height":83.333,"width":66.667,"x":33.333,"y":0.0},{"height":83.333,"width":83.333,"x":16.667,"y":0.0},{"height":100.0,"width":16.667,"x":83.333,"y":0.0},{"height":100.0,"width":33.333,"x":66.667,"y":0.0},{"height":100.0,"width":50.0,"x":50.0,"y":0.0},{"height":100.0,"width":66.667,"x":33.333,"y":0.0},{"height":100.0,"width":83.333,"x":16.667,"y":0.0},{"height":16.667,"width":16.667,"x":83.333,"y":83.333},{"height":16.667,"width":33.333,"x":66.667,"y":83.333},{"height":16.667,"width":50.0,"x":50.0,"y":83.333},{"height":16.667,"width":66.667,"x":33.333,"y":83.333},{"height":16.667,"width":83.333,"x":16.667,"y":83.333},{"height":33.333,"width":16.667,"x":83.333,"y":66.667},{"height":33.333,"width":33.333,"x":66.667,"y":66.667},{"height":33.333,"width":50.0,"x":50.0,"y":66.667},{"height":33.333,"width":66.667,"x":33.333,"y":66.667},{"height":33.333,"width":83.333,"x":16.667,"y":66.667},{"height":50.0,"width":16.667,"x":83.333,"y":50.0},{"height":50.0,"width":33.333,"x":66.667,"y":50.0},{"height":50.0,"width":50.0,"x":50.0,"y":50.0},{"height":50.0,"width":66.667,"x":33.333,"y":50.0},{"height":50.0,"width":83.333,"x":16.667,"y":50.0},{"height":66.667,"width":16.667,"x":83.333,"y":33.333},{"height":66.667,"width":33.333,"x":66.667,"y":33.333},{"height":66.667,"width":50.0,"x":50.0,"y":33.333},{"height":66.667,"width":66.667,"x":33.333,"y":33.333},{"height":66.667,"width":83.333,"x":16.667,"y":33.333},{"height":83.333,"width":16.667,"x":83.333,"y":16.667},{"height":83.333,"width":33.333,"x":66.667,"y":16.667},{"height":83.333,"width":50.0,"x":50.0,"y":16.667},{"height":83.333,"width":66.667,"x":33.333,"y":16.667},{"height":83.333,"width":83.333,"x":16.667,"y":16.667},{"height":16.667,"width":100.0,"x":0.0,"y":83.333},{"height":33.333,"width":100.0,"x":0.0,"y":66.667},{"height":50.0,"width":100.0,"x":0.0,"y":50.0},{"height":66.667,"width":100.0,"x":0.0,"y":33.333},{"height":83.333,"width":100.0,"x":0.0,"y":16.667},{"height":16.667,"width":16.667,"x":0.0,"y":83.333},{"height":16.667,"width":33.333,"x":0.0,"y":83.333},{"height":16.667,"width":50.0,"x":0.0,"y":83.333},{"height":16.667,"width":66.667,"x":0.0,"y":83.333},{"height":16.667,"width":83.333,"x":0.0,"y":83.333},{"height":33.333,"width":16.667,"x":0.0,"y":66.667},{"height":33.333,"width":33.333,"x":0.0,"y":66.667},{"height":33.333,"width":50.0,"x":0.0,"y":66.667},{"height":33.333,"width":66.667,"x":0.0,"y":66.667},{"height":33.333,"width":83.333,"x":0.0,"y":66.667},{"height":50.0,"width":16.667,"x":0.0,"y":50.0},{"height":50.0,"width":33.333,"x":0.0,"y":50.0},{"height":50.0,"width":50.0,"x":0.0,"y":50.0},{"height":50.0,"width":66.667,"x":0.0,"y":50.0},{"height":50.0,"width":83.333,"x":0.0,"y":50.0},{"height":66.667,"width":16.667,"x":0.0,"y":33.333},{"height":66.667,"width":33.333,"x":0.0,"y":33.333},{"height":66.667,"width":50.0,"x":0.0,"y":33.333},{"height":66.667,"width":66.667,"x":0.0,"y":33.333},{"height":66.667,"width":83.333,"x":0.0,"y":33.333},{"height":83.333,"width":16.667,"x":0.0,"y":16.667},{"height":83.333,"width":33.333,"x":0.0,"y":16.667},{"height":83.333,"width":50.0,"x":0.0,"y":16.667},{"height":83.333,"width":66.667,"x":0.0,"y":16.667},{"height":83.333,"width":83.333,"x":0.0,"y":16.667},{"height":100.0,"width":16.667,"x":0.0,"y":0.0},{"height":100.0,"width":33.333,"x":0.0,"y":0.0},{"height":100.0,"width":50.0,"x":0.0,"y":0.0},{"height":100.0,"width":66.667,"x":0.0,"y":0.0},{"height":100.0,"width":83.333,"x":0.0,"y":0.0},{"height":33.333,"width":33.333,"x":33.333,"y":33.333},{"height":66.667,"width":60.0,"x":20.0,"y":16.667},{"height":80.0,"width":66.667,"x":16.667,"y":10.0},{"height":83.333,"width":80.0,"x":10.0,"y":8.333}\x5d}\x5d
layoutsJson=\x5b{"name":"xiphergrid2 (converted)","padding":0,"zones":\x5b{"height":16.667,"width":33.333,"x":33.333,"y":0.0},{"height":33.333,"width":33.333,"x":33.333,"y":0.0},{"height":50.0,"width":33.333,"x":33.333,"y":0.0},{"height":66.667,"width":33.333,"x":33.333,"y":0.0},{"height":83.333,"width":33.333,"x":33.333,"y":0.0},{"height":100.0,"width":33.333,"x":33.333,"y":0.0},{"height":16.667,"width":33.333,"x":33.333,"y":83.333},{"height":33.333,"width":33.333,"x":33.333,"y":66.667},{"height":50.0,"width":33.333,"x":33.333,"y":50.0},{"height":66.667,"width":33.333,"x":33.333,"y":33.333},{"height":83.333,"width":33.333,"x":33.333,"y":16.667},{"height":33.333,"width":16.667,"x":0.0,"y":33.333},{"height":33.333,"width":33.333,"x":0.0,"y":33.333},{"height":33.333,"width":50.0,"x":0.0,"y":33.333},{"height":33.333,"width":66.667,"x":0.0,"y":33.333},{"height":33.333,"width":83.333,"x":0.0,"y":33.333},{"height":33.333,"width":100.0,"x":0.0,"y":33.333},{"height":33.333,"width":16.667,"x":83.333,"y":33.333},{"height":33.333,"width":33.333,"x":66.667,"y":33.333},{"height":33.333,"width":50.0,"x":50.0,"y":33.333},{"height":33.333,"width":66.667,"x":33.333,"y":33.333},{"height":33.333,"width":83.333,"x":16.667,"y":33.333},{"height":16.667,"width":16.667,"x":0.0,"y":0.0},{"height":16.667,"width":33.333,"x":0.0,"y":0.0},{"height":16.667,"width":50.0,"x":0.0,"y":0.0},{"height":16.667,"width":66.667,"x":0.0,"y":0.0},{"height":16.667,"width":83.333,"x":0.0,"y":0.0},{"height":33.333,"width":16.667,"x":0.0,"y":0.0},{"height":33.333,"width":33.333,"x":0.0,"y":0.0},{"height":33.333,"width":50.0,"x":0.0,"y":0.0},{"height":33.333,"width":66.667,"x":0.0,"y":0.0},{"height":33.333,"width":83.333,"x":0.0,"y":0.0},{"height":50.0,"width":16.667,"x":0.0,"y":0.0},{"height":50.0,"width":33.333,"x":0.0,"y":0.0},{"height":50.0,"width":50.0,"x":0.0,"y":0.0},{"height":50.0,"width":66.667,"x":0.0,"y":0.0},{"height":50.0,"width":83.333,"x":0.0,"y":0.0},{"height":66.667,"width":16.667,"x":0.0,"y":0.0},{"height":66.667,"width":33.333,"x":0.0,"y":0.0},{"height":66.667,"width":50.0,"x":0.0,"y":0.0},{"height":66.667,"width":66.667,"x":0.0,"y":0.0},{"height":66.667,"width":83.333,"x":0.0,"y":0.0},{"height":83.333,"width":16.667,"x":0.0,"y":0.0},{"height":83.333,"width":33.333,"x":0.0,"y":0.0},{"height":83.333,"width":50.0,"x":0.0,"y":0.0},{"height":83.333,"width":66.667,"x":0.0,"y":0.0},{"height":83.333,"width":83.333,"x":0.0,"y":0.0},{"height":16.667,"width":100.0,"x":0.0,"y":0.0},{"height":33.333,"width":100.0,"x":0.0,"y":0.0},{"height":50.0,"width":100.0,"x":0.0,"y":0.0},{"height":66.667,"width":100.0,"x":0.0,"y":0.0},{"height":83.333,"width":100.0,"x":0.0,"y":0.0},{"height":16.667,"width":16.667,"x":83.333,"y":0.0},{"height":16.667,"width":33.333,"x":66.667,"y":0.0},{"height":16.667,"width":50.0,"x":50.0,"y":0.0},{"height":16.667,"width":66.667,"x":33.333,"y":0.0},{"height":16.667,"width":83.333,"x":16.667,"y":0.0},{"height":33.333,"width":16.667,"x":83.333,"y":0.0},{"height":33.333,"width":33.333,"x":66.667,"y":0.0},{"height":33.333,"width":50.0,"x":50.0,"y":0.0},{"height":33.333,"width":66.667,"x":33.333,"y":0.0},{"height":33.333,"width":83.333,"x":16.667,"y":0.0},{"height":50.0,"width":16.667,"x":83.333,"y":0.0},{"height":50.0,"width":33.333,"x":66.667,"y":0.0},{"height":50.0,"width":50.0,"x":50.0,"y":0.0},{"height":50.0,"width":66.667,"x":33.333,"y":0.0},{"height":50.0,"width":83.333,"x":16.667,"y":0.0},{"height":66.667,"width":16.667,"x":83.333,"y":0.0},{"height":66.667,"width":33.333,"x":66.667,"y":0.0},{"height":66.667,"width":50.0,"x":50.0,"y":0.0},{"height":66.667,"width":66.667,"x":33.333,"y":0.0},{"height":66.667,"width":83.333,"x":16.667,"y":0.0},{"height":83.333,"width":16.667,"x":83.333,"y":0.0},{"height":83.333,"width":33.333,"x":66.667,"y":0.0},{"height":83.333,"width":50.0,"x":50.0,"y":0.0},{"height":83.333,"width":66.667,"x":33.333,"y":0.0},{"height":83.333,"width":83.333,"x":16.667,"y":0.0},{"height":100.0,"width":16.667,"x":83.333,"y":0.0},{"height":100.0,"width":33.333,"x":66.667,"y":0.0},{"height":100.0,"width":50.0,"x":50.0,"y":0.0},{"height":100.0,"width":66.667,"x":33.333,"y":0.0},{"height":100.0,"width":83.333,"x":16.667,"y":0.0},{"height":16.667,"width":16.667,"x":83.333,"y":83.333},{"height":16.667,"width":33.333,"x":66.667,"y":83.333},{"height":16.667,"width":50.0,"x":50.0,"y":83.333},{"height":16.667,"width":66.667,"x":33.333,"y":83.333},{"height":16.667,"width":83.333,"x":16.667,"y":83.333},{"height":33.333,"width":16.667,"x":83.333,"y":66.667},{"height":33.333,"width":33.333,"x":66.667,"y":66.667},{"height":33.333,"width":50.0,"x":50.0,"y":66.667},{"height":33.333,"width":66.667,"x":33.333,"y":66.667},{"height":33.333,"width":83.333,"x":16.667,"y":66.667},{"height":50.0,"width":16.667,"x":83.333,"y":50.0},{"height":50.0,"width":33.333,"x":66.667,"y":50.0},{"height":50.0,"width":50.0,"x":50.0,"y":50.0},{"height":50.0,"width":66.667,"x":33.333,"y":50.0},{"height":50.0,"width":83.333,"x":16.667,"y":50.0},{"height":66.667,"width":16.667,"x":83.333,"y":33.333},{"height":66.667,"width":33.333,"x":66.667,"y":33.333},{"height":66.667,"width":50.0,"x":50.0,"y":33.333},{"height":66.667,"width":66.667,"x":33.333,"y":33.333},{"height":66.667,"width":83.333,"x":16.667,"y":33.333},{"height":83.333,"width":16.667,"x":83.333,"y":16.667},{"height":83.333,"width":33.333,"x":66.667,"y":16.667},{"height":83.333,"width":50.0,"x":50.0,"y":16.667},{"height":83.333,"width":66.667,"x":33.333,"y":16.667},{"height":83.333,"width":83.333,"x":16.667,"y":16.667},{"height":16.667,"width":100.0,"x":0.0,"y":83.333},{"height":33.333,"width":100.0,"x":0.0,"y":66.667},{"height":50.0,"width":100.0,"x":0.0,"y":50.0},{"height":66.667,"width":100.0,"x":0.0,"y":33.333},{"height":83.333,"width":100.0,"x":0.0,"y":16.667},{"height":16.667,"width":16.667,"x":0.0,"y":83.333},{"height":16.667,"width":33.333,"x":0.0,"y":83.333},{"height":16.667,"width":50.0,"x":0.0,"y":83.333},{"height":16.667,"width":66.667,"x":0.0,"y":83.333},{"height":16.667,"width":83.333,"x":0.0,"y":83.333},{"height":33.333,"width":16.667,"x":0.0,"y":66.667},{"height":33.333,"width":33.333,"x":0.0,"y":66.667},{"height":33.333,"width":50.0,"x":0.0,"y":66.667},{"height":33.333,"width":66.667,"x":0.0,"y":66.667},{"height":33.333,"width":83.333,"x":0.0,"y":66.667},{"height":50.0,"width":16.667,"x":0.0,"y":50.0},{"height":50.0,"width":33.333,"x":0.0,"y":50.0},{"height":50.0,"width":50.0,"x":0.0,"y":50.0},{"height":50.0,"width":66.667,"x":0.0,"y":50.0},{"height":50.0,"width":83.333,"x":0.0,"y":50.0},{"height":66.667,"width":16.667,"x":0.0,"y":33.333},{"height":66.667,"width":33.333,"x":0.0,"y":33.333},{"height":66.667,"width":50.0,"x":0.0,"y":33.333},{"height":66.667,"width":66.667,"x":0.0,"y":33.333},{"height":66.667,"width":83.333,"x":0.0,"y":33.333},{"height":83.333,"width":16.667,"x":0.0,"y":16.667},{"height":83.333,"width":33.333,"x":0.0,"y":16.667},{"height":83.333,"width":50.0,"x":0.0,"y":16.667},{"height":83.333,"width":66.667,"x":0.0,"y":16.667},{"height":83.333,"width":83.333,"x":0.0,"y":16.667},{"height":100.0,"width":16.667,"x":0.0,"y":0.0},{"height":100.0,"width":33.333,"x":0.0,"y":0.0},{"height":100.0,"width":50.0,"x":0.0,"y":0.0},{"height":100.0,"width":66.667,"x":0.0,"y":0.0},{"height":100.0,"width":83.333,"x":0.0,"y":0.0},{"height":33.333,"width":33.333,"x":33.333,"y":33.333},{"height":66.667,"width":60.0,"x":20.0,"y":16.667},{"height":80.0,"width":66.667,"x":16.667,"y":10.0},{"height":83.333,"width":80.0,"x":10.0,"y":8.333}\x5d}\x5d
overlayHighlightWhen=cursorOverInd
overlayShowWhen=startMove
rememberWindowGeometries=true
selectorTriggerDistance=Medium
trackActiveLayoutPerScreen=false
zoneOverlayIndicatorDisplay=1

I pasted the output and asked chatgpt to update my kde-home.nix with the path

chatgpt gave me this config

# modules/kde-home.nix
{ config, lib, pkgs, plasma-manager, ... }:
let
  wpDir        = "${config.home.homeDirectory}/Pictures/desktop backgrounds";
  intervalS    = 900; # seconds between wallpaper changes (sync cadence)

  # Script: pick frame deterministically and set same image on ALL desktops
  syncWallpapers = pkgs.writeShellScriptBin "sync-wallpapers" ''
    set -euo pipefail
    DIR=${lib.escapeShellArg wpDir}
    INTERVAL=${toString intervalS}

    # Collect images (stable order)
    mapfile -t FILES < <(${pkgs.findutils}/bin/find "$DIR" -type f \
      \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \) \
      | ${pkgs.coreutils}/bin/sort)

    [ "''${#FILES[@]}" -gt 0 ] || exit 0

    now=$(${pkgs.coreutils}/bin/date +%s)
    idx=$(( (now / INTERVAL) % ''${#FILES[@]} ))
    img="''${FILES[$idx]}"

    # Plasma JS: set same static image on every desktop
    js='var ds = desktops();
        for (var i = 0; i < ds.length; ++i) {
          var d = ds[i];
          d.wallpaperPlugin = "org.kde.image";
          d.currentConfigGroup = ["Wallpaper","org.kde.image","General"];
          d.writeConfig("Image", "file://'"$img"'" );
        }'
    ${pkgs.qt6.qttools}/bin/qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.evaluateScript "$js"
  '';

  # Read your repo JSON, normalize to a single-line JSON string
  kzLayouts = builtins.toJSON (builtins.fromJSON (builtins.readFile ../dotfiles/xiphergrid2_kzones.json));
in {
  programs.plasma = {
    enable = true;
    overrideConfig = true;

    workspace.lookAndFeel = "com.valve.vgui.desktop";

    # IMPORTANT: Do NOT use Plasma's slideshow for the desktop; our timer drives it.
    workspace.wallpaper =
      "${pkgs.kdePackages.plasma-workspace-wallpapers}/share/wallpapers/Patak/contents/images/1920x1080.jpg";

    # Lock screen can keep using slideshow (independent of desktop sync)
    kscreenlocker.appearance.wallpaperSlideShow = {
      path     = wpDir;
      interval = intervalS;
    };

    # -------------------------
    # KWin + KZones configuration
    # -------------------------
    configFile = {
      # Enable the KZones plugin ONCE (avoid duplicates elsewhere)
      "kwinrc"."Plugins"."kzonesEnabled".value = true;

      # KZones script settings
      "kwinrc"."Script-kzones" = {
        # Supply your layout JSON to both keys seen in your config
        "layouts".value     = kzLayouts;
        "layoutsJson".value = kzLayouts;

        # Match your UI settings / dump
        # 1 == "Only target zone"
        "zoneOverlayIndicatorDisplay".value = 1;

        # Toggles reflected from your screenshot
        "enableZoneSelector".value           = false;
        "selectorTriggerDistance".value      = "Medium";

        "enableZoneOverlay".value            = true;
        "overlayShowWhen".value              = "startMove";       # "I start moving a window"
        "overlayHighlightWhen".value         = "cursorOverInd";   # "My cursor is above the zone indicator"

        "enableEdgeSnapping".value           = false;
        "edgeSnapTriggerDistance".value      = "Medium";

        "rememberWindowGeometries".value     = true;
        "trackActiveLayoutPerScreen".value   = false;
        "autoSnapNewWindows".value           = false;
        "displayOsdMessages".value           = true;
      };

      # Optional: pin built-in KWin tiling padding from your dump
      "kwinrc"."Tiling"."padding".value = 4;

      # (Optional) If you want Xwayland scale pinned
      # "kwinrc"."Xwayland"."Scale".value = 1;
    };

    kwin = {
      nightLight = {
        enable = true;
        mode = "location";
        location.latitude  = "41.8781";
        location.longitude = "-87.6298";
        temperature.day   = 6500;
        temperature.night = 1800;
        transitionTime    = 30;
      };
    };

    shortcuts = {
      kwin = {
        "Walk Through Windows of Current Application" = [ "Alt+`" ];
        "Walk Through Windows of Current Application (Reverse)" = [ "Alt+~" ];
        "Walk Through Windows Alternative" = [ ];
        "Walk Through Windows Alternative (Reverse)" = [ ];
      };
    };

    # Panel layout (unchanged)
    panels = lib.mkForce [
      {
        screen   = "all";
        location = "bottom";
        height   = 64;
        floating = false;
        hiding   = "dodgewindows";

        widgets = [
          {
            name = "org.kde.plasma.kickoff";
            config = {
              PreloadWeight   = 100;
              popupHeight     = 508;
              popupWidth      = 647;
              "General/icon"  = "distributor-logo-steamdeck";
            };
          }
          "org.kde.plasma.pager"
          {
            name = "org.kde.plasma.icontasks";
            config = {
              "launchers" = [
                "preferred://browser"
                "preferred://filemanager"
                "applications:org.strawberrymusicplayer.strawberry.desktop"
                "applications:steam.desktop"
              ];
              "General/sortingStrategy"   = 0;
              "General/separateLaunchers" = true;
            };
          }
          {
            name = "org.kde.plasma.systemmonitor.net";
            config = {
              CurrentPreset = "org.kde.plasma.systemmonitor";
              PreloadWeight = 90;
              popupHeight   = 200;
              popupWidth    = 210;
              "Appearance/chartFace" = "org.kde.ksysguard.linechart";
              "Appearance/title"     = "Network Speed";
              "Sensors/highPrioritySensorIds" = [
                "network/all/download"
                "network/all/upload"
              ];
              "SensorColors/network/all/download" = "0,255,255";
              "SensorColors/network/all/upload"   = "170,0,255";
            };
          }
          {
            name = "org.kde.plasma.systemmonitor.cpucore";
            config = {
              CurrentPreset = "org.kde.plasma.systemmonitor";
              PreloadWeight = 65;
              popupHeight   = 386;
              popupWidth    = 306;
              "Appearance/chartFace" = "org.kde.ksysguard.barchart";
              "Appearance/title"     = "Individual Core Usage";
              "Sensors/highPrioritySensorIds" = [ "cpu/cpu.*/usage" ];
              "Sensors/totalSensors"          = [ "cpu/all/usage" ];
            };
          }
          {
            name = "org.kde.plasma.systemmonitor.memory";
            config = {
              CurrentPreset = "org.kde.plasma.systemmonitor";
              PreloadWeight = 95;
              popupHeight   = 240;
              popupWidth    = 244;
              "Appearance/chartFace" = "org.kde.ksysguard.piechart";
              "Appearance/title"     = "Memory Usage";
              "Sensors/highPrioritySensorIds" = [ "memory/physical/used" ];
              "Sensors/lowPrioritySensorIds"  = [ "memory/physical/total" ];
              "Sensors/totalSensors"          = [ "memory/physical/usedPercent" ];
              "SensorColors/memory/physical/used" = "0,0,255";
            };
          }
          "org.kde.plasma.marginsseparator"
          "org.kde.plasma.systemtray"
          {
            name = "org.kde.plasma.digitalclock";
            config = {
              popupHeight = 400;
              popupWidth  = 560;
              "Appearance/fontWeight" = 400;
            };
          }
          "org.kde.plasma.showdesktop"
        ];
      }
    ];

    # Run once at login so you start in sync immediately (after Plasma init)
    startup.desktopScript."sync-wallpapers-on-login" = {
      text = ''"${syncWallpapers}/bin/sync-wallpapers"'';
      priority = 3; # after other Plasma startup scripts
    };
  };

  # Reconfigure KWin so changes take effect without logout
  home.activation.reconfigureKWin = lib.mkAfter ''
    ${pkgs.qt6.qttools}/bin/qdbus org.kde.KWin /KWin reconfigure || true
  '';

  # Timer/service to update every intervalS seconds (keeps all monitors in lockstep)
  systemd.user.services.sync-wallpapers = {
    Unit.Description = "Sync KDE wallpapers across monitors";
    Service = {
      Type = "oneshot";
      ExecStart = "${syncWallpapers}/bin/sync-wallpapers";
    };
    Install.WantedBy = [ "default.target" ];
  };

  systemd.user.timers.sync-wallpapers = {
    Unit.Description = "Timer for synced wallpapers";
    Timer = {
      OnBootSec = "7s";
      OnUnitActiveSec = "${toString intervalS}s";
      AccuracySec = "1s";
    };
    Install.WantedBy = [ "timers.target" ];
  };

  # VSCodium desktop entry override
  xdg.desktopEntries.vscodium = {
    name        = "VSCodium";
    genericName = "Source-code Editor";
    exec        = "codium %F";
    icon        = "vscodium";
    mimeType    = [ "inode/directory" ];
    categories  = [ "Utility" "Development" "TextEditor" "IDE" ];
    terminal    = false;
  };

  # MIME associations
  xdg.mimeApps = {
    enable = true;
    associations.added."inode/directory"  = [ "vscodium.desktop" ];
    defaultApplications."inode/directory" = [ "org.kde.dolphin.desktop" ];
  };
}

Which I applied and what you’ll see for the final config