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
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
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
define the folder paths (folder for a given ssh key) and ssh key file paths
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
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
};
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
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())
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
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.
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
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.)
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
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).
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
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
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
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.
Ultimately what worked was this section
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
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
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";
};
};
}
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
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