From 4dba68ca16d90f7127572bb1436c7bca408252e1 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sat, 7 Mar 2026 17:42:43 +0100 Subject: [PATCH 01/32] ubuntu 26 / wayland migration + setup cleanup Wayland migration: - Switch clipboard from xclip to wl-copy (tmux, aliases, functions) - Remove setxkbmap from zshrc, add keyboard layout to gnome.nix dconf - Switch desktop.nix from kitty to ghostty - Switch zephyrus from x11.nix to wayland.nix - Add nvtop LD_LIBRARY_PATH alias for non-NixOS GNOME declarative config: - Migrate keybindings from keybindings.pl/CSV to dconf.settings - Migrate tiling-shell config from dconf dump to dconf.settings - Migrate MIME defaults from setup script to HM xdg.mimeApps - Migrate nvim desktop entry from .desktop file to HM xdg.desktopEntries - Add dconf2nix to desktop packages Desktop packages: - Enable tiling-shell from nixpkgs (GNOME 50 >= 45) - Add obsidian from nixpkgs (replaces AppImage) - Add qdirstat - Drop GTK workaround packages (gedit, nautilus, nemo, yaru-theme, alacritty) Setup script: - Refactor ubuntu(): drivers, codecs, minimal apt, everything else in HM - Remove obsolete functions: obsidian, discord, tiling_shell, nvidia_optimus, vim_copilot, wireguard_client - Remove get_claude TODO guard - Remove obsolete files: keybindings.pl, keybindings.csv, tilingshell.dconf, nvim.desktop, obsidian.desktop Co-Authored-By: Claude Opus 4.6 --- bin/keybindings.pl | 153 ---------------------- desktop/extensions/tilingshell.dconf | 28 ---- desktop/keybindings.csv | 185 --------------------------- desktop/nvim.desktop | 10 -- desktop/obsidian.desktop | 8 -- nix/home/desktop.nix | 42 +++++- nix/home/gnome.nix | 117 +++++++++++++++-- nix/home/hosts/zephyrus.nix | 2 +- nix/home/tmux.nix | 2 +- setup | 154 +++------------------- zsh/aliases | 5 +- zsh/functions | 4 +- zsh/zshrc | 3 - 13 files changed, 166 insertions(+), 547 deletions(-) delete mode 100755 bin/keybindings.pl delete mode 100644 desktop/extensions/tilingshell.dconf delete mode 100644 desktop/keybindings.csv delete mode 100644 desktop/nvim.desktop delete mode 100644 desktop/obsidian.desktop diff --git a/bin/keybindings.pl b/bin/keybindings.pl deleted file mode 100755 index 1213871..0000000 --- a/bin/keybindings.pl +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/perl - -# Source: https://gist.github.com/elgalu/8511861#file-keybindings-pl - -use strict; - -my $action = ''; -my $filename = $ENV{'HOME'} . '/.dotfiles/desktop/keybindings.csv'; # default filename if arg not specified -my $extensionsDir = $ENV{'HOME'} . '/.dotfiles/desktop/extensions'; - -my $dconfExtensions = [ - ['tilingshell', '/org/gnome/shell/extensions/tilingshell/'], -]; - -for my $arg (@ARGV){ - if ($arg eq "-e" or $arg eq "--export"){ - $action = 'export'; - } elsif ($arg eq "-i" or $arg eq "--import"){ - $action = 'import'; - } elsif ($arg eq "-h" or $arg eq "--help"){ - print "Import and export keybindings\n"; - print " -e, --export \n"; - print " -i, --import \n"; - print " -h, --help\n"; - exit; - } elsif ($arg =~ /^\-/){ - die "Unknown argument $arg"; - } else { - $filename = $arg; - if (!$action){ - if ( -e $filename){ - $action='import'; - } else { - $action='export'; - } - } - } -} - -$action='export' if (!$action); -if ($action eq 'export'){ - &export(); -} else { - &import(); -} - -sub export(){ - my $gsettingsFolders = [ - ['org.gnome.desktop.wm.keybindings','.'], - ['org.gnome.settings-daemon.plugins.power','button'], - ['org.gnome.settings-daemon.plugins.media-keys','.'], - ]; - - my $customBindings = [ - ]; - - $filename = ">$filename"; - open (my $fh, $filename) || die "Can't open file $filename: $!"; - - for my $folder (@$gsettingsFolders){ - my @keylist = split(/\n/, `gsettings list-recursively $folder->[0]`); - foreach my $line (@keylist){ - if ($line =~ /^([^ ]+) ([^ ]+)(?: \@[a-z]+)? (.*)/){ - my ($path, $name, $value) = ($1,$2,$3); - if ($name eq "custom-keybindings"){ - $value =~ s/[\[\]\' ]//g; - my @c = split(/,/, $value); - $customBindings = \@c; - } elsif ($name =~ /$folder->[1]/){ - if ($value =~ /^\[|\'/){ - if ($value =~ /^\[\'(?:disabled)?\'\]$/){ - $value = '[]'; - } - print $fh "$path\t$name\t$value\n"; - } - } - } else { - die "Could note parse $line"; - } - } - } - - for my $folder (@$customBindings){ - my $gs = `gsettings list-recursively org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:$folder`; - my ($binding) = $gs =~ /org.gnome.settings-daemon.plugins.media-keys.custom-keybinding binding (\'[^\n]+\')/g; - my ($command) = $gs =~ /org.gnome.settings-daemon.plugins.media-keys.custom-keybinding command (\'[^\n]+\')/g; - my ($name) = $gs =~ /org.gnome.settings-daemon.plugins.media-keys.custom-keybinding name (\'[^\n]+\')/g; - print $fh "custom\t$name\t$command\t$binding\n" - } - - close($fh); - - # Export dconf-based extension settings - mkdir $extensionsDir unless -d $extensionsDir; - for my $ext (@$dconfExtensions){ - my ($name, $path) = @$ext; - my $check = `dconf dump $path 2>/dev/null`; - if ($check){ - my $extFile = "$extensionsDir/$name.dconf"; - print "Exporting extension: $name\n"; - system("dconf dump $path > $extFile"); - } - } -} - -sub import(){ - - $filename = "<$filename"; - open (my $fh, $filename) || die "Can't open file $filename: $!"; - - my $customcount=0; - - while (my $line = <$fh>){ - chomp $line; - if ($line){ - my @v = split(/\t/, $line); - if (@v[0] eq 'custom'){ - my ($custom, $name, $command, $binding) = @v; - print "Installing custom keybinding: $name\n"; - print `gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom$customcount/ name \"$name\"`; - print `gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom$customcount/ command \"$command\"`; - print `gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom$customcount/ binding \"$binding\"`; - $customcount++; - } else { - my ($path, $name, $value) = @v; - print "Importing $path $name\n"; - print `gsettings set \"$path\" \"$name\" \"$value\"`; - } - } - } - if ($customcount > 0){ - my $customlist = ""; - for (my $i=0; $i<$customcount; $i++){ - $customlist .= "," if ($customlist); - $customlist .= "'/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom$i/'"; - } - $customlist = "[$customlist]"; - print "Importing list of custom keybindings.\n"; - print `gsettings set org.gnome.settings-daemon.plugins.media-keys custom-keybindings \"$customlist\"`; - } - - close($fh); - - # Import dconf-based extension settings - for my $ext (@$dconfExtensions){ - my ($name, $path) = @$ext; - my $extFile = "$extensionsDir/$name.dconf"; - if (-e $extFile){ - print "Importing extension: $name\n"; - system("dconf load $path < $extFile"); - } - } -} diff --git a/desktop/extensions/tilingshell.dconf b/desktop/extensions/tilingshell.dconf deleted file mode 100644 index 9c9bd8e..0000000 --- a/desktop/extensions/tilingshell.dconf +++ /dev/null @@ -1,28 +0,0 @@ -[/] -cycle-layouts=['p'] -enable-autotiling=true -enable-screen-edges-windows-suggestions=true -enable-snap-assistant-windows-suggestions=true -enable-tiling-system-windows-suggestions=true -enable-window-border=true -focus-window-down=['j'] -focus-window-left=['h'] -focus-window-next=@as [] -focus-window-prev=@as [] -focus-window-right=['l'] -focus-window-up=['k'] -inner-gaps=uint32 0 -last-version-name-installed='17.1' -layouts-json='[{"id":"Layout 1","tiles":[{"x":0,"y":0,"width":0.22,"height":0.5,"groups":[1,2]},{"x":0,"y":0.5,"width":0.22,"height":0.5,"groups":[1,2]},{"x":0.22,"y":0,"width":0.56,"height":1,"groups":[2,3]},{"x":0.78,"y":0,"width":0.22,"height":0.5,"groups":[3,4]},{"x":0.78,"y":0.5,"width":0.22,"height":0.5,"groups":[3,4]}]},{"id":"Layout 2","tiles":[{"x":0,"y":0,"width":0.22,"height":1,"groups":[1]},{"x":0.22,"y":0,"width":0.56,"height":1,"groups":[1,2]},{"x":0.78,"y":0,"width":0.22,"height":1,"groups":[2]}]},{"id":"Layout 3","tiles":[{"x":0,"y":0,"width":0.33,"height":1,"groups":[1]},{"x":0.33,"y":0,"width":0.67,"height":1,"groups":[1]}]},{"id":"Layout 4","tiles":[{"x":0,"y":0,"width":0.67,"height":1,"groups":[1]},{"x":0.67,"y":0,"width":0.33,"height":1,"groups":[1]}]},{"id":"13597114","tiles":[{"x":0,"y":0,"width":0.36770833333333336,"height":0.5,"groups":[1,2]},{"x":0.36770833333333336,"y":0,"width":0.6322916666666674,"height":1,"groups":[1]},{"x":0,"y":0.5,"width":0.36770833333333336,"height":0.5,"groups":[2,1]}]},{"id":"13201446","tiles":[{"x":0,"y":0,"width":0.29069767441860467,"height":1,"groups":[1]},{"x":0.29069767441860467,"y":0,"width":0.5087209302325582,"height":1,"groups":[2,1]},{"x":0.7994186046511628,"y":0,"width":0.20058139534883718,"height":1,"groups":[2]}]},{"id":"13300092","tiles":[{"x":0,"y":0,"width":0.5,"height":1,"groups":[1]},{"x":0.5,"y":0,"width":0.4999999999999982,"height":1,"groups":[1]}]}]' -move-window-center=['c'] -outer-gaps=uint32 0 -overridden-settings="{\"org.gnome.mutter.keybindings\":{\"toggle-tiled-right\":\"['Right']\",\"toggle-tiled-left\":\"['Left']\"},\"org.gnome.desktop.wm.keybindings\":{\"maximize\":\"['Up']\",\"unmaximize\":\"['Down', 'F5']\"},\"org.gnome.mutter\":{\"edge-tiling\":\"true\"}}" -quarter-tiling-threshold=uint32 40 -selected-layouts=[['Layout 3', 'Layout 3'], ['13300092', '13201446'], ['Layout 3', 'Layout 3']] -span-window-all-tiles=['f'] -span-window-down=['Down'] -span-window-left=['Left'] -span-window-right=['Right'] -span-window-up=['Up'] -top-edge-maximize=false -window-border-width=uint32 10 diff --git a/desktop/keybindings.csv b/desktop/keybindings.csv deleted file mode 100644 index 60b554a..0000000 --- a/desktop/keybindings.csv +++ /dev/null @@ -1,185 +0,0 @@ -org.gnome.desktop.wm.keybindings activate-window-menu ['space'] -org.gnome.desktop.wm.keybindings always-on-top [] -org.gnome.desktop.wm.keybindings begin-move ['F7'] -org.gnome.desktop.wm.keybindings begin-resize ['F8'] -org.gnome.desktop.wm.keybindings close ['F4'] -org.gnome.desktop.wm.keybindings cycle-group ['F6'] -org.gnome.desktop.wm.keybindings cycle-group-backward ['F6'] -org.gnome.desktop.wm.keybindings cycle-panels ['Escape'] -org.gnome.desktop.wm.keybindings cycle-panels-backward ['Escape'] -org.gnome.desktop.wm.keybindings cycle-windows [] -org.gnome.desktop.wm.keybindings cycle-windows-backward ['Escape'] -org.gnome.desktop.wm.keybindings lower [] -org.gnome.desktop.wm.keybindings maximize [] -org.gnome.desktop.wm.keybindings maximize-horizontally [] -org.gnome.desktop.wm.keybindings maximize-vertically [] -org.gnome.desktop.wm.keybindings minimize ['m'] -org.gnome.desktop.wm.keybindings move-to-center [] -org.gnome.desktop.wm.keybindings move-to-corner-ne [] -org.gnome.desktop.wm.keybindings move-to-corner-nw [] -org.gnome.desktop.wm.keybindings move-to-corner-se [] -org.gnome.desktop.wm.keybindings move-to-corner-sw [] -org.gnome.desktop.wm.keybindings move-to-monitor-down ['Down'] -org.gnome.desktop.wm.keybindings move-to-monitor-left ['Left'] -org.gnome.desktop.wm.keybindings move-to-monitor-right ['Right'] -org.gnome.desktop.wm.keybindings move-to-monitor-up ['Up'] -org.gnome.desktop.wm.keybindings move-to-side-e [] -org.gnome.desktop.wm.keybindings move-to-side-n [] -org.gnome.desktop.wm.keybindings move-to-side-s [] -org.gnome.desktop.wm.keybindings move-to-side-w [] -org.gnome.desktop.wm.keybindings move-to-workspace-1 [] -org.gnome.desktop.wm.keybindings move-to-workspace-10 [] -org.gnome.desktop.wm.keybindings move-to-workspace-11 [] -org.gnome.desktop.wm.keybindings move-to-workspace-12 [] -org.gnome.desktop.wm.keybindings move-to-workspace-2 [] -org.gnome.desktop.wm.keybindings move-to-workspace-3 [] -org.gnome.desktop.wm.keybindings move-to-workspace-4 [] -org.gnome.desktop.wm.keybindings move-to-workspace-5 [] -org.gnome.desktop.wm.keybindings move-to-workspace-6 [] -org.gnome.desktop.wm.keybindings move-to-workspace-7 [] -org.gnome.desktop.wm.keybindings move-to-workspace-8 [] -org.gnome.desktop.wm.keybindings move-to-workspace-9 [] -org.gnome.desktop.wm.keybindings move-to-workspace-down [] -org.gnome.desktop.wm.keybindings move-to-workspace-last [] -org.gnome.desktop.wm.keybindings move-to-workspace-left ['Page_Up'] -org.gnome.desktop.wm.keybindings move-to-workspace-right ['Page_Down'] -org.gnome.desktop.wm.keybindings move-to-workspace-up [] -org.gnome.desktop.wm.keybindings panel-main-menu ['F1'] -org.gnome.desktop.wm.keybindings panel-run-dialog ['F2'] -org.gnome.desktop.wm.keybindings raise [] -org.gnome.desktop.wm.keybindings raise-or-lower [] -org.gnome.desktop.wm.keybindings set-spew-mark [] -org.gnome.desktop.wm.keybindings show-desktop [] -org.gnome.desktop.wm.keybindings switch-applications ['Tab'] -org.gnome.desktop.wm.keybindings switch-applications-backward ['Tab'] -org.gnome.desktop.wm.keybindings switch-group ['Above_Tab', 'Above_Tab'] -org.gnome.desktop.wm.keybindings switch-group-backward ['Above_Tab', 'Above_Tab'] -org.gnome.desktop.wm.keybindings switch-input-source ['space', 'XF86Keyboard'] -org.gnome.desktop.wm.keybindings switch-input-source-backward ['space', 'XF86Keyboard'] -org.gnome.desktop.wm.keybindings switch-panels ['Tab'] -org.gnome.desktop.wm.keybindings switch-panels-backward ['Tab'] -org.gnome.desktop.wm.keybindings switch-to-workspace-1 ['1'] -org.gnome.desktop.wm.keybindings switch-to-workspace-10 [] -org.gnome.desktop.wm.keybindings switch-to-workspace-11 [] -org.gnome.desktop.wm.keybindings switch-to-workspace-12 [] -org.gnome.desktop.wm.keybindings switch-to-workspace-2 ['2'] -org.gnome.desktop.wm.keybindings switch-to-workspace-3 ['3'] -org.gnome.desktop.wm.keybindings switch-to-workspace-4 [] -org.gnome.desktop.wm.keybindings switch-to-workspace-5 [] -org.gnome.desktop.wm.keybindings switch-to-workspace-6 [] -org.gnome.desktop.wm.keybindings switch-to-workspace-7 [] -org.gnome.desktop.wm.keybindings switch-to-workspace-8 [] -org.gnome.desktop.wm.keybindings switch-to-workspace-9 [] -org.gnome.desktop.wm.keybindings switch-to-workspace-down [] -org.gnome.desktop.wm.keybindings switch-to-workspace-last [] -org.gnome.desktop.wm.keybindings switch-to-workspace-left ['Page_Up'] -org.gnome.desktop.wm.keybindings switch-to-workspace-right ['Page_Down'] -org.gnome.desktop.wm.keybindings switch-to-workspace-up [] -org.gnome.desktop.wm.keybindings switch-windows ['Tab'] -org.gnome.desktop.wm.keybindings switch-windows-backward ['Tab'] -org.gnome.desktop.wm.keybindings toggle-above [] -org.gnome.desktop.wm.keybindings toggle-fullscreen [] -org.gnome.desktop.wm.keybindings toggle-maximized ['F10'] -org.gnome.desktop.wm.keybindings toggle-on-all-workspaces [] -org.gnome.desktop.wm.keybindings toggle-shaded [] -org.gnome.desktop.wm.keybindings unmaximize [] -org.gnome.settings-daemon.plugins.power power-button-action 'interactive' -org.gnome.settings-daemon.plugins.media-keys battery-status [] -org.gnome.settings-daemon.plugins.media-keys battery-status-static ['XF86Battery'] -org.gnome.settings-daemon.plugins.media-keys calculator [] -org.gnome.settings-daemon.plugins.media-keys calculator-static ['XF86Calculator'] -org.gnome.settings-daemon.plugins.media-keys control-center ['s'] -org.gnome.settings-daemon.plugins.media-keys control-center-static ['XF86Tools'] -org.gnome.settings-daemon.plugins.media-keys decrease-text-size [] -org.gnome.settings-daemon.plugins.media-keys eject [] -org.gnome.settings-daemon.plugins.media-keys eject-static ['XF86Eject'] -org.gnome.settings-daemon.plugins.media-keys email [] -org.gnome.settings-daemon.plugins.media-keys email-static ['XF86Mail'] -org.gnome.settings-daemon.plugins.media-keys help ['', 'F1'] -org.gnome.settings-daemon.plugins.media-keys hibernate [] -org.gnome.settings-daemon.plugins.media-keys hibernate-static ['XF86Suspend', 'XF86Hibernate'] -org.gnome.settings-daemon.plugins.media-keys home [] -org.gnome.settings-daemon.plugins.media-keys home-static ['XF86Explorer'] -org.gnome.settings-daemon.plugins.media-keys increase-text-size [] -org.gnome.settings-daemon.plugins.media-keys keyboard-brightness-down [] -org.gnome.settings-daemon.plugins.media-keys keyboard-brightness-down-static ['XF86KbdBrightnessDown'] -org.gnome.settings-daemon.plugins.media-keys keyboard-brightness-toggle [] -org.gnome.settings-daemon.plugins.media-keys keyboard-brightness-toggle-static ['XF86KbdLightOnOff'] -org.gnome.settings-daemon.plugins.media-keys keyboard-brightness-up [] -org.gnome.settings-daemon.plugins.media-keys keyboard-brightness-up-static ['XF86KbdBrightnessUp'] -org.gnome.settings-daemon.plugins.media-keys logout ['Delete'] -org.gnome.settings-daemon.plugins.media-keys magnifier [] -org.gnome.settings-daemon.plugins.media-keys magnifier-zoom-in [] -org.gnome.settings-daemon.plugins.media-keys magnifier-zoom-out [] -org.gnome.settings-daemon.plugins.media-keys media [] -org.gnome.settings-daemon.plugins.media-keys media-static ['XF86AudioMedia'] -org.gnome.settings-daemon.plugins.media-keys mic-mute [] -org.gnome.settings-daemon.plugins.media-keys mic-mute-static ['XF86AudioMicMute'] -org.gnome.settings-daemon.plugins.media-keys next [] -org.gnome.settings-daemon.plugins.media-keys next-static ['XF86AudioNext', 'XF86AudioNext'] -org.gnome.settings-daemon.plugins.media-keys on-screen-keyboard [] -org.gnome.settings-daemon.plugins.media-keys pause [] -org.gnome.settings-daemon.plugins.media-keys pause-static ['XF86AudioPause'] -org.gnome.settings-daemon.plugins.media-keys play [] -org.gnome.settings-daemon.plugins.media-keys play-static ['XF86AudioPlay', 'XF86AudioPlay'] -org.gnome.settings-daemon.plugins.media-keys playback-forward [] -org.gnome.settings-daemon.plugins.media-keys playback-forward-static ['XF86AudioForward'] -org.gnome.settings-daemon.plugins.media-keys playback-random [] -org.gnome.settings-daemon.plugins.media-keys playback-random-static ['XF86AudioRandomPlay'] -org.gnome.settings-daemon.plugins.media-keys playback-repeat [] -org.gnome.settings-daemon.plugins.media-keys playback-repeat-static ['XF86AudioRepeat'] -org.gnome.settings-daemon.plugins.media-keys playback-rewind [] -org.gnome.settings-daemon.plugins.media-keys playback-rewind-static ['XF86AudioRewind'] -org.gnome.settings-daemon.plugins.media-keys power [] -org.gnome.settings-daemon.plugins.media-keys power-static ['XF86PowerOff'] -org.gnome.settings-daemon.plugins.media-keys previous [] -org.gnome.settings-daemon.plugins.media-keys previous-static ['XF86AudioPrev', 'XF86AudioPrev'] -org.gnome.settings-daemon.plugins.media-keys rfkill [] -org.gnome.settings-daemon.plugins.media-keys rfkill-bluetooth [] -org.gnome.settings-daemon.plugins.media-keys rfkill-bluetooth-static ['XF86Bluetooth'] -org.gnome.settings-daemon.plugins.media-keys rfkill-static ['XF86WLAN', 'XF86UWB', 'XF86RFKill'] -org.gnome.settings-daemon.plugins.media-keys rotate-video-lock [] -org.gnome.settings-daemon.plugins.media-keys rotate-video-lock-static ['o', 'XF86RotationLockToggle'] -org.gnome.settings-daemon.plugins.media-keys screen-brightness-cycle [] -org.gnome.settings-daemon.plugins.media-keys screen-brightness-cycle-static ['XF86MonBrightnessCycle'] -org.gnome.settings-daemon.plugins.media-keys screen-brightness-down [] -org.gnome.settings-daemon.plugins.media-keys screen-brightness-down-static ['XF86MonBrightnessDown'] -org.gnome.settings-daemon.plugins.media-keys screen-brightness-up [] -org.gnome.settings-daemon.plugins.media-keys screen-brightness-up-static ['XF86MonBrightnessUp'] -org.gnome.settings-daemon.plugins.media-keys screenreader [] -org.gnome.settings-daemon.plugins.media-keys screensaver ['l'] -org.gnome.settings-daemon.plugins.media-keys screensaver-static ['XF86ScreenSaver'] -org.gnome.settings-daemon.plugins.media-keys search ['f'] -org.gnome.settings-daemon.plugins.media-keys search-static ['XF86Search'] -org.gnome.settings-daemon.plugins.media-keys stop [] -org.gnome.settings-daemon.plugins.media-keys stop-static ['XF86AudioStop'] -org.gnome.settings-daemon.plugins.media-keys suspend [] -org.gnome.settings-daemon.plugins.media-keys suspend-static ['XF86Sleep'] -org.gnome.settings-daemon.plugins.media-keys terminal ['t'] -org.gnome.settings-daemon.plugins.media-keys toggle-contrast [] -org.gnome.settings-daemon.plugins.media-keys touchpad-off [] -org.gnome.settings-daemon.plugins.media-keys touchpad-off-static ['XF86TouchpadOff'] -org.gnome.settings-daemon.plugins.media-keys touchpad-on [] -org.gnome.settings-daemon.plugins.media-keys touchpad-on-static ['XF86TouchpadOn'] -org.gnome.settings-daemon.plugins.media-keys touchpad-toggle [] -org.gnome.settings-daemon.plugins.media-keys touchpad-toggle-static ['XF86TouchpadToggle', 'XF86TouchpadToggle'] -org.gnome.settings-daemon.plugins.media-keys volume-down [] -org.gnome.settings-daemon.plugins.media-keys volume-down-precise [] -org.gnome.settings-daemon.plugins.media-keys volume-down-precise-static ['XF86AudioLowerVolume', 'XF86AudioLowerVolume'] -org.gnome.settings-daemon.plugins.media-keys volume-down-quiet [] -org.gnome.settings-daemon.plugins.media-keys volume-down-quiet-static ['XF86AudioLowerVolume', 'XF86AudioLowerVolume'] -org.gnome.settings-daemon.plugins.media-keys volume-down-static ['XF86AudioLowerVolume', 'XF86AudioLowerVolume'] -org.gnome.settings-daemon.plugins.media-keys volume-mute [] -org.gnome.settings-daemon.plugins.media-keys volume-mute-quiet [] -org.gnome.settings-daemon.plugins.media-keys volume-mute-quiet-static ['XF86AudioMute'] -org.gnome.settings-daemon.plugins.media-keys volume-mute-static ['XF86AudioMute'] -org.gnome.settings-daemon.plugins.media-keys volume-up [] -org.gnome.settings-daemon.plugins.media-keys volume-up-precise [] -org.gnome.settings-daemon.plugins.media-keys volume-up-precise-static ['XF86AudioRaiseVolume', 'XF86AudioRaiseVolume'] -org.gnome.settings-daemon.plugins.media-keys volume-up-quiet [] -org.gnome.settings-daemon.plugins.media-keys volume-up-quiet-static ['XF86AudioRaiseVolume', 'XF86AudioRaiseVolume'] -org.gnome.settings-daemon.plugins.media-keys volume-up-static ['XF86AudioRaiseVolume', 'XF86AudioRaiseVolume'] -org.gnome.settings-daemon.plugins.media-keys www ['c'] -org.gnome.settings-daemon.plugins.media-keys www-static ['XF86WWW'] -custom 'Open nautilus' 'nautilus' 'e' -custom 'Toggle nightlight' 'toggle_nightlight' 'n' diff --git a/desktop/nvim.desktop b/desktop/nvim.desktop deleted file mode 100644 index a8f68cb..0000000 --- a/desktop/nvim.desktop +++ /dev/null @@ -1,10 +0,0 @@ -[Desktop Entry] -Type=Application -Name=Neovim -Comment=Edit files with Neovim -Exec=nvim %F -Terminal=true -Icon=$HOME/.icons/nvim.svg -Categories=Utility;Development;TextEditor; -MimeType=text/markdown;text/plain; -Path=$HOME/Downloads diff --git a/desktop/obsidian.desktop b/desktop/obsidian.desktop deleted file mode 100644 index 1e78684..0000000 --- a/desktop/obsidian.desktop +++ /dev/null @@ -1,8 +0,0 @@ -[Desktop Entry] -Type=Application -Name=Obsidian -Exec=$HOME/applications/obsidian.AppImage -Icon=$HOME/.icons/obsidian-icon.svg -Categories=Utility; - -MimeType=x-scheme-handler/obsidian;text/html; diff --git a/nix/home/desktop.nix b/nix/home/desktop.nix index 21024ef..11ba271 100644 --- a/nix/home/desktop.nix +++ b/nix/home/desktop.nix @@ -3,11 +3,10 @@ let dotfiles = "${config.home.homeDirectory}/.dotfiles"; in { - # TODO: make pure once theme works on Ubuntu 26 home.file.".config/vesktop/themes/custom.theme.css".source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/desktop/discord/themes/custom.theme.css"; - imports = [ ./kitty.nix ./newsboat.nix ]; # TODO(ubuntu-26): switch to ./ghostty.nix + imports = [ ./ghostty.nix ./newsboat.nix ]; home.file.".icons".source = ../../desktop/icons; @@ -15,14 +14,12 @@ in config.lib.file.mkOutOfStoreSymlink "${dotfiles}/newsboat/urls"; home.packages = with pkgs; [ - alacritty + dconf2nix loupe - gedit # TODO(ubuntu-26): remove, workaround for GTK conflicts - nautilus # TODO(ubuntu-26): remove, workaround for GTK conflicts - nemo # TODO(ubuntu-26): remove, workaround for GTK conflicts + obsidian + qdirstat signal-desktop vesktop - yaru-theme # TODO(ubuntu-26): remove, workaround for GTK conflicts zathura ]; @@ -35,4 +32,35 @@ in categories = [ "Network" "InstantMessaging" ]; terminal = false; }; + + xdg.desktopEntries.nvim = { + name = "Neovim"; + comment = "Edit files with Neovim"; + exec = "nvim %F"; + icon = toString ../../desktop/icons/nvim.svg; + type = "Application"; + categories = [ "Utility" "Development" "TextEditor" ]; + mimeType = [ "text/markdown" "text/plain" ]; + terminal = true; + settings.Path = "${config.home.homeDirectory}/Downloads"; + }; + + xdg.mimeApps = { + enable = true; + defaultApplications = { + "text/plain" = "nvim.desktop"; + "text/markdown" = "nvim.desktop"; + "text/x-python" = "nvim.desktop"; + "text/x-shellscript" = "nvim.desktop"; + "text/x-yaml" = "nvim.desktop"; + "text/x-toml" = "nvim.desktop"; + "application/json" = "nvim.desktop"; + "application/javascript" = "nvim.desktop"; + "application/x-shellscript" = "nvim.desktop"; + "x-scheme-handler/obsidian" = "obsidian.desktop"; + "text/html" = "firefox.desktop"; + "x-scheme-handler/http" = "firefox.desktop"; + "x-scheme-handler/https" = "firefox.desktop"; + }; + }; } diff --git a/nix/home/gnome.nix b/nix/home/gnome.nix index 5a28ef3..f992cd5 100644 --- a/nix/home/gnome.nix +++ b/nix/home/gnome.nix @@ -1,14 +1,13 @@ -{ pkgs, config, ... }: +{ pkgs, lib, config, ... }: { - # tiling-shell: installed via setup script for now (nixpkgs only has GNOME 45+ build, we're on GNOME 42) - # TODO: uncomment when on GNOME 45+ (Ubuntu 25.04+) - # home.packages = with pkgs; [ - # gnomeExtensions.tiling-shell - # ]; - # # GNOME doesn't see ~/.nix-profile in XDG_DATA_DIRS, so symlink extensions here - # xdg.dataFile."gnome-shell/extensions/tilingshell@ferrarodomenico.com".source = - # config.lib.file.mkOutOfStoreSymlink - # "${config.home.homeDirectory}/.nix-profile/share/gnome-shell/extensions/tilingshell@ferrarodomenico.com"; + home.packages = with pkgs; [ + gnomeExtensions.tiling-shell + ]; + + # GNOME doesn't see ~/.nix-profile in XDG_DATA_DIRS, so symlink extensions here + xdg.dataFile."gnome-shell/extensions/tilingshell@ferrarodomenico.com".source = + config.lib.file.mkOutOfStoreSymlink + "${config.home.homeDirectory}/.nix-profile/share/gnome-shell/extensions/tilingshell@ferrarodomenico.com"; dconf.settings = { "org/gnome/shell" = { @@ -18,15 +17,105 @@ ]; }; - # Screen timeout: 1 hour before blanking + "org/gnome/desktop/input-sources" = { + sources = [ (lib.hm.gvariant.mkTuple [ "xkb" "de+nodeadkeys" ]) ]; + }; + "org/gnome/desktop/session" = { - idle-delay = 3600; # seconds (1 hour) + idle-delay = 3600; }; - # Lock screen: enabled, but 2 hour delay after screen blanks "org/gnome/desktop/screensaver" = { lock-enabled = true; - lock-delay = 7200; # seconds (2 hours) after screen blanks + lock-delay = 7200; + }; + + # Window management keybindings + "org/gnome/desktop/wm/keybindings" = { + minimize = [ "m" ]; + maximize = [ "m" ]; + switch-to-workspace-1 = [ "1" ]; + switch-to-workspace-2 = [ "2" ]; + switch-to-workspace-3 = [ "3" ]; + # Alt+Tab for windows, Super+Tab for apps + switch-windows = [ "Tab" ]; + switch-windows-backward = [ "Tab" ]; + switch-applications = [ "Tab" ]; + switch-applications-backward = [ "Tab" ]; + # Disable workspace move/switch defaults that conflict with tiling-shell + cycle-windows = []; + move-to-workspace-1 = []; + move-to-workspace-down = []; + move-to-workspace-up = []; + move-to-workspace-last = []; + switch-to-workspace-down = []; + switch-to-workspace-up = []; + switch-to-workspace-last = []; + }; + + # Media/shortcut keys + "org/gnome/settings-daemon/plugins/media-keys" = { + control-center = [ "s" ]; + search = [ "f" ]; + www = [ "c" ]; + screensaver = [ "l" ]; + terminal = [ "t" ]; + # Custom keybindings list + custom-keybindings = [ + "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/" + "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom1/" + ]; + }; + + # Custom: file manager + "org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0" = { + name = "Open file manager"; + command = "nautilus"; + binding = "e"; + }; + + # Custom: nightlight toggle + "org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom1" = { + name = "Toggle nightlight"; + command = "toggle_nightlight"; + binding = "n"; + }; + + "org/gnome/settings-daemon/plugins/power" = { + power-button-action = "interactive"; + }; + + # Tiling Shell extension + "org/gnome/shell/extensions/tilingshell" = { + enable-autotiling = true; + enable-screen-edges-windows-suggestions = true; + enable-snap-assistant-windows-suggestions = true; + enable-tiling-system-windows-suggestions = true; + enable-window-border = true; + window-border-width = lib.hm.gvariant.mkUint32 10; + top-edge-maximize = false; + inner-gaps = lib.hm.gvariant.mkUint32 0; + outer-gaps = lib.hm.gvariant.mkUint32 0; + quarter-tiling-threshold = lib.hm.gvariant.mkUint32 40; + # Keybindings + cycle-layouts = [ "p" ]; + move-window-center = [ "c" ]; + span-window-all-tiles = [ "f" ]; + span-window-down = [ "Down" ]; + span-window-left = [ "Left" ]; + span-window-right = [ "Right" ]; + span-window-up = [ "Up" ]; + focus-window-down = [ "j" ]; + focus-window-left = [ "h" ]; + focus-window-right = [ "l" ]; + focus-window-up = [ "k" ]; + focus-window-next = []; + focus-window-prev = []; + # Layouts + selected-layouts = [ [ "Layout 3" "Layout 3" ] [ "Layout 3" "Layout 3" ] ]; + layouts-json = ''[{"id":"Layout 1","tiles":[{"x":0,"y":0,"width":0.22,"height":0.5,"groups":[1,2]},{"x":0,"y":0.5,"width":0.22,"height":0.5,"groups":[1,2]},{"x":0.22,"y":0,"width":0.56,"height":1,"groups":[2,3]},{"x":0.78,"y":0,"width":0.22,"height":0.5,"groups":[3,4]},{"x":0.78,"y":0.5,"width":0.22,"height":0.5,"groups":[3,4]}]},{"id":"Layout 2","tiles":[{"x":0,"y":0,"width":0.22,"height":1,"groups":[1]},{"x":0.22,"y":0,"width":0.56,"height":1,"groups":[1,2]},{"x":0.78,"y":0,"width":0.22,"height":1,"groups":[2]}]},{"id":"Layout 3","tiles":[{"x":0,"y":0,"width":0.33,"height":1,"groups":[1]},{"x":0.33,"y":0,"width":0.67,"height":1,"groups":[1]}]},{"id":"Layout 4","tiles":[{"x":0,"y":0,"width":0.67,"height":1,"groups":[1]},{"x":0.67,"y":0,"width":0.33,"height":1,"groups":[1]}]},{"id":"13597114","tiles":[{"x":0,"y":0,"width":0.36770833333333336,"height":0.5,"groups":[1,2]},{"x":0.36770833333333336,"y":0,"width":0.6322916666666674,"height":1,"groups":[1]},{"x":0,"y":0.5,"width":0.36770833333333336,"height":0.5,"groups":[2,1]}]},{"id":"13201446","tiles":[{"x":0,"y":0,"width":0.29069767441860467,"height":1,"groups":[1]},{"x":0.29069767441860467,"y":0,"width":0.5087209302325582,"height":1,"groups":[2,1]},{"x":0.7994186046511628,"y":0,"width":0.20058139534883718,"height":1,"groups":[2]}]},{"id":"13300092","tiles":[{"x":0,"y":0,"width":0.5,"height":1,"groups":[1]},{"x":0.5,"y":0,"width":0.4999999999999982,"height":1,"groups":[1]}]}]''; + # Settings tiling-shell overrides in GNOME + overridden-settings = ''{"org.gnome.mutter.keybindings":{"toggle-tiled-right":"['Right']","toggle-tiled-left":"['Left']"},"org.gnome.desktop.wm.keybindings":{"maximize":"['Up']","unmaximize":"['Down', 'F5']"},"org.gnome.mutter":{"edge-tiling":"true"}}''; }; }; } diff --git a/nix/home/hosts/zephyrus.nix b/nix/home/hosts/zephyrus.nix index d8bcb7d..5d83e4e 100644 --- a/nix/home/hosts/zephyrus.nix +++ b/nix/home/hosts/zephyrus.nix @@ -8,6 +8,6 @@ ../desktop.nix ../gnome.nix ../timers.nix - ../x11.nix + ../wayland.nix ]; } diff --git a/nix/home/tmux.nix b/nix/home/tmux.nix index b4008ff..6114417 100644 --- a/nix/home/tmux.nix +++ b/nix/home/tmux.nix @@ -113,7 +113,7 @@ in # Vi copy mode bindings bind -T copy-mode-vi v send -X begin-selection - bind -T copy-mode-vi y send -X copy-pipe "xclip -selection clipboard -i" + bind -T copy-mode-vi y send -X copy-pipe "wl-copy" bind -T copy-mode-vi C-v send -X rectangle-toggle bind -T copy-mode-vi Escape send -X cancel diff --git a/setup b/setup index c6983d5..3cd4ef4 100755 --- a/setup +++ b/setup @@ -110,73 +110,35 @@ gpu() { ubuntu() { sudo apt-get update + + # System-level packages not available via Nix/HM sudo apt-get install -y \ libfuse2 \ - baobab \ - blueman \ - gnome-shell-extensions-gpaste \ - gparted \ - pulseaudio-module-bluetooth \ - qdirstat \ - virt-viewer + ubuntu-drivers-common + + # NVIDIA drivers (auto-detects correct version) + sudo ubuntu-drivers install + + # Codecs, fonts, restricted formats + echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | sudo debconf-set-selections + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y ubuntu-restricted-extras # Enable unprivileged user namespaces for Electron app sandboxing # Ubuntu 23.10+ uses AppArmor to restrict userns per-app; disable that restriction - echo 'kernel.apparmor_restrict_unprivileged_userns=0' | sudo tee /etc/sysctl.d/60-apparmor-namespace.conf - sudo sysctl --system - - ./bin/keybindings.pl -i + if [ ! -f /etc/sysctl.d/60-apparmor-namespace.conf ]; then + echo 'kernel.apparmor_restrict_unprivileged_userns=0' | sudo tee /etc/sysctl.d/60-apparmor-namespace.conf + sudo sysctl --system + fi - # disable screenshot sound + # Disable screenshot sound if [ -f "/usr/share/sounds/freedesktop/stereo/camera-shutter.oga" ]; then - sudo mv /usr/share/sounds/freedesktop/stereo/camera-shutter.oga /usr/share/sounds/freedesktop/stereo/camera-shutter-disabled.oga + sudo mv /usr/share/sounds/freedesktop/stereo/camera-shutter.oga /usr/share/sounds/freedesktop/stereo/camera-shutter-disabled.oga fi - # webp support for eye of gnome image viewer - # yes | sudo add-apt-repository ppa:helkaluin/webp-pixbuf-loader (ships with >=22.04) - sudo apt install webp-pixbuf-loader - xdg-mime default org.gnome.eog.desktop image/webp - - obsidian - obsidian_vault desktop shallow + # Remove bloat (brltty grabs USB serial devices) + sudo apt-get remove -y orca brltty - # Setup all desktop entries - for desktop_file in "$HOME"/.dotfiles/desktop/*.desktop; do - if [ -f "$desktop_file" ]; then - filename=$(basename "$desktop_file") - sed "s|\$HOME|$HOME|g" "$desktop_file" > "$HOME/.local/share/applications/$filename" - echo "Installed desktop entry: $filename" - fi - done - update-desktop-database "$HOME"/.local/share/applications/ - - # Setup default MIME type associations - # Text and code files to nvim - xdg-mime default nvim.desktop text/plain - xdg-mime default nvim.desktop text/markdown - xdg-mime default nvim.desktop text/x-python - xdg-mime default nvim.desktop text/x-shellscript - xdg-mime default nvim.desktop text/x-yaml - xdg-mime default nvim.desktop text/x-toml - xdg-mime default nvim.desktop application/json - xdg-mime default nvim.desktop application/javascript - xdg-mime default nvim.desktop application/x-shellscript - - # Obsidian for its protocol - xdg-mime default obsidian.desktop x-scheme-handler/obsidian - - # Browser for HTML - xdg-mime default firefox.desktop text/html - xdg-mime default firefox.desktop x-scheme-handler/http - xdg-mime default firefox.desktop x-scheme-handler/https - - sudo snap install celluloid - - ### remove bloat - # screen reader - sudo apt-get remove -y orca - # braille display driver - sudo apt-get remove -y brltty + echo "Run './setup gpu' after reboot to set up Nix GPU driver symlinks." } # todo https://github.com/nix-community/nix-on-droid-app @@ -255,9 +217,8 @@ get_veracrypt() { } get_claude() { - echot "TODO try native installer on ubuntun 26" && exit - curl -fsSL https://claude.ai/install.sh | bash + mkdir -p ~/.claude/plugins rm -rf ~/.claude/settings.json ~/.claude/CLAUDE.md ~/.claude/commands ~/.claude/output-styles ~/.claude/mcp.json ln -sf "$DOTFILES/claude/settings.json" ~/.claude/settings.json @@ -292,11 +253,6 @@ secrets() { export SOPS_AGE_KEY_FILE="$key_file" sops -d ~/.dotfiles/secrets/api_keys/wakatime.cfg > ~/.wakatime.cfg - # find ~/.dotfiles/secrets/bin -type f -exec ln -sf {} ~/bin/ \; -} - -vim_copilot() { - git clone --depth 1 https://github.com/github/copilot.vim.git ~/.vim/pack/github/start/copilot.vim } obsidian_vault() { @@ -347,64 +303,6 @@ obsidian_vault() { } -obsidian() { - appimage="$(find ~/Downloads -name 'Obsidian*.AppImage')" - if [ -z "$appimage" ]; then - echo "Error: No Obsidian AppImage found in Downloads." - exit 1 - fi - COUNT=$(echo "$appimage" | wc -l) - if [ $COUNT -gt 1 ]; then - echo "Warning: Multiple Obsidian AppImages found. Using the first one." - fi - appimage=$(echo "$appimage" | head -n 1) - echo "Using Obsidian AppImage: $appimage" - mv "$appimage" ~/applications/obsidian.AppImage - chmod +x ~/applications/obsidian.AppImage - sed "s|\$HOME|$HOME|g" "$HOME"/.dotfiles/desktop/obsidian.desktop > "$HOME"/.local/share/applications/obsidian.desktop - update-desktop-database "$HOME"/.local/share/applications/ - echo "Obsidian app set up." -} - -discord() { - wget -O /tmp/discord.deb "https://discord.com/api/download?platform=linux&format=deb" - sudo dpkg -i /tmp/discord.deb - rm /tmp/discord.deb -} - -tiling_shell() { - # Temporary: nixpkgs only has GNOME 45+ build, this installs GNOME 42-44 build - # TODO: remove when on GNOME 45+ and re-enable in gnome.nix - gnome_version=$(gnome-shell --version | grep -oP '\d+' | head -1) - if [ "$gnome_version" -ge 45 ]; then - echo "GNOME $gnome_version detected. Use Nix version instead (uncomment in gnome.nix)" - return - fi - echo "Installing Tiling Shell for GNOME 42-44..." - ext_dir="$HOME/.local/share/gnome-shell/extensions/tilingshell@ferrarodomenico.com" - rm -rf "$ext_dir" - mkdir -p "$ext_dir" - curl -fsSL "https://github.com/domferr/tilingshell/releases/latest/download/GNOME.42-44.tilingshell@ferrarodomenico.com.zip" -o /tmp/tilingshell.zip - unzip -o /tmp/tilingshell.zip -d "$ext_dir" - rm /tmp/tilingshell.zip - echo "Tiling Shell installed. Log out and back in to enable." -} - - -### Networking etc - -wireguard_client() { - sudo apt update && sudo apt install -y wireguard resolveconf - mkdir -p ~/wireguard && chmod 700 ~/wireguard - wg genkey | tee ~/wireguard/private.key - cat ~/wireguard/private.key | wg pubkey | tee ~/wireguard/public.key - echo "EXECUTE THE FOLLOWING ON THE VPS SERVER, then create a new client and copy the client config to ~/wireguard/wg0.conf\n--------------" - echo "curl -O https://raw.githubusercontent.com/angristan/wireguard-install/master/wireguard-install.sh - chmod +x wireguard-install.sh - ./wireguard-install.sh" - echo "When done, execute (on the CLIENT):\nsudo wg-quick up ~/wireguard/wg0.conf" -} - sshkeys() { ssh-keygen -t ed25519 -C "69987866+MaxWolf-01@users.noreply.github.com" find ~/.ssh/ -type f -exec chmod 600 {} \; && find ~/.ssh/ -type d -exec chmod 700 {} \; && find ~/.ssh/ -type f -name "*.pub" -exec chmod 644 {} \; @@ -424,18 +322,6 @@ nvidia_mps() { sudo systemctl enable --now nvidia-mps } -nvidia_optimus() { - # TODO iGPU not used / display not working if hybrid / off - cd ~/repos/tools - if [ -d "envycontrol" ]; then cd envycontrol && git pull; else git clone --depth 1 https://github.com/bayasdev/envycontrol.git && cd envycontrol; fi; - sudo pip install . - # https://github.com/bayasdev/envycontrol/wiki/Frequently-Asked-Questions#instructions-for-ubuntu-and-its-derivatives - sudo prime-select on-demand - sudo systemctl mask gpu-manager.service - sudo apt-get install -y gnome-tweaks - echo "Install the gui manager according to gnome version @ https://github.com/LorenzoMorelli/GPU_profile_selector?tab=readme-ov-file#manual" -} - # Check if the first argument is the name of a function if declare -f "$1" > /dev/null; then # Call the function with the rest of the arguments diff --git a/zsh/aliases b/zsh/aliases index 8d0325f..a71721f 100644 --- a/zsh/aliases +++ b/zsh/aliases @@ -96,7 +96,7 @@ alias steam='GDK_SCALE=2 steam' alias cc="claude" alias ccc="claude --continue" alias ccr="claude --resume" -alias cpwd="pwd | xclip -selection clipboard" +alias cpwd="pwd | wl-copy" alias cmd="vi ~/.dotfiles/claude/CLAUDE.md" # miscl @@ -159,3 +159,6 @@ oyh() { } alias zshperf="ZPROF=1 zsh -ic exit" + +# Nix-on-Ubuntu: nvtop needs system NVIDIA libs to see the dGPU +[ ! -f /etc/NIXOS ] && alias nvtop='LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu nvtop' diff --git a/zsh/functions b/zsh/functions index 6c69486..38e8719 100644 --- a/zsh/functions +++ b/zsh/functions @@ -341,9 +341,9 @@ function restic_snaps() { # put the content of a file into the clipboard, or read from stdin if no file is provided clip() { if [ -z "$1" ]; then - xclip -selection clipboard + wl-copy else - xclip -selection clipboard < "$1" + wl-copy < "$1" fi } diff --git a/zsh/zshrc b/zsh/zshrc index 271550a..f27c116 100644 --- a/zsh/zshrc +++ b/zsh/zshrc @@ -13,9 +13,6 @@ source "$HOME/.dotfiles/zsh/plugin-files/zoxide-autocd.zsh" # disable terminal flow control (can press ctrl+s without freezing it) stty -ixon -# pressing "`" once suffices for the key to be printed -setxkbmap de nodeadkeys &> /dev/null - # clipse tui clipboard manager if command -v clipse &> /dev/null; then (clipse --listen &> /dev/null &) From 4a87f6085d0195767c3369543ce4fbae076896e9 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sat, 7 Mar 2026 21:02:38 +0100 Subject: [PATCH 02/32] ghostty: unbind alt+N and ctrl+shift+t, set as default terminal - Pass Alt+1-9 through to tmux (was ghostty tab switching) - Pass Ctrl+Shift+T through, bind in tmux as new-window - Set ghostty as GNOME default terminal Co-Authored-By: Claude Opus 4.6 --- nix/home/ghostty.nix | 2 ++ nix/home/gnome.nix | 4 ++++ nix/home/tmux.nix | 1 + 3 files changed, 7 insertions(+) diff --git a/nix/home/ghostty.nix b/nix/home/ghostty.nix index b546abd..93ab373 100644 --- a/nix/home/ghostty.nix +++ b/nix/home/ghostty.nix @@ -8,6 +8,8 @@ shell-integration-features = "ssh-terminfo,ssh-env"; confirm-close-surface = false; copy-on-select = "clipboard"; + # Pass Alt+N through to tmux instead of ghostty tab switching + keybind = "alt+one=unbind\nalt+two=unbind\nalt+three=unbind\nalt+four=unbind\nalt+five=unbind\nalt+six=unbind\nalt+seven=unbind\nalt+eight=unbind\nalt+nine=unbind\nctrl+shift+t=unbind"; }; }; diff --git a/nix/home/gnome.nix b/nix/home/gnome.nix index f992cd5..0055050 100644 --- a/nix/home/gnome.nix +++ b/nix/home/gnome.nix @@ -17,6 +17,10 @@ ]; }; + "org/gnome/desktop/default-applications/terminal" = { + exec = "ghostty"; + }; + "org/gnome/desktop/input-sources" = { sources = [ (lib.hm.gvariant.mkTuple [ "xkb" "de+nodeadkeys" ]) ]; }; diff --git a/nix/home/tmux.nix b/nix/home/tmux.nix index 6114417..151008d 100644 --- a/nix/home/tmux.nix +++ b/nix/home/tmux.nix @@ -125,6 +125,7 @@ in # New window in current directory bind c new-window -c "#{pane_current_path}" + bind -n C-S-t new-window -c "#{pane_current_path}" # Session switching with fzf bind f display-popup -E "tmux list-sessions -F '#{session_name}' | fzf --reverse | xargs -r tmux switch-client -t" From 80911f0fa9c350114b1dbfe5992148e35d05284b Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 01:17:08 +0100 Subject: [PATCH 03/32] add HM Firefox, screenshot keybindings, update docs - firefox.nix: policies (telemetry, studies, tracking, AI chatbot/link previews disabled), custom search engines (@gh/@nix/@pypi/@mdn), about:config hardening (HTTPS-only, privacy, VA-API hw decode, tab unloading, dark mode, containers) - gnome.nix: screenshot keybindings, message tray, disable toggle-overview and switch-to-application-1..9 - desktop.nix: import firefox.nix - README.md: update tree, setup flow, remove stale tiling_shell ref - CLAUDE.md: update HM file listing and setup flow Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 15 ++++--- README.md | 11 +++-- nix/home/desktop.nix | 2 +- nix/home/firefox.nix | 104 +++++++++++++++++++++++++++++++++++++++++++ nix/home/gnome.nix | 18 ++++++++ 5 files changed, 136 insertions(+), 14 deletions(-) create mode 100644 nix/home/firefox.nix diff --git a/CLAUDE.md b/CLAUDE.md index df1ce4b..834176a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,14 +51,15 @@ tmux new-session -d -s "$SESSION" -x "$(tput cols)" -y "$(tput lines)" Structure: - `flake.nix` — defines all hosts. PC is a NixOS system (with HM as module); laptops are HM standalone. - `nix/home/common.nix` - CLI tools for all hosts (auto-included via mkHome) -- `nix/home/desktop.nix` - GUI apps (vesktop, nemo, fonts) — workstation machines only -- `nix/home/gnome.nix` - GNOME-specific (tiling-shell, dconf) -- `nix/home/kitty.nix` - terminal emulator +- `nix/home/desktop.nix` - GUI apps (vesktop, obsidian, etc.) — workstation machines only +- `nix/home/firefox.nix` - Firefox: policies, search engines, about:config +- `nix/home/ghostty.nix` - terminal emulator +- `nix/home/gnome.nix` - GNOME keybindings, tiling-shell, dconf settings - `nix/home/newsboat.nix` - RSS reader with desktop notifications - `nix/home/tmux.nix` - tmux config - `nix/home/timers.nix` - systemd user timers, zephyrus only. Secrets via `EnvironmentFile` from `secrets/env/` - `nix/home/pc-timers.nix` - PC user timers (youtube backup, phone sync + backup, encrypted backup) -- `nix/home/x11.nix` / `wayland.nix` - display server specific +- `nix/home/wayland.nix` - Wayland clipboard (wl-clipboard) - `nix/home/hosts/` - per-machine configs (stateVersion + imports) - `nix/nixos/pc/` - NixOS system config for PC (configuration.nix, hardware-configuration.nix, youtube-download.nix) @@ -69,7 +70,7 @@ Host tiers: Setup flow: - `./setup minimal` installs Nix -- Laptops (HM standalone): `nix run home-manager/master -- switch --flake ~/.dotfiles#$NIX_HOST`, then `hmswitch` +- Laptops (HM standalone): `./setup host ` (auto-runs first HM switch), then `hmswitch` for subsequent changes - PC (NixOS): `nswitch` (alias for `sudo nixos-rebuild switch --flake ...`) — rebuilds system + HM together ### Key Nix Concepts @@ -107,8 +108,8 @@ See `agent/knowledge/nixos-new-machine.md` (disko + nixos-facter + nixos-anywher ## What stays outside Nix -- Ubuntu-specific apt packages (pulseaudio-module-bluetooth) - system-level -- GNOME keybindings - keybindings.pl works fine +- Ubuntu-specific apt packages (libfuse2, ubuntu-drivers, ubuntu-restricted-extras) — system-level +- NVIDIA drivers — `sudo ubuntu-drivers install` via `./setup ubuntu` # Rime MCP diff --git a/README.md b/README.md index fc0c90d..00d59c6 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,17 @@ Managed via [Nix Home Manager](https://github.com/nix-community/home-manager) dotfiles ├── backup # restic/rsync backup scripts (ntfy notifications) ├── bin # custom scripts -├── desktop # desktop shortcuts, icons, discord theme +├── desktop # icons, discord theme ├── nix │ ├── home │ │ ├── common.nix # CLI tools, git, zsh plugins (all hosts) │ │ ├── desktop.nix # GUI apps (workstations) -│ │ ├── gnome.nix # GNOME extensions, dconf +│ │ ├── firefox.nix # Firefox config, policies, search engines +│ │ ├── gnome.nix # GNOME keybindings, extensions, dconf │ │ ├── tmux.nix # tmux config + resurrect │ │ ├── timers.nix # systemd timers (zephyrus) │ │ ├── pc-timers.nix # backup/sync timers (pc) -│ │ ├── x11.nix / wayland.nix +│ │ ├── wayland.nix │ │ └── hosts/ # per-machine: imports + stateVersion │ └── nixos/ # NixOS system configs ├── nvim # neovim config (lazy.nvim) @@ -44,11 +45,10 @@ git clone https://github.com/MaxWolf-01/dotfiles.git ~/.dotfiles cd ~/.dotfiles && ./setup minimal ``` -Restart shell, then set host and run Home Manager: +Restart shell, then set host (auto-runs first HM switch): ```bash ./setup host zephyrus -nix run home-manager/master -- switch --flake ~/.dotfiles#$NIX_HOST gh auth login -w ``` @@ -70,7 +70,6 @@ All `./setup` functions are idempotent — safe to re-run. ./setup docker ./setup nvidia_container_toolkit ./setup get_vibetyper -./setup tiling_shell ``` diff --git a/nix/home/desktop.nix b/nix/home/desktop.nix index 11ba271..4ad3d7b 100644 --- a/nix/home/desktop.nix +++ b/nix/home/desktop.nix @@ -6,7 +6,7 @@ in home.file.".config/vesktop/themes/custom.theme.css".source = config.lib.file.mkOutOfStoreSymlink "${dotfiles}/desktop/discord/themes/custom.theme.css"; - imports = [ ./ghostty.nix ./newsboat.nix ]; + imports = [ ./firefox.nix ./ghostty.nix ./newsboat.nix ]; home.file.".icons".source = ../../desktop/icons; diff --git a/nix/home/firefox.nix b/nix/home/firefox.nix new file mode 100644 index 0000000..69ee65b --- /dev/null +++ b/nix/home/firefox.nix @@ -0,0 +1,104 @@ +{ ... }: +{ + programs.firefox = { + enable = true; + + policies = { + DisableTelemetry = true; + DisableFirefoxStudies = true; + DisableRemoteImprovements = true; + DontCheckDefaultBrowser = true; + NoDefaultBookmarks = true; + OverrideFirstRunPage = ""; + OverridePostUpdatePage = ""; + + EnableTrackingProtection = { + Value = true; + Cryptomining = true; + Fingerprinting = true; + EmailTracking = true; + }; + + GenerativeAI = { + Chatbot = false; + LinkPreviews = false; + }; + }; + + profiles.default = { + isDefault = true; + + search = { + default = "google"; + force = true; + engines = { + "GitHub" = { + urls = [{ template = "https://github.com/search?q={searchTerms}&type=repositories"; }]; + definedAliases = [ "@gh" ]; + }; + "Nix Packages" = { + urls = [{ template = "https://search.nixos.org/packages?query={searchTerms}"; }]; + definedAliases = [ "@nix" ]; + }; + "PyPI" = { + urls = [{ template = "https://pypi.org/search/?q={searchTerms}"; }]; + definedAliases = [ "@pypi" ]; + }; + "MDN" = { + urls = [{ template = "https://developer.mozilla.org/en-US/search?q={searchTerms}"; }]; + definedAliases = [ "@mdn" ]; + }; + "bing".metaData.hidden = true; + "amazon".metaData.hidden = true; + "ebay".metaData.hidden = true; + }; + }; + + settings = { + # Session & startup + "browser.startup.page" = 3; + "browser.newtabpage.enabled" = false; + "browser.newtabpage.activity-stream.showSponsoredTopSites" = false; + "browser.aboutConfig.showWarning" = false; + + # UI + "browser.toolbars.bookmarks.visibility" = "never"; + "browser.download.autohideButton" = false; + "browser.zoom.siteSpecific" = false; + + # Tab unloading + "browser.tabs.unloadOnLowMemory" = true; + "browser.low_commit_space_threshold_percent" = 5; + + # Search + "browser.search.suggest.enabled" = false; + "browser.urlbar.quicksuggest.dataCollection.enabled" = false; + + # Privacy + "privacy.donottrackheader.enabled" = true; + "privacy.globalprivacycontrol.enabled" = true; + "network.dns.disablePrefetch" = true; + "network.http.speculative-parallel-limit" = 0; + "network.prefetch-next" = false; + "dom.security.https_only_mode" = true; + + # Containers + "privacy.userContext.enabled" = true; + "privacy.userContext.ui.enabled" = true; + + # Appearance + "layout.css.prefers-color-scheme.content-override" = 0; + "layout.spellcheckDefault" = 0; + + # Translations + "browser.translations.automaticallyPopup" = false; + "browser.translations.neverTranslateLanguages" = "de"; + + # Wayland / performance + "media.ffmpeg.vaapi.enabled" = true; + "widget.use-xdg-desktop-portal.file-picker" = 1; + "media.eme.enabled" = true; + }; + }; + }; +} diff --git a/nix/home/gnome.nix b/nix/home/gnome.nix index 0055050..7284f1d 100644 --- a/nix/home/gnome.nix +++ b/nix/home/gnome.nix @@ -10,6 +10,24 @@ "${config.home.homeDirectory}/.nix-profile/share/gnome-shell/extensions/tilingshell@ferrarodomenico.com"; dconf.settings = { + "org/gnome/shell/keybindings" = { + screenshot = [ "s" ]; + screenshot-window = [ "s" ]; + show-screenshot-ui = [ "s" ]; + toggle-message-tray = [ "n" ]; + toggle-overview = []; + # Free Super+N for workspace switching (handled in wm/keybindings) + switch-to-application-1 = []; + switch-to-application-2 = []; + switch-to-application-3 = []; + switch-to-application-4 = []; + switch-to-application-5 = []; + switch-to-application-6 = []; + switch-to-application-7 = []; + switch-to-application-8 = []; + switch-to-application-9 = []; + }; + "org/gnome/shell" = { enabled-extensions = [ "tilingshell@ferrarodomenico.com" From 67aca55aca75dec2ee304929745a9118979029f0 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 04:40:03 +0100 Subject: [PATCH 04/32] fix: ghostty keybind use list for duplicate keys Newline-joined string only prefixed the first line with `keybind =`, causing Ghostty to treat the rest as unknown top-level fields. Co-Authored-By: Claude Opus 4.6 --- nix/home/ghostty.nix | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/nix/home/ghostty.nix b/nix/home/ghostty.nix index 93ab373..47e0b6a 100644 --- a/nix/home/ghostty.nix +++ b/nix/home/ghostty.nix @@ -9,7 +9,18 @@ confirm-close-surface = false; copy-on-select = "clipboard"; # Pass Alt+N through to tmux instead of ghostty tab switching - keybind = "alt+one=unbind\nalt+two=unbind\nalt+three=unbind\nalt+four=unbind\nalt+five=unbind\nalt+six=unbind\nalt+seven=unbind\nalt+eight=unbind\nalt+nine=unbind\nctrl+shift+t=unbind"; + keybind = [ + "alt+one=unbind" + "alt+two=unbind" + "alt+three=unbind" + "alt+four=unbind" + "alt+five=unbind" + "alt+six=unbind" + "alt+seven=unbind" + "alt+eight=unbind" + "alt+nine=unbind" + "ctrl+shift+t=unbind" + ]; }; }; From fc578961dfeb208704a99a1a7ce209f8cbfb9778 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 04:59:05 +0100 Subject: [PATCH 05/32] fix: set ghostty as default terminal via xdg-terminals.list GNOME's Ctrl+Alt+T uses xdg-terminal-exec, which defaults to ptyxis on Ubuntu 26. Setting xdg-terminals.list tells it to use ghostty. Co-Authored-By: Claude Opus 4.6 --- nix/home/desktop.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/home/desktop.nix b/nix/home/desktop.nix index 4ad3d7b..e62d671 100644 --- a/nix/home/desktop.nix +++ b/nix/home/desktop.nix @@ -23,6 +23,8 @@ in zathura ]; + xdg.configFile."xdg-terminals.list".text = "com.mitchellh.ghostty.desktop\n"; + xdg.desktopEntries.vesktop = { name = "Vesktop"; genericName = "Discord Client"; From 3cf007e404cbcfba6f31ef39d67809ab76690e81 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 04:59:27 +0100 Subject: [PATCH 06/32] nix gc, auto-optimise, dconf uint32 fix, ubuntu-install script - common.nix: add weekly nix GC (--delete-older-than 30d) - nix.conf: enable auto-optimise-store (hardlink dedup) - pc/configuration.nix: system-level GC + auto-optimise-store - gnome.nix: fix idle-delay/lock-delay with mkUint32 (were silently ignored), add power management settings, disable ibus emoji - setup: remove /usr/lib/firefox so HM Firefox takes precedence - bin/ubuntu-install: debootstrap installer for Ubuntu 26.04 LUKS+LVM+EFI, interactive disk/root-size prompts Co-Authored-By: Claude Opus 4.6 --- bin/ubuntu-install | 328 +++++++++++++++++++++++++++++++++ nix/home/common.nix | 5 + nix/home/gnome.nix | 16 +- nix/nix.conf | 1 + nix/nixos/pc/configuration.nix | 7 + setup | 6 + 6 files changed, 361 insertions(+), 2 deletions(-) create mode 100755 bin/ubuntu-install diff --git a/bin/ubuntu-install b/bin/ubuntu-install new file mode 100755 index 0000000..1d3515c --- /dev/null +++ b/bin/ubuntu-install @@ -0,0 +1,328 @@ +#!/usr/bin/env bash +# Ubuntu debootstrap installer — LUKS + LVM + EFI +# +# Two-layer design: +# Layer 1 (interactive): disk selection + partitioning + LUKS + LVM +# Layer 2 (automated): debootstrap + chroot config + GRUB +# +# Run from a live USB environment (Ubuntu installer or any live Linux). +# Requires: debootstrap, sgdisk, cryptsetup, lvm2 +# +# Arguments: +# DISK target disk (e.g. /dev/nvme0n1). Prompted if omitted. +# --root GB root partition size in GB (required) +# +# From a live USB: +# sudo apt-get install -y debootstrap gdisk +# curl -sLO https://raw.githubusercontent.com/MaxWolf-01/dotfiles/master/bin/ubuntu-install +# chmod +x ubuntu-install && sudo ./ubuntu-install /dev/nvme0n1 --root 200 + +set -euo pipefail + +CODENAME="resolute" # Ubuntu 26.04 +LOCALE="en_US.UTF-8" +TIMEZONE="Europe/Vienna" +KEYBOARD_LAYOUT="de" +KEYBOARD_VARIANT="nodeadkeys" +USERNAME="max" +HOSTNAME="" +ROOT_SIZE="" + +# Parse arguments +POSITIONAL=() +while [[ $# -gt 0 ]]; do + case $1 in + --root) ROOT_SIZE="${2}G"; shift 2 ;; + *) POSITIONAL+=("$1"); shift ;; + esac +done + +if [[ -z "$ROOT_SIZE" ]]; then + read -rp "$(echo -e "${BOLD}Root partition size in GB:${NC} ")" root_gb + [[ -n "$root_gb" ]] || die "Root size cannot be empty" + ROOT_SIZE="${root_gb}G" +fi + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BOLD='\033[1m' +NC='\033[0m' + +info() { echo -e "${GREEN}>>>${NC} $*"; } +warn() { echo -e "${YELLOW}⚠${NC} $*"; } +error() { echo -e "${RED}✗${NC} $*" >&2; } +die() { error "$@"; exit 1; } + +confirm() { + local prompt="$1" + local response + read -rp "$(echo -e "${BOLD}${prompt}${NC} [y/N] ")" response + [[ "$response" =~ ^[Yy]$ ]] +} + +# ============================================================ +# Preflight checks +# ============================================================ + +[[ $EUID -eq 0 ]] || die "Must run as root" + +for cmd in debootstrap mkfs.vfat mkfs.ext4 cryptsetup lvcreate pvcreate vgcreate; do + command -v "$cmd" &>/dev/null || die "Missing: $cmd" +done + +# ============================================================ +# Layer 1: Disk selection + partitioning +# ============================================================ + +disk_selection() { + if [[ -n "${1:-}" ]]; then + DISK="$1" + else + info "Available disks:" + lsblk -d -o NAME,SIZE,MODEL,TRAN | grep -v '^loop\|^sr\|^NAME' + echo + read -rp "$(echo -e "${BOLD}Target disk (e.g. /dev/nvme0n1):${NC} ")" DISK + fi + + [[ -b "$DISK" ]] || die "$DISK is not a block device" + + # Determine partition suffix (nvme uses p1, sda uses 1) + if [[ "$DISK" == *nvme* ]] || [[ "$DISK" == *mmcblk* ]]; then + PART_PREFIX="${DISK}p" + else + PART_PREFIX="${DISK}" + fi + + echo + warn "This will ERASE ALL DATA on $DISK" + lsblk "$DISK" 2>/dev/null + echo + confirm "Continue?" || exit 0 +} + +read_hostname() { + if [[ -z "$HOSTNAME" ]]; then + read -rp "$(echo -e "${BOLD}Hostname:${NC} ")" HOSTNAME + [[ -n "$HOSTNAME" ]] || die "Hostname cannot be empty" + fi +} + +partition_disk() { + info "Partitioning $DISK (GPT: EFI + boot + LUKS)" + + sgdisk --zap-all "$DISK" + sgdisk -n 1:0:+1G -t 1:ef00 -c 1:"EFI" "$DISK" + sgdisk -n 2:0:+2G -t 2:8300 -c 2:"boot" "$DISK" + sgdisk -n 3:0:0 -t 3:8309 -c 3:"luks" "$DISK" + partprobe "$DISK" + sleep 1 + + EFI_PART="${PART_PREFIX}1" + BOOT_PART="${PART_PREFIX}2" + LUKS_PART="${PART_PREFIX}3" +} + +setup_luks_lvm() { + info "Setting up LUKS on $LUKS_PART" + cryptsetup luksFormat --type luks2 "$LUKS_PART" + cryptsetup open "$LUKS_PART" cryptroot + + info "Setting up LVM (root=${ROOT_SIZE}, home=rest)" + pvcreate /dev/mapper/cryptroot + vgcreate vg0 /dev/mapper/cryptroot + lvcreate -L "$ROOT_SIZE" -n root vg0 + lvcreate -l 100%FREE -n home vg0 +} + +format_filesystems() { + info "Formatting filesystems" + mkfs.vfat -F32 "$EFI_PART" + mkfs.ext4 -q "$BOOT_PART" + mkfs.ext4 -q /dev/vg0/root + mkfs.ext4 -q /dev/vg0/home +} + +mount_filesystems() { + info "Mounting filesystems" + mount /dev/vg0/root /mnt + mkdir -p /mnt/boot /mnt/home + mount "$BOOT_PART" /mnt/boot + mount /dev/vg0/home /mnt/home + mkdir -p /mnt/boot/efi + mount "$EFI_PART" /mnt/boot/efi +} + +# ============================================================ +# Layer 2: OS bootstrap (fully automated from here) +# ============================================================ + +install_base() { + info "Running debootstrap ($CODENAME)" + debootstrap "$CODENAME" /mnt http://archive.ubuntu.com/ubuntu +} + +configure_sources() { + info "Configuring apt sources (deb822)" + cat > /mnt/etc/apt/sources.list.d/ubuntu.sources < /mnt/etc/apt/sources.list +} + +configure_system() { + info "Configuring hostname, locale, timezone, keyboard" + + echo "$HOSTNAME" > /mnt/etc/hostname + cat > /mnt/etc/hosts < /mnt/etc/default/locale + + # Timezone + ln -sf "/usr/share/zoneinfo/$TIMEZONE" /mnt/etc/localtime + chroot /mnt dpkg-reconfigure -f noninteractive tzdata + + # Keyboard + cat > /mnt/etc/default/keyboard < /mnt/etc/fstab < +UUID=${root_uuid} / ext4 errors=remount-ro 0 1 +UUID=${home_uuid} /home ext4 defaults 0 2 +UUID=${boot_uuid} /boot ext4 defaults 0 2 +UUID=${efi_uuid} /boot/efi vfat umask=0077 0 1 +FSTAB + + # crypttab + local luks_uuid + luks_uuid=$(blkid -s UUID -o value "$LUKS_PART") + echo "cryptroot UUID=${luks_uuid} none luks,discard" > /mnt/etc/crypttab +} + +install_packages() { + info "Installing kernel, desktop, bootloader, crypto" + + # Bind-mount for chroot + mount --bind /dev /mnt/dev + mount --bind /dev/pts /mnt/dev/pts + mount --bind /proc /mnt/proc + mount --bind /sys /mnt/sys + mount --bind /run /mnt/run + + chroot /mnt apt-get update + chroot /mnt apt-get install -y \ + linux-generic \ + ubuntu-desktop \ + grub-efi-amd64 \ + cryptsetup-initramfs \ + lvm2 \ + openssh-server \ + network-manager +} + +configure_user() { + info "Creating user $USERNAME" + chroot /mnt useradd -m -s /bin/bash -G sudo "$USERNAME" + info "Set password for $USERNAME:" + chroot /mnt passwd "$USERNAME" + + # SSH: allow key auth from github + local ssh_dir="/mnt/home/${USERNAME}/.ssh" + mkdir -p "$ssh_dir" + curl -sL "https://github.com/${USERNAME}Wolf-01.keys" > "${ssh_dir}/authorized_keys" + chmod 700 "$ssh_dir" + chmod 600 "${ssh_dir}/authorized_keys" + chroot /mnt chown -R "${USERNAME}:${USERNAME}" "/home/${USERNAME}/.ssh" +} + +install_bootloader() { + info "Installing GRUB and rebuilding initramfs" + chroot /mnt update-initramfs -u -k all + chroot /mnt grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu + chroot /mnt update-grub +} + +cleanup() { + info "Unmounting" + umount -R /mnt 2>/dev/null || true + cryptsetup close cryptroot 2>/dev/null || true + vgchange -an vg0 2>/dev/null || true +} + +# ============================================================ +# Main +# ============================================================ + +trap cleanup EXIT + +echo +echo -e "${BOLD}Ubuntu ${CODENAME} debootstrap installer${NC}" +echo -e "${BOLD}LUKS + LVM + EFI${NC}" +echo + +disk_selection "${POSITIONAL[0]:-}" +read_hostname + +echo +info "Configuration:" +echo " Disk: $DISK" +echo " Hostname: $HOSTNAME" +echo " Root: $ROOT_SIZE" +echo " Home: (remaining space)" +echo " LUKS: ${PART_PREFIX}3" +echo " Codename: $CODENAME" +echo " Locale: $LOCALE" +echo " Timezone: $TIMEZONE" +echo " Keyboard: ${KEYBOARD_LAYOUT}+${KEYBOARD_VARIANT}" +echo +confirm "Proceed with install?" || exit 0 + +partition_disk +setup_luks_lvm +format_filesystems +mount_filesystems +install_base +configure_sources +configure_system +configure_fstab_crypttab +install_packages +configure_user +install_bootloader + +trap - EXIT +info "Unmounting" +umount -R /mnt +cryptsetup close cryptroot +vgchange -an vg0 + +echo +info "Done! Reboot and remove the USB drive." +info "After boot, run: ~/.dotfiles/setup minimal && ~/.dotfiles/setup host $HOSTNAME" diff --git a/nix/home/common.nix b/nix/home/common.nix index f0528a9..3131784 100644 --- a/nix/home/common.nix +++ b/nix/home/common.nix @@ -6,6 +6,11 @@ programs.home-manager.enable = true; + nix.gc = { + automatic = true; + options = "--delete-older-than 30d"; + }; + # Non-NixOS: add ~/.nix-profile/share to XDG_DATA_DIRS so GNOME finds desktop entries targets.genericLinux.enable = lib.mkDefault true; diff --git a/nix/home/gnome.nix b/nix/home/gnome.nix index 7284f1d..b761ad8 100644 --- a/nix/home/gnome.nix +++ b/nix/home/gnome.nix @@ -44,12 +44,18 @@ }; "org/gnome/desktop/session" = { - idle-delay = 3600; + idle-delay = lib.hm.gvariant.mkUint32 3600; }; "org/gnome/desktop/screensaver" = { lock-enabled = true; - lock-delay = 7200; + lock-delay = lib.hm.gvariant.mkUint32 7200; + }; + + "org/gnome/settings-daemon/plugins/power" = { + sleep-inactive-ac-timeout = lib.hm.gvariant.mkUint32 0; + sleep-inactive-battery-timeout = lib.hm.gvariant.mkUint32 3600; + idle-dim = false; }; # Window management keybindings @@ -107,6 +113,12 @@ power-button-action = "interactive"; }; + "org/freedesktop/ibus/panel/emoji" = { + hotkey = [ "" ]; + unicode-hotkey = [ "" ]; + load-emoji-at-startup = false; + }; + # Tiling Shell extension "org/gnome/shell/extensions/tilingshell" = { enable-autotiling = true; diff --git a/nix/nix.conf b/nix/nix.conf index 32cf45b..ea5019b 100644 --- a/nix/nix.conf +++ b/nix/nix.conf @@ -1,2 +1,3 @@ experimental-features = nix-command flakes download-buffer-size = 1073741824 +auto-optimise-store = true diff --git a/nix/nixos/pc/configuration.nix b/nix/nixos/pc/configuration.nix index 01a89a3..3eef200 100644 --- a/nix/nixos/pc/configuration.nix +++ b/nix/nixos/pc/configuration.nix @@ -112,6 +112,13 @@ nix.settings = { experimental-features = [ "nix-command" "flakes" ]; trusted-users = [ "max" ]; + auto-optimise-store = true; + }; + + nix.gc = { + automatic = true; + dates = "weekly"; + options = "--delete-older-than 30d"; }; # Disable deprecated udev-settle (times out waiting for NVIDIA/ZFS events, nothing needs it) diff --git a/setup b/setup index 3cd4ef4..90c9c36 100755 --- a/setup +++ b/setup @@ -138,6 +138,12 @@ ubuntu() { # Remove bloat (brltty grabs USB serial devices) sudo apt-get remove -y orca brltty + # Remove system Firefox install script so HM Firefox takes precedence + if [ -d /usr/lib/firefox ]; then + sudo mv /usr/lib/firefox /usr/lib/firefox.bak + echo "Moved /usr/lib/firefox → /usr/lib/firefox.bak (HM Firefox takes priority)" + fi + echo "Run './setup gpu' after reboot to set up Nix GPU driver symlinks." } From 4e71ce32ccbe090072b8009b174968f6054e4707 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 05:22:56 +0100 Subject: [PATCH 07/32] add restore-working and migrate-firefox-snap scripts - restore-working: restore latest "working" backup from rsync.net - migrate-firefox-snap: move Firefox profile from snap to HM path Co-Authored-By: Claude Opus 4.6 --- bin/migrate-firefox-snap | 31 +++++++++++++++++++++++ bin/restore-working | 53 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100755 bin/migrate-firefox-snap create mode 100755 bin/restore-working diff --git a/bin/migrate-firefox-snap b/bin/migrate-firefox-snap new file mode 100755 index 0000000..99a3151 --- /dev/null +++ b/bin/migrate-firefox-snap @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Migrate Firefox profile from snap path to HM path +# +# snap/firefox/common/.mozilla/firefox → ~/.mozilla/firefox +# +# Run once after restoring a backup that has the snap-era Firefox profile. + +set -euo pipefail + +SNAP_DIR="$HOME/snap/firefox/common/.mozilla/firefox" +HM_DIR="$HOME/.mozilla/firefox" + +die() { echo "Error: $*" >&2; exit 1; } + +[[ -d "$SNAP_DIR" ]] || die "No snap Firefox profile found at $SNAP_DIR" + +if [[ -d "$HM_DIR" ]]; then + echo "~/.mozilla/firefox already exists." + read -rp "Replace it with snap profile? [y/N] " response + [[ "$response" =~ ^[Yy]$ ]] || exit 0 + rm -rf "$HM_DIR" +fi + +mkdir -p "$HOME/.mozilla" +mv "$SNAP_DIR" "$HM_DIR" + +# Clean up empty snap dirs +rm -rf "$HOME/snap/firefox" 2>/dev/null || true +rmdir "$HOME/snap" 2>/dev/null || true + +echo "Done. Firefox profile moved to ~/.mozilla/firefox" diff --git a/bin/restore-working b/bin/restore-working new file mode 100755 index 0000000..b3752c5 --- /dev/null +++ b/bin/restore-working @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Restore "working" backup from rsync.net to $HOME +# +# Requires: restic, sops, age key at ~/.local/secrets/age-key.txt +# Run after: setup minimal, setup host, setup secrets +# +# Usage: +# restore-working # restore latest snapshot +# restore-working --list # list available snapshots +# restore-working --snapshot ID + +set -euo pipefail + +BACKUP_DIR="$HOME/.dotfiles/secrets/backup/restic/working" + +info() { echo -e "\033[0;32m>>>\033[0m $*"; } +die() { echo -e "\033[0;31mx\033[0m $*" >&2; exit 1; } + +source "$BACKUP_DIR/_common" +source "$BACKUP_DIR/rsyncnet.conf" + +export RESTIC_REPOSITORY="$repo_path" +export RESTIC_PASSWORD_COMMAND="$password_command" + +SNAPSHOT="latest" +while [[ $# -gt 0 ]]; do + case $1 in + --list) restic snapshots; exit 0 ;; + --snapshot) SNAPSHOT="$2"; shift 2 ;; + *) die "Unknown argument: $1" ;; + esac +done + +[[ -f "$HOME/.local/secrets/age-key.txt" ]] || die "Age key not found. Run './setup secrets' first." +command -v restic &>/dev/null || die "restic not found" + +if [[ "$SNAPSHOT" == "latest" ]]; then + SNAPSHOT_ID=$(restic snapshots --json | jq -r '.[-1].id') + [[ -n "$SNAPSHOT_ID" && "$SNAPSHOT_ID" != "null" ]] || die "No snapshots found" + SNAPSHOT_DATE=$(restic snapshots --json | jq -r '.[-1].time[:19]') + info "Latest snapshot: ${SNAPSHOT_ID:0:8} ($SNAPSHOT_DATE)" +else + SNAPSHOT_ID="$SNAPSHOT" + info "Using snapshot: ${SNAPSHOT_ID:0:8}" +fi + +read -rp "Restore snapshot ${SNAPSHOT_ID:0:8} to $HOME? [y/N] " response +[[ "$response" =~ ^[Yy]$ ]] || exit 0 + +info "Restoring..." +restic restore "$SNAPSHOT_ID" --target / --overwrite always + +info "Done." From 0191420f367caee00f550bc7269ff3d8158958e8 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 05:29:27 +0100 Subject: [PATCH 08/32] fix: remove manual nix-gc service that conflicts with nix.gc module HM's built-in nix.gc already generates the systemd service and timer with sensible defaults (weekly, persistent). The manual definition caused a duplicate ExecStart error. Co-Authored-By: Claude Opus 4.6 --- nix/home/common.nix | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/nix/home/common.nix b/nix/home/common.nix index 3131784..648fab2 100644 --- a/nix/home/common.nix +++ b/nix/home/common.nix @@ -254,23 +254,4 @@ yt-dlp ]; - # Weekly GC: delete generations older than 30 days, then remove unreferenced store paths. - # Works together with min-free/max-free in /etc/nix/nix.conf (daemon-level safety net). - systemd.user.services.nix-gc = { - Unit.Description = "Nix garbage collection"; - Service = { - Type = "oneshot"; - ExecStart = "${pkgs.nix}/bin/nix-collect-garbage --delete-older-than 30d"; - }; - }; - - systemd.user.timers.nix-gc = { - Unit.Description = "Weekly Nix garbage collection"; - Timer = { - OnCalendar = "weekly"; - Persistent = true; - RandomizedDelaySec = "1h"; - }; - Install.WantedBy = [ "timers.target" ]; - }; } From 6abc484ed53469f42971f95862b4b59f457e5c73 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 06:17:00 +0100 Subject: [PATCH 09/32] docs: rewrite Ubuntu setup for debootstrap + restore flow Co-Authored-By: Claude Opus 4.6 --- README.md | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 00d59c6..a73c981 100644 --- a/README.md +++ b/README.md @@ -39,32 +39,45 @@ dotfiles ## Setup (Ubuntu) +### Fresh install (LUKS + LVM) + +Boot a live USB, then: + ```bash -sudo apt-get update && sudo apt-get install -y git -git clone https://github.com/MaxWolf-01/dotfiles.git ~/.dotfiles -cd ~/.dotfiles && ./setup minimal +sudo apt-get install -y debootstrap gdisk +curl -sLO https://raw.githubusercontent.com/MaxWolf-01/dotfiles/master/bin/ubuntu-install +chmod +x ubuntu-install && sudo ./ubuntu-install /dev/nvmeXnY --root 200 ``` -Restart shell, then set host (auto-runs first HM switch): +Reboot, remove USB. + +### Post-install ```bash -./setup host zephyrus -gh auth login -w +sudo apt-get update && sudo apt-get install -y git +git clone https://github.com/MaxWolf-01/dotfiles.git ~/.dotfiles +cd ~/.dotfiles && ./setup minimal ``` -After first run, use `hmswitch` to apply changes. +Restart shell, then: -Place your age key at `~/.local/secrets/age-key.txt` (copy from another machine or backup), then: ```bash -./setup secrets -./setup ubuntu +./setup host zephyrus # sets NIX_HOST, runs first HM switch +./setup ubuntu # NVIDIA drivers, codecs, cleanup +sudo reboot +./setup gpu # nix GPU driver symlinks ./setup get_claude +gh auth login -w +./setup secrets # clones private secrets repo, decrypts +restore-working # restore data from rsync.net backup ``` +After first run, use `hmswitch` to apply HM changes. + All `./setup` functions are idempotent — safe to re-run.
-Other common setup functions for the daily driver +Other setup functions ```bash ./setup docker From 621446ef8129e96af3c9432e1528910dcb83e94b Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 06:18:43 +0100 Subject: [PATCH 10/32] firejail: sandbox Firefox with whitelist-only home access Firefox can only see ~/Downloads, ~/Pictures, ~/Music, ~/Videos, and ~/.mozilla. Screenshots and screencasts blacklisted within those dirs. Everything else in ~ (secrets, SSH keys, repos, claude sessions, dotfiles) is invisible. Desktop entry launches through firejail; GNOME keybindings and link clicks inherit the sandbox. Co-Authored-By: Claude Opus 4.6 --- firejail/firefox.local | 9 +++++++++ nix/home/firefox.nix | 19 +++++++++++++++++++ setup | 22 +++++++++++++++------- 3 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 firejail/firefox.local diff --git a/firejail/firefox.local b/firejail/firefox.local new file mode 100644 index 0000000..d37135f --- /dev/null +++ b/firejail/firefox.local @@ -0,0 +1,9 @@ +# Extends the default firefox.profile shipped with firejail. +# Whitelist mode: tmpfs over ~, only these paths visible to Firefox. + +whitelist ${HOME}/Downloads +whitelist ${HOME}/Pictures +blacklist ${HOME}/Pictures/Screenshots +whitelist ${HOME}/Music +whitelist ${HOME}/Videos +blacklist ${HOME}/Videos/Screencasts diff --git a/nix/home/firefox.nix b/nix/home/firefox.nix index 69ee65b..188ae47 100644 --- a/nix/home/firefox.nix +++ b/nix/home/firefox.nix @@ -1,5 +1,24 @@ { ... }: { + home.file.".config/firejail/firefox.local".source = ../../firejail/firefox.local; + + xdg.desktopEntries.firefox = { + name = "Firefox"; + genericName = "Web Browser"; + exec = "firejail firefox %u"; + icon = "firefox"; + type = "Application"; + categories = [ "Network" "WebBrowser" ]; + mimeType = [ + "text/html" + "x-scheme-handler/http" + "x-scheme-handler/https" + "application/xhtml+xml" + "application/pdf" + ]; + terminal = false; + }; + programs.firefox = { enable = true; diff --git a/setup b/setup index 90c9c36..262c028 100755 --- a/setup +++ b/setup @@ -113,6 +113,7 @@ ubuntu() { # System-level packages not available via Nix/HM sudo apt-get install -y \ + firejail \ libfuse2 \ ubuntu-drivers-common @@ -241,22 +242,29 @@ get_claude() { } secrets() { - local key_file="$HOME/.local/secrets/age-key.txt" + local key_plain="$HOME/.local/secrets/age-key.txt" + local key_enc="$HOME/.local/secrets/age-key.txt.enc" + local key_tmpfs="/run/user/$(id -u)/age-key.txt" if ! [ -d ~/.dotfiles/secrets ]; then gh repo clone MaxWolf-01/secrets ~/.dotfiles/secrets -- --depth 1 fi - if [ ! -f "$key_file" ]; then - echo "Age key not found at $key_file" - echo "Generate a new key with: age-keygen -o $key_file" + if [ ! -f "$key_plain" ]; then + mkdir -p "$(dirname "$key_plain")" + echo "Age key not found at $key_plain" + echo "Generate a new key with: age-keygen -o $key_plain" echo "Or place your existing key there, then run './setup secrets' again" - return + echo "" + echo "To encrypt at rest (recommended when no FDE):" + echo " age -p -o $key_enc $key_plain && rm $key_plain" + echo " The login hook in secrets/zshrc will decrypt to tmpfs on each login." + return 1 fi - cd ~/.dotfiles/secrets && git pull + git -C ~/.dotfiles/secrets pull - export SOPS_AGE_KEY_FILE="$key_file" + export SOPS_AGE_KEY_FILE="$key_plain" sops -d ~/.dotfiles/secrets/api_keys/wakatime.cfg > ~/.wakatime.cfg } From b37ee1e476cb3febd74d95f19b3d706ff1aac8e9 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 06:43:44 +0100 Subject: [PATCH 11/32] fix: graceful fallback when firejail is not installed Co-Authored-By: Claude Opus 4.6 --- nix/home/firefox.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/home/firefox.nix b/nix/home/firefox.nix index 188ae47..ee2c40b 100644 --- a/nix/home/firefox.nix +++ b/nix/home/firefox.nix @@ -5,7 +5,7 @@ xdg.desktopEntries.firefox = { name = "Firefox"; genericName = "Web Browser"; - exec = "firejail firefox %u"; + exec = "sh -c 'if command -v firejail >/dev/null; then firejail firefox %u; else firefox %u; fi'"; icon = "firefox"; type = "Application"; categories = [ "Network" "WebBrowser" ]; From 962b814838354b5daf8de9754fa0bc47158e37f8 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 07:02:25 +0100 Subject: [PATCH 12/32] fix: revert firefox desktop entry to direct firejail exec The sh -c wrapper with conditionals isn't valid in desktop entry Exec fields (reserved characters). firejail is installed via apt on Ubuntu, so it's always available on desktop machines. Co-Authored-By: Claude Opus 4.6 --- nix/home/firefox.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/home/firefox.nix b/nix/home/firefox.nix index ee2c40b..188ae47 100644 --- a/nix/home/firefox.nix +++ b/nix/home/firefox.nix @@ -5,7 +5,7 @@ xdg.desktopEntries.firefox = { name = "Firefox"; genericName = "Web Browser"; - exec = "sh -c 'if command -v firejail >/dev/null; then firejail firefox %u; else firefox %u; fi'"; + exec = "firejail firefox %u"; icon = "firefox"; type = "Application"; categories = [ "Network" "WebBrowser" ]; From 52c482cfd661e7bcf0d175dfd76cfb35209a6c6e Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 07:26:14 +0100 Subject: [PATCH 13/32] fix: firejail + nix firefox (AppArmor, GNOME PATH) Firejail's AppArmor profile blocks execution from /nix/store, causing "Permission denied" on .firefox-wrapped. Disable AppArmor confinement in firefox.local (all other sandbox layers remain active). Desktop entry now uses absolute paths so GNOME can launch firefox without ~/.nix-profile/bin in its PATH. Co-Authored-By: Claude Opus 4.6 --- firejail/firefox.local | 4 ++++ nix/home/firefox.nix | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/firejail/firefox.local b/firejail/firefox.local index d37135f..c6ee47b 100644 --- a/firejail/firefox.local +++ b/firejail/firefox.local @@ -7,3 +7,7 @@ blacklist ${HOME}/Pictures/Screenshots whitelist ${HOME}/Music whitelist ${HOME}/Videos blacklist ${HOME}/Videos/Screencasts + +# Nix-installed Firefox lives in /nix/store; firejail's AppArmor profile +# only allows execution from standard system paths. +ignore apparmor diff --git a/nix/home/firefox.nix b/nix/home/firefox.nix index 188ae47..78f7564 100644 --- a/nix/home/firefox.nix +++ b/nix/home/firefox.nix @@ -1,11 +1,14 @@ -{ ... }: +{ config, ... }: +let + firefoxBin = "${config.home.homeDirectory}/.nix-profile/bin/firefox"; +in { home.file.".config/firejail/firefox.local".source = ../../firejail/firefox.local; xdg.desktopEntries.firefox = { name = "Firefox"; genericName = "Web Browser"; - exec = "firejail firefox %u"; + exec = "/usr/bin/firejail ${firefoxBin} %u"; icon = "firefox"; type = "Application"; categories = [ "Network" "WebBrowser" ]; From bf33d3048def278b342c062ff1ee825b1f38b352 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 08:10:16 +0100 Subject: [PATCH 14/32] fix: statusline command exit code when duration is empty The last command `[ -n "$dur_str" ] && printf ...` exits 1 when dur_str is empty, causing Claude Code to suppress the entire statusline output. Append `; :` (no-op) to ensure exit 0. Co-Authored-By: Claude Opus 4.6 --- claude/settings.json | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/claude/settings.json b/claude/settings.json index 1956d40..ace96e9 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -16,7 +16,6 @@ "Edit(//tmp/**)", "WebSearch", "WebFetch", - "Bash(basename *)", "Bash(cat *)", "Bash(curl *)", @@ -93,17 +92,14 @@ "Bash(which *)", "Bash(whoami)", "Bash(whoami *)", - "Bash(grep *)", "Bash(rg *)", "Bash(sg *)", "Bash(ast-grep *)", "Bash(fastmod *)", - "Bash(nix run nixpkgs#fd *)", "Bash(nix search *)", "Bash(nix-env *)", - "Bash(coredumpctl list)", "Bash(coredumpctl list *)", "Bash(dpkg -l)", @@ -124,7 +120,6 @@ "Bash(systemctl --user list-timers *)", "Bash(tmux show-options)", "Bash(tmux show-options *)", - "Bash(uv run *)", "Bash(uvx *)", "Bash(ty)", @@ -134,23 +129,19 @@ "Bash(npx tsc)", "Bash(npx tsc *)", "Bash(npm view *)", - "Bash(codex --version)", "Bash(codex --version *)", "Bash(mx *)", - "Bash(gnome-extensions info *)", "Bash(gnome-extensions list)", "Bash(gnome-extensions list *)", "Bash(gsettings get *)", "Bash(gsettings list-recursively)", "Bash(gsettings list-recursively *)", - "Bash(claude config --help)", "Bash(claude mcp *)", "Bash(claude plugin marketplace list)", "Bash(claude plugin marketplace list *)", - "Bash(git status)", "Bash(git status *)", "Bash(git log)", @@ -201,7 +192,6 @@ "Bash(git fetch *)", "Bash(git worktree list)", "Bash(git worktree list *)", - "Bash(git -C * status)", "Bash(git -C * status *)", "Bash(git -C * log)", @@ -253,7 +243,6 @@ "Bash(git -C * worktree list)", "Bash(git -C * worktree list *)", "Bash(git -C * check-ignore *)", - "Bash(gh api -X GET *)", "Bash(gh auth status)", "Bash(gh auth status *)", @@ -299,7 +288,6 @@ "Bash(gh workflow list)", "Bash(gh workflow list *)", "Bash(gh workflow view *)", - "Bash(ssh * cat *)", "Bash(ssh * head *)", "Bash(ssh * tail *)", @@ -341,7 +329,6 @@ "Bash(ssh * ps *)", "Bash(ssh * id)", "Bash(ssh * id *)", - "Bash(ssh * docker ps)", "Bash(ssh * docker ps *)", "Bash(ssh * docker logs *)", @@ -358,9 +345,7 @@ "Bash(ssh * docker compose ps *)", "Bash(ssh * docker compose logs)", "Bash(ssh * docker compose logs *)", - "Bash(ssh-keyscan *)", - "Bash(docker ps)", "Bash(docker ps *)", "Bash(docker images)", @@ -378,7 +363,6 @@ "Bash(docker compose ps *)", "Bash(docker compose logs)", "Bash(docker compose logs *)", - "Bash(nix flake check)", "Bash(nix flake check *)", "Bash(sleep *)" @@ -387,7 +371,7 @@ }, "statusLine": { "type": "command", - "command": "input=$(cat); sid=$(echo \"$input\" | jq -r '.session_id // \"\"'); model=$(echo \"$input\" | jq -r '.model.display_name'); dir=$(echo \"$input\" | jq -r '.workspace.current_dir' | sed 's|.*/||'); tp=$(echo \"$input\" | jq -r '.transcript_path // \"\"'); dur_str=\"\"; if [ -n \"$tp\" ] && [ -f \"$tp\" ]; then t1=$(head -1 \"$tp\" | jq -r '.timestamp // empty' 2>/dev/null); t2=$(tail -1 \"$tp\" | jq -r '.timestamp // empty' 2>/dev/null); if [ -n \"$t1\" ] && [ -n \"$t2\" ]; then s1=$(date -d \"$t1\" +%s 2>/dev/null); s2=$(date -d \"$t2\" +%s 2>/dev/null); if [ -n \"$s1\" ] && [ -n \"$s2\" ]; then dur=$((s2 - s1)); h=$((dur / 3600)); m=$(( (dur % 3600) / 60 )); if [ $h -gt 0 ]; then dur_str=\"${h}h${m}m\"; elif [ $m -gt 0 ]; then dur_str=\"${m}m\"; else dur_str=\"<1m\"; fi; fi; fi; fi; pct=$(echo \"$input\" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1); git_info=\"\"; branch=$(git --no-optional-locks branch --show-current 2>/dev/null); if [ -n \"$branch\" ]; then git_info=\"$branch\"; ab=$(git --no-optional-locks rev-list --left-right --count @{u}...HEAD 2>/dev/null) && { behind=$(echo \"$ab\" | awk '{print $1}'); ahead=$(echo \"$ab\" | awk '{print $2}'); div=\"\"; [ \"${ahead:-0}\" -gt 0 ] && div=\"↑${ahead}\"; [ \"${behind:-0}\" -gt 0 ] && div=\"${div}↓${behind}\"; [ -n \"$div\" ] && git_info=\"${git_info} ${div}\"; }; status=$(git --no-optional-locks status --porcelain 2>/dev/null); if [ -n \"$status\" ]; then staged=$(echo \"$status\" | grep -c '^[MADRC]'); modified=$(echo \"$status\" | grep -c '^.[MD]'); untracked=$(echo \"$status\" | grep -c '^??'); counts=\"\"; [ \"${staged:-0}\" -gt 0 ] && counts=\"+${staged}\"; [ \"${modified:-0}\" -gt 0 ] && counts=\"${counts}${counts:+ }~${modified}\"; [ \"${untracked:-0}\" -gt 0 ] && counts=\"${counts}${counts:+ }?${untracked}\"; [ -n \"$counts\" ] && git_info=\"${git_info} ${counts}\"; fi; fi; [ -n \"$sid\" ] && printf \"\\e[90m%s\\e[0m \\e[90m│\\e[0m \" \"$sid\"; printf \"\\e[36m[\\e[0m\\e[37m%s\\e[0m\\e[36m]\\e[0m \\e[37m%s\\e[0m \\e[32min\\e[0m \\e[33;1m%s\\e[0m \\e[32mwith\\e[0m \\e[36m%s\\e[0m\" \"$(hostname -s)\" \"$(whoami)\" \"$dir\" \"$model\"; [ -n \"$git_info\" ] && printf \" \\e[90m│\\e[0m \\e[35m%s\\e[0m\" \"$git_info\"; filled=$((pct / 10)); [ $filled -gt 10 ] && filled=10; [ $filled -lt 0 ] && filled=0; case $filled in 0) bar=\"░░░░░░░░░░\";; 1) bar=\"█░░░░░░░░░\";; 2) bar=\"██░░░░░░░░\";; 3) bar=\"███░░░░░░░\";; 4) bar=\"████░░░░░░\";; 5) bar=\"█████░░░░░\";; 6) bar=\"██████░░░░\";; 7) bar=\"███████░░░\";; 8) bar=\"████████░░\";; 9) bar=\"█████████░\";; 10) bar=\"██████████\";; esac; if [ $pct -lt 50 ]; then bc=\"\\e[32m\"; elif [ $pct -lt 75 ]; then bc=\"\\e[33m\"; else bc=\"\\e[31m\"; fi; printf \" \\e[90m│\\e[0m ${bc}%s\\e[0m %d%%\" \"$bar\" \"$pct\"; [ -n \"$dur_str\" ] && printf \" \\e[90m│\\e[0m \\e[37m%s\\e[0m\" \"$dur_str\"" + "command": "input=$(cat); sid=$(echo \"$input\" | jq -r '.session_id // \"\"'); model=$(echo \"$input\" | jq -r '.model.display_name'); dir=$(echo \"$input\" | jq -r '.workspace.current_dir' | sed 's|.*/||'); tp=$(echo \"$input\" | jq -r '.transcript_path // \"\"'); dur_str=\"\"; if [ -n \"$tp\" ] && [ -f \"$tp\" ]; then t1=$(head -1 \"$tp\" | jq -r '.timestamp // empty' 2>/dev/null); t2=$(tail -1 \"$tp\" | jq -r '.timestamp // empty' 2>/dev/null); if [ -n \"$t1\" ] && [ -n \"$t2\" ]; then s1=$(date -d \"$t1\" +%s 2>/dev/null); s2=$(date -d \"$t2\" +%s 2>/dev/null); if [ -n \"$s1\" ] && [ -n \"$s2\" ]; then dur=$((s2 - s1)); h=$((dur / 3600)); m=$(( (dur % 3600) / 60 )); if [ $h -gt 0 ]; then dur_str=\"${h}h${m}m\"; elif [ $m -gt 0 ]; then dur_str=\"${m}m\"; else dur_str=\"<1m\"; fi; fi; fi; fi; pct=$(echo \"$input\" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1); git_info=\"\"; branch=$(git --no-optional-locks branch --show-current 2>/dev/null); if [ -n \"$branch\" ]; then git_info=\"$branch\"; ab=$(git --no-optional-locks rev-list --left-right --count @{u}...HEAD 2>/dev/null) && { behind=$(echo \"$ab\" | awk '{print $1}'); ahead=$(echo \"$ab\" | awk '{print $2}'); div=\"\"; [ \"${ahead:-0}\" -gt 0 ] && div=\"↑${ahead}\"; [ \"${behind:-0}\" -gt 0 ] && div=\"${div}↓${behind}\"; [ -n \"$div\" ] && git_info=\"${git_info} ${div}\"; }; status=$(git --no-optional-locks status --porcelain 2>/dev/null); if [ -n \"$status\" ]; then staged=$(echo \"$status\" | grep -c '^[MADRC]'); modified=$(echo \"$status\" | grep -c '^.[MD]'); untracked=$(echo \"$status\" | grep -c '^??'); counts=\"\"; [ \"${staged:-0}\" -gt 0 ] && counts=\"+${staged}\"; [ \"${modified:-0}\" -gt 0 ] && counts=\"${counts}${counts:+ }~${modified}\"; [ \"${untracked:-0}\" -gt 0 ] && counts=\"${counts}${counts:+ }?${untracked}\"; [ -n \"$counts\" ] && git_info=\"${git_info} ${counts}\"; fi; fi; [ -n \"$sid\" ] && printf \"\\e[90m%s\\e[0m \\e[90m│\\e[0m \" \"$sid\"; printf \"\\e[36m[\\e[0m\\e[37m%s\\e[0m\\e[36m]\\e[0m \\e[37m%s\\e[0m \\e[32min\\e[0m \\e[33;1m%s\\e[0m \\e[32mwith\\e[0m \\e[36m%s\\e[0m\" \"$(hostname -s)\" \"$(whoami)\" \"$dir\" \"$model\"; [ -n \"$git_info\" ] && printf \" \\e[90m│\\e[0m \\e[35m%s\\e[0m\" \"$git_info\"; filled=$((pct / 10)); [ $filled -gt 10 ] && filled=10; [ $filled -lt 0 ] && filled=0; case $filled in 0) bar=\"░░░░░░░░░░\";; 1) bar=\"█░░░░░░░░░\";; 2) bar=\"██░░░░░░░░\";; 3) bar=\"███░░░░░░░\";; 4) bar=\"████░░░░░░\";; 5) bar=\"█████░░░░░\";; 6) bar=\"██████░░░░\";; 7) bar=\"███████░░░\";; 8) bar=\"████████░░\";; 9) bar=\"█████████░\";; 10) bar=\"██████████\";; esac; if [ $pct -lt 50 ]; then bc=\"\\e[32m\"; elif [ $pct -lt 75 ]; then bc=\"\\e[33m\"; else bc=\"\\e[31m\"; fi; printf \" \\e[90m│\\e[0m ${bc}%s\\e[0m %d%%\" \"$bar\" \"$pct\"; [ -n \"$dur_str\" ] && printf \" \\e[90m│\\e[0m \\e[37m%s\\e[0m\" \"$dur_str\"; :" }, "enabledPlugins": { "claude-code-wakatime@wakatime": true, @@ -395,6 +379,26 @@ "frontend-design@claude-plugins-official": true, "mx@MaxWolf-01": true }, + "extraKnownMarketplaces": { + "wakatime": { + "source": { + "source": "git", + "url": "https://github.com/wakatime/claude-code-wakatime.git" + } + }, + "claude-plugins-official": { + "source": { + "source": "github", + "repo": "anthropics/claude-plugins-official" + } + }, + "MaxWolf-01": { + "source": { + "source": "github", + "repo": "MaxWolf-01/agents" + } + } + }, "alwaysThinkingEnabled": true, "autoUpdatesChannel": "latest", "additionalWorkingDirectories": [ @@ -403,5 +407,6 @@ "feedbackSurveyState": { "lastShownTime": 1754064350328 }, - "effortLevel": "high" + "effortLevel": "high", + "skipDangerousModePermissionPrompt": true } From 9db51da2aa4bdc9f0e585166a4f3a533b81b26c0 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 08:11:08 +0100 Subject: [PATCH 15/32] setup: decrypt SSH key from sops in secrets flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bootstraps ~/.ssh/id_ed25519 from the sops-encrypted copy in secrets/passwords/. Derives public key automatically. Enables self-bootstrapping restore: age key → setup secrets → SSH key → rsync.net. Co-Authored-By: Claude Opus 4.6 --- setup | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/setup b/setup index 262c028..54d5e05 100755 --- a/setup +++ b/setup @@ -266,6 +266,13 @@ secrets() { export SOPS_AGE_KEY_FILE="$key_plain" + if [ ! -f ~/.ssh/id_ed25519 ]; then + sops -d ~/.dotfiles/secrets/passwords/id_ed25519 > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub + echo "SSH key decrypted to ~/.ssh/id_ed25519" + fi + sops -d ~/.dotfiles/secrets/api_keys/wakatime.cfg > ~/.wakatime.cfg } From 24f67baa31f4fefc210b61b9a44007541082fd7d Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 08:20:27 +0100 Subject: [PATCH 16/32] setup: ssh-add key to agent after decrypt Without this, restore-working fails because the passphrase-protected key isn't loaded into the agent yet. Co-Authored-By: Claude Opus 4.6 --- setup | 1 + 1 file changed, 1 insertion(+) diff --git a/setup b/setup index 54d5e05..5a66b13 100755 --- a/setup +++ b/setup @@ -271,6 +271,7 @@ secrets() { chmod 600 ~/.ssh/id_ed25519 ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub echo "SSH key decrypted to ~/.ssh/id_ed25519" + ssh-add ~/.ssh/id_ed25519 fi sops -d ~/.dotfiles/secrets/api_keys/wakatime.cfg > ~/.wakatime.cfg From 1a2358d7c6947e3535abfae802467c4c6865cdf8 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 08:48:14 +0100 Subject: [PATCH 17/32] setup: btop build in /tmp, remove obsidian-skills clone btop source only needed during build, no reason to persist in ~/repos/tools. obsidian-skills now handled as a Claude plugin. Co-Authored-By: Claude Opus 4.6 --- setup | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/setup b/setup index 5a66b13..f42929a 100755 --- a/setup +++ b/setup @@ -96,7 +96,9 @@ minimal() { sudo systemctl restart nix-daemon # btop from source for GPU support (nixpkgs btop-cuda can't see system NVIDIA drivers) - cd "$HOME/repos/tools" && { if [ -d "btop" ]; then cd btop && git pull; else git clone --depth 1 https://github.com/aristocratos/btop.git && cd btop; fi; sudo make && sudo make install; } + git clone --depth 1 https://github.com/aristocratos/btop.git /tmp/btop + sudo make -C /tmp/btop install + rm -rf /tmp/btop } gpu() { @@ -311,16 +313,6 @@ obsidian_vault() { ln -sf ~/.dotfiles/git/hooks/quartz-sync-pre-push "$path/knowledge-base/.git/hooks/pre-push" echo "Pre-push hook for Quartz sync installed" - # Set up Claude Code obsidian skills - if [ ! -d ~/repos/tools/obsidian-skills ]; then - git clone --depth 1 https://github.com/kepano/obsidian-skills.git ~/repos/tools/obsidian-skills - fi - mkdir -p "$path/knowledge-base/.claude/skills" - for skill in ~/repos/tools/obsidian-skills/*/; do - skill_name=$(basename "$skill") - [ -f "$skill/SKILL.md" ] && ln -sf "$skill" "$path/knowledge-base/.claude/skills/$skill_name" - done - echo "Claude Code obsidian skills installed" fi } From 572263c6313998a05a4927db79bd81d2fc43b054 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 08:52:29 +0100 Subject: [PATCH 18/32] update claude --- .claude/settings.local.json | 11 +++++++++-- claude/settings.json | 26 +++++++++----------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 239c78b..6c785e5 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,8 +2,15 @@ "permissions": { "allow": [ "mcp__rime", - "mcp__plugin_context7_context7__get-library-docs", - "mcp__plugin_context7_context7__resolve-library-id" + "Bash(ssh pc \"ssh-add -l\" 2>&1)", + "Bash(ssh pc \"who -b\")", + "Bash(ssh pc \"systemctl --user list-timers --no-pager\" 2>&1)", + "Bash(tmux list-sessions 2>/dev/null)", + "Bash(ssh pc 'journalctl -b -u systemd-modules-load --no-pager' 2>&1)", + "Bash(ssh pc 'ip link show' 2>&1)", + "Bash(ssh pc 'cat /proc/cmdline' 2>&1)", + "Bash(ssh pc 'cat /var/log/boot-diag.log' 2>&1)", + "Bash(localectl status)" ] }, "enableAllProjectMcpServers": true, diff --git a/claude/settings.json b/claude/settings.json index 1956d40..b615ed4 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -16,7 +16,6 @@ "Edit(//tmp/**)", "WebSearch", "WebFetch", - "Bash(basename *)", "Bash(cat *)", "Bash(curl *)", @@ -93,17 +92,14 @@ "Bash(which *)", "Bash(whoami)", "Bash(whoami *)", - "Bash(grep *)", "Bash(rg *)", "Bash(sg *)", "Bash(ast-grep *)", "Bash(fastmod *)", - "Bash(nix run nixpkgs#fd *)", "Bash(nix search *)", "Bash(nix-env *)", - "Bash(coredumpctl list)", "Bash(coredumpctl list *)", "Bash(dpkg -l)", @@ -124,7 +120,6 @@ "Bash(systemctl --user list-timers *)", "Bash(tmux show-options)", "Bash(tmux show-options *)", - "Bash(uv run *)", "Bash(uvx *)", "Bash(ty)", @@ -134,23 +129,19 @@ "Bash(npx tsc)", "Bash(npx tsc *)", "Bash(npm view *)", - "Bash(codex --version)", "Bash(codex --version *)", "Bash(mx *)", - "Bash(gnome-extensions info *)", "Bash(gnome-extensions list)", "Bash(gnome-extensions list *)", "Bash(gsettings get *)", "Bash(gsettings list-recursively)", "Bash(gsettings list-recursively *)", - "Bash(claude config --help)", "Bash(claude mcp *)", "Bash(claude plugin marketplace list)", "Bash(claude plugin marketplace list *)", - "Bash(git status)", "Bash(git status *)", "Bash(git log)", @@ -201,7 +192,6 @@ "Bash(git fetch *)", "Bash(git worktree list)", "Bash(git worktree list *)", - "Bash(git -C * status)", "Bash(git -C * status *)", "Bash(git -C * log)", @@ -253,7 +243,6 @@ "Bash(git -C * worktree list)", "Bash(git -C * worktree list *)", "Bash(git -C * check-ignore *)", - "Bash(gh api -X GET *)", "Bash(gh auth status)", "Bash(gh auth status *)", @@ -299,7 +288,6 @@ "Bash(gh workflow list)", "Bash(gh workflow list *)", "Bash(gh workflow view *)", - "Bash(ssh * cat *)", "Bash(ssh * head *)", "Bash(ssh * tail *)", @@ -341,7 +329,6 @@ "Bash(ssh * ps *)", "Bash(ssh * id)", "Bash(ssh * id *)", - "Bash(ssh * docker ps)", "Bash(ssh * docker ps *)", "Bash(ssh * docker logs *)", @@ -358,9 +345,7 @@ "Bash(ssh * docker compose ps *)", "Bash(ssh * docker compose logs)", "Bash(ssh * docker compose logs *)", - "Bash(ssh-keyscan *)", - "Bash(docker ps)", "Bash(docker ps *)", "Bash(docker images)", @@ -378,7 +363,6 @@ "Bash(docker compose ps *)", "Bash(docker compose logs)", "Bash(docker compose logs *)", - "Bash(nix flake check)", "Bash(nix flake check *)", "Bash(sleep *)" @@ -403,5 +387,13 @@ "feedbackSurveyState": { "lastShownTime": 1754064350328 }, - "effortLevel": "high" + "effortLevel": "high", + "extraKnownMarketplaces": { + "obsidian-skills": { + "source": { + "source": "github", + "repo": "kepano/obsidian-skills" + } + } + } } From 96cbaeafabc075aeffa53476f8dcf317c6fdc022 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 08:53:04 +0100 Subject: [PATCH 19/32] gnome: consolidate duplicate power dconf block Co-Authored-By: Claude Opus 4.6 --- nix/home/gnome.nix | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/nix/home/gnome.nix b/nix/home/gnome.nix index b761ad8..ff6511b 100644 --- a/nix/home/gnome.nix +++ b/nix/home/gnome.nix @@ -56,6 +56,7 @@ sleep-inactive-ac-timeout = lib.hm.gvariant.mkUint32 0; sleep-inactive-battery-timeout = lib.hm.gvariant.mkUint32 3600; idle-dim = false; + power-button-action = "interactive"; }; # Window management keybindings @@ -109,10 +110,6 @@ binding = "n"; }; - "org/gnome/settings-daemon/plugins/power" = { - power-button-action = "interactive"; - }; - "org/freedesktop/ibus/panel/emoji" = { hotkey = [ "" ]; unicode-hotkey = [ "" ]; From a2ce807867c7e7d52af35aefa337a9a8cf1de182 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 09:00:41 +0100 Subject: [PATCH 20/32] =?UTF-8?q?docs:=20reorder=20setup=20=E2=80=94=20res?= =?UTF-8?q?tore=20before=20get=5Fclaude?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoids .claude/ conflicts from restore overwriting files claude just created. Co-Authored-By: Claude Opus 4.6 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a73c981..58bad9b 100644 --- a/README.md +++ b/README.md @@ -66,10 +66,10 @@ Restart shell, then: ./setup ubuntu # NVIDIA drivers, codecs, cleanup sudo reboot ./setup gpu # nix GPU driver symlinks -./setup get_claude gh auth login -w -./setup secrets # clones private secrets repo, decrypts +./setup secrets # clones secrets repo, decrypts SSH key + secrets restore-working # restore data from rsync.net backup +./setup get_claude # after restore to avoid .claude/ conflicts ``` After first run, use `hmswitch` to apply HM changes. From dc2dc2f57c8ca1cdc220228ee4a4328a01a39580 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 09:37:13 +0100 Subject: [PATCH 21/32] setup: fix fresh-install issues, cleanup - Move oyo install + loginctl enable-linger to ubuntu() - Move chsh to host() after HM switch (nix zsh not available earlier) - Remove apt zsh (nix provides it) - Fix ubuntu-install: define colors/die before first use Co-Authored-By: Claude Opus 4.6 --- bin/ubuntu-install | 24 ++++++++++++------------ setup | 14 ++++++++------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/bin/ubuntu-install b/bin/ubuntu-install index 1d3515c..596b644 100755 --- a/bin/ubuntu-install +++ b/bin/ubuntu-install @@ -29,6 +29,18 @@ HOSTNAME="" ROOT_SIZE="" # Parse arguments +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BOLD='\033[1m' +NC='\033[0m' + +info() { echo -e "${GREEN}>>>${NC} $*"; } +warn() { echo -e "${YELLOW}⚠${NC} $*"; } +error() { echo -e "${RED}✗${NC} $*" >&2; } +die() { error "$@"; exit 1; } + POSITIONAL=() while [[ $# -gt 0 ]]; do case $1 in @@ -43,18 +55,6 @@ if [[ -z "$ROOT_SIZE" ]]; then ROOT_SIZE="${root_gb}G" fi -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BOLD='\033[1m' -NC='\033[0m' - -info() { echo -e "${GREEN}>>>${NC} $*"; } -warn() { echo -e "${YELLOW}⚠${NC} $*"; } -error() { echo -e "${RED}✗${NC} $*" >&2; } -die() { error "$@"; exit 1; } - confirm() { local prompt="$1" local response diff --git a/setup b/setup index f42929a..f4b1901 100755 --- a/setup +++ b/setup @@ -27,6 +27,7 @@ host() { if command -v nix &> /dev/null; then touch ~/.zshrc nix run home-manager/master -- switch --flake ~/.dotfiles#"$NIX_HOST" + [ ! -f /etc/NIXOS ] && sudo chsh -s "$HOME/.nix-profile/bin/zsh" "$USER" else echo "Nix not installed. Run './setup minimal' first, then re-run './setup host $name'" fi @@ -60,9 +61,6 @@ minimal() { link ruff ~/.config/ruff link oyo/config.toml ~/.config/oyo/config.toml - # https://github.com/ahkohd/oyo - cargo install oyo - if [ -f /etc/NIXOS ]; then echo "Done (NixOS). Run nswitch to apply." return @@ -74,9 +72,7 @@ minimal() { sudo apt-get update sudo apt-get install -y \ build-essential \ - gcc \ - zsh - sudo chsh -s "$(which zsh)" "$USER" + gcc sudo mkdir -p /usr/local/bin sudo ln -sf ~/bin/* /usr/local/bin/ @@ -147,6 +143,12 @@ ubuntu() { echo "Moved /usr/lib/firefox → /usr/lib/firefox.bak (HM Firefox takes priority)" fi + # https://github.com/ahkohd/oyo + cargo install oyo + + # Keep systemd user timers alive after logout (backups, etc.) + loginctl enable-linger "$USER" + echo "Run './setup gpu' after reboot to set up Nix GPU driver symlinks." } From 953b810b6d7782a591e6b9df8eed78fbf71165c4 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 09:40:10 +0100 Subject: [PATCH 22/32] cleanup --- setup | 6 ------ 1 file changed, 6 deletions(-) diff --git a/setup b/setup index f4b1901..e0db06c 100755 --- a/setup +++ b/setup @@ -37,7 +37,6 @@ minimal() { # --- Universal (all systems) --- mkdir -p ~/.ssh mkdir -p ~/bin - mkdir -p ~/repos/tools mkdir -p ~/tmp mkdir -p ~/logs touch ~/.local_exports @@ -327,11 +326,6 @@ sshkeys() { echo "Put your public key on github -> settings -> SSH and GPG keys" } -openconnect() { - sudo apt-get update && sudo apt-get install network-manager-openconnect network-manager-openconnect-gnome - sudo systemctl restart NetworkManager -} - nvidia_mps() { sudo ln -sf "$DOTFILES/systemd/nvidia-mps.service" /etc/systemd/system/ sudo systemctl daemon-reload From c724f073404486b9debc83e0f05b6a51a1dacd17 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Sun, 8 Mar 2026 23:26:03 +0100 Subject: [PATCH 23/32] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 58bad9b..74d9f08 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,9 @@ dotfiles Boot a live USB, then: ```bash -sudo apt-get install -y debootstrap gdisk +sudo apt-get install -y debootstrap gdisk curl curl -sLO https://raw.githubusercontent.com/MaxWolf-01/dotfiles/master/bin/ubuntu-install -chmod +x ubuntu-install && sudo ./ubuntu-install /dev/nvmeXnY --root 200 +chmod +x ubuntu-install && sudo ./ubuntu-install ``` Reboot, remove USB. From 6eaaf50b78197c3125b7fac2dd4f87d0dfa1f4f6 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Mon, 9 Mar 2026 01:15:46 +0100 Subject: [PATCH 24/32] docs: add pre-release ISO fallback + CLI pastebin to README Co-Authored-By: Claude Opus 4.6 --- README.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 74d9f08..d60cdf6 100644 --- a/README.md +++ b/README.md @@ -44,11 +44,32 @@ dotfiles Boot a live USB, then: ```bash -sudo apt-get install -y debootstrap gdisk curl +sudo apt-get install -y curl debootstrap gdisk curl -sLO https://raw.githubusercontent.com/MaxWolf-01/dotfiles/master/bin/ubuntu-install -chmod +x ubuntu-install && sudo ./ubuntu-install +sudo bash ubuntu-install ``` +
+If apt doesn't work (pre-release ISO) + +```bash +curl -Lo /tmp/debootstrap.deb http://archive.ubuntu.com/ubuntu/pool/main/d/debootstrap/debootstrap_1.0.142ubuntu1_all.deb +curl -Lo /tmp/gdisk.deb http://archive.ubuntu.com/ubuntu/pool/main/g/gdisk/gdisk_1.0.10-2build1_amd64.deb +sudo dpkg -i /tmp/debootstrap.deb /tmp/gdisk.deb +curl -sLO https://raw.githubusercontent.com/MaxWolf-01/dotfiles/master/bin/ubuntu-install +sudo bash ubuntu-install +``` +
+ +
+CLI pastebin (send text between machines without login/git) + +```bash +echo "commands here" | curl --data-binary @- https://paste.rs +cat script.sh | curl --data-binary @- https://paste.rs +``` +
+ Reboot, remove USB. ### Post-install From 9400cc27502dd76c072b07276fd1d8b754e3de30 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:35:44 +0100 Subject: [PATCH 25/32] setup: merge minimal into host, fix fresh-install issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Absorb minimal() into host() — one command for full setup - Add curl to apt deps (debootstrap installs don't include it) - Move btop build from host() to ubuntu() - Use disk-based bootloader-id (ubuntu-nvme1n1) to avoid EFI collisions - Update README for single-command flow Co-Authored-By: Claude Opus 4.6 --- README.md | 7 +++--- bin/ubuntu-install | 4 ++-- setup | 57 ++++++++++++++++++++++------------------------ 3 files changed, 32 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index d60cdf6..d206bda 100644 --- a/README.md +++ b/README.md @@ -77,14 +77,13 @@ Reboot, remove USB. ```bash sudo apt-get update && sudo apt-get install -y git git clone https://github.com/MaxWolf-01/dotfiles.git ~/.dotfiles -cd ~/.dotfiles && ./setup minimal +cd ~/.dotfiles && ./setup host zephyrus # dirs, symlinks, nix, HM switch ``` -Restart shell, then: +Restart shell (nix needs it on first install), then re-run `./setup host zephyrus`. ```bash -./setup host zephyrus # sets NIX_HOST, runs first HM switch -./setup ubuntu # NVIDIA drivers, codecs, cleanup +./setup ubuntu # NVIDIA drivers, codecs, btop, cleanup sudo reboot ./setup gpu # nix GPU driver symlinks gh auth login -w diff --git a/bin/ubuntu-install b/bin/ubuntu-install index 596b644..291466b 100755 --- a/bin/ubuntu-install +++ b/bin/ubuntu-install @@ -266,7 +266,7 @@ configure_user() { install_bootloader() { info "Installing GRUB and rebuilding initramfs" chroot /mnt update-initramfs -u -k all - chroot /mnt grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu + chroot /mnt grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id="ubuntu-$(basename "$DISK")" chroot /mnt update-grub } @@ -325,4 +325,4 @@ vgchange -an vg0 echo info "Done! Reboot and remove the USB drive." -info "After boot, run: ~/.dotfiles/setup minimal && ~/.dotfiles/setup host $HOSTNAME" +info "After boot: git clone dotfiles, then ./setup host $HOSTNAME" diff --git a/setup b/setup index e0db06c..ffb6222 100755 --- a/setup +++ b/setup @@ -16,32 +16,11 @@ host() { echo "Available: $(ls "$DOTFILES/nix/home/hosts/" | sed 's/\.nix$//' | tr '\n' ' ')" return 1 fi - if grep -q 'NIX_HOST=' ~/.local_exports 2>/dev/null; then - sed -i "s/export NIX_HOST=.*/export NIX_HOST=\"$name\"/" ~/.local_exports - else - echo "export NIX_HOST=\"$name\"" >> ~/.local_exports - fi - export NIX_HOST="$name" - echo "NIX_HOST=$name" - if command -v nix &> /dev/null; then - touch ~/.zshrc - nix run home-manager/master -- switch --flake ~/.dotfiles#"$NIX_HOST" - [ ! -f /etc/NIXOS ] && sudo chsh -s "$HOME/.nix-profile/bin/zsh" "$USER" - else - echo "Nix not installed. Run './setup minimal' first, then re-run './setup host $name'" - fi -} - -minimal() { - # --- Universal (all systems) --- - mkdir -p ~/.ssh - mkdir -p ~/bin - mkdir -p ~/tmp - mkdir -p ~/logs + # --- Dirs and symlinks --- + mkdir -p ~/.ssh ~/bin ~/tmp ~/logs touch ~/.local_exports - # Clean dead symlinks find ~ -maxdepth 1 -type l ! -exec test -e {} \; -delete 2>/dev/null || true find ~/.ssh -maxdepth 1 -type l ! -exec test -e {} \; -delete 2>/dev/null || true @@ -60,17 +39,29 @@ minimal() { link ruff ~/.config/ruff link oyo/config.toml ~/.config/oyo/config.toml + # --- Set NIX_HOST --- + if grep -q 'NIX_HOST=' ~/.local_exports 2>/dev/null; then + sed -i "s/export NIX_HOST=.*/export NIX_HOST=\"$name\"/" ~/.local_exports + else + echo "export NIX_HOST=\"$name\"" >> ~/.local_exports + fi + export NIX_HOST="$name" + echo "NIX_HOST=$name" + + # --- NixOS: done here, use nswitch --- if [ -f /etc/NIXOS ]; then echo "Done (NixOS). Run nswitch to apply." return fi + # --- Non-NixOS: install Nix + first HM switch --- mkdir -p ~/applications link nix/nix.conf ~/.config/nix/nix.conf - export NONINTERACTIVE=1 + sudo apt-get update sudo apt-get install -y \ build-essential \ + curl \ gcc sudo mkdir -p /usr/local/bin @@ -78,8 +69,8 @@ minimal() { if ! command -v nix &> /dev/null; then sh <(curl -L https://nixos.org/nix/install) --daemon --yes - echo "Nix installed. Restart shell, set NIX_HOST in ~/.local_exports, then:" - echo " nix run home-manager/master -- switch --flake ~/.dotfiles#\$NIX_HOST" + echo "Nix installed. Restart your shell, then re-run: ./setup host $name" + return fi if ! grep -q 'min-free' /etc/nix/nix.conf 2>/dev/null; then @@ -90,10 +81,9 @@ minimal() { fi sudo systemctl restart nix-daemon - # btop from source for GPU support (nixpkgs btop-cuda can't see system NVIDIA drivers) - git clone --depth 1 https://github.com/aristocratos/btop.git /tmp/btop - sudo make -C /tmp/btop install - rm -rf /tmp/btop + touch ~/.zshrc + nix run home-manager/master -- switch --flake ~/.dotfiles#"$NIX_HOST" + [ ! -f /etc/NIXOS ] && sudo chsh -s "$HOME/.nix-profile/bin/zsh" "$USER" } gpu() { @@ -142,6 +132,13 @@ ubuntu() { echo "Moved /usr/lib/firefox → /usr/lib/firefox.bak (HM Firefox takes priority)" fi + # btop from source for GPU support (nixpkgs btop-cuda can't see system NVIDIA drivers) + if ! command -v btop &> /dev/null; then + git clone --depth 1 https://github.com/aristocratos/btop.git /tmp/btop + sudo make -C /tmp/btop install + rm -rf /tmp/btop + fi + # https://github.com/ahkohd/oyo cargo install oyo From 4bf3f951c037a3d93cc7a4a5279c01b962afb427 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Mon, 9 Mar 2026 17:06:15 +0100 Subject: [PATCH 26/32] setup: add nix zsh to /etc/shells before chsh Co-Authored-By: Claude Opus 4.6 --- setup | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup b/setup index ffb6222..afb7603 100755 --- a/setup +++ b/setup @@ -83,7 +83,10 @@ host() { touch ~/.zshrc nix run home-manager/master -- switch --flake ~/.dotfiles#"$NIX_HOST" - [ ! -f /etc/NIXOS ] && sudo chsh -s "$HOME/.nix-profile/bin/zsh" "$USER" + if [ ! -f /etc/NIXOS ]; then + grep -qxF "$HOME/.nix-profile/bin/zsh" /etc/shells || echo "$HOME/.nix-profile/bin/zsh" | sudo tee -a /etc/shells + sudo chsh -s "$HOME/.nix-profile/bin/zsh" "$USER" + fi } gpu() { From 1a17cf8b4172f141217d6d59aae5f0847e57d3a2 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Mon, 9 Mar 2026 17:30:10 +0100 Subject: [PATCH 27/32] setup: fix btop build (compile before install) Co-Authored-By: Claude Opus 4.6 --- setup | 1 + 1 file changed, 1 insertion(+) diff --git a/setup b/setup index afb7603..2f243f2 100755 --- a/setup +++ b/setup @@ -138,6 +138,7 @@ ubuntu() { # btop from source for GPU support (nixpkgs btop-cuda can't see system NVIDIA drivers) if ! command -v btop &> /dev/null; then git clone --depth 1 https://github.com/aristocratos/btop.git /tmp/btop + make -C /tmp/btop -j"$(nproc)" sudo make -C /tmp/btop install rm -rf /tmp/btop fi From 384b1794c83dbfe7cb84d753585c9fd0cfd342ae Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Mon, 9 Mar 2026 17:35:44 +0100 Subject: [PATCH 28/32] nix: update stateVersion to 26.05 for fresh installs Co-Authored-By: Claude Opus 4.6 --- nix/home/hosts/xmg19.nix | 2 +- nix/home/hosts/zephyrus.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/home/hosts/xmg19.nix b/nix/home/hosts/xmg19.nix index 112a737..f52a5d6 100644 --- a/nix/home/hosts/xmg19.nix +++ b/nix/home/hosts/xmg19.nix @@ -2,7 +2,7 @@ { home.username = "max"; home.homeDirectory = "/home/max"; - home.stateVersion = "24.05"; + home.stateVersion = "26.05"; imports = [ ../desktop.nix diff --git a/nix/home/hosts/zephyrus.nix b/nix/home/hosts/zephyrus.nix index 5d83e4e..08047cd 100644 --- a/nix/home/hosts/zephyrus.nix +++ b/nix/home/hosts/zephyrus.nix @@ -2,7 +2,7 @@ { home.username = "max"; home.homeDirectory = "/home/max"; - home.stateVersion = "24.05"; + home.stateVersion = "26.05"; imports = [ ../desktop.nix From d656323597a2a4ac2e0636908d174eed2879a6e8 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Mon, 9 Mar 2026 19:33:01 +0100 Subject: [PATCH 29/32] setup: use system compiler for btop (fix GPU detection) Nix's c++ produces binaries with Nix's ld-linux, which can't dlopen system libs like libnvidia-ml.so at runtime. Co-Authored-By: Claude Opus 4.6 --- setup | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup b/setup index 2f243f2..b52d6ec 100755 --- a/setup +++ b/setup @@ -136,9 +136,11 @@ ubuntu() { fi # btop from source for GPU support (nixpkgs btop-cuda can't see system NVIDIA drivers) + # Must use system compiler — Nix's c++ produces binaries with Nix's ld-linux, + # which can't dlopen system libs like libnvidia-ml.so at runtime. if ! command -v btop &> /dev/null; then git clone --depth 1 https://github.com/aristocratos/btop.git /tmp/btop - make -C /tmp/btop -j"$(nproc)" + make -C /tmp/btop CXX=/usr/bin/g++ -j"$(nproc)" sudo make -C /tmp/btop install rm -rf /tmp/btop fi From bca0cbd3945bd6a3d9ece99acd1e2853e2429b27 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:39:44 +0100 Subject: [PATCH 30/32] firefox: enable WebGPU (not yet default on Linux) Co-Authored-By: Claude Opus 4.6 --- nix/home/firefox.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nix/home/firefox.nix b/nix/home/firefox.nix index 78f7564..a6c5f56 100644 --- a/nix/home/firefox.nix +++ b/nix/home/firefox.nix @@ -116,7 +116,9 @@ in "browser.translations.automaticallyPopup" = false; "browser.translations.neverTranslateLanguages" = "de"; - # Wayland / performance + # GPU / performance + "dom.webgpu.enabled" = true; + "gfx.webgpu.ignore-blocklist" = true; "media.ffmpeg.vaapi.enabled" = true; "widget.use-xdg-desktop-portal.file-picker" = 1; "media.eme.enabled" = true; From 8f7af60681b9b61e3f6d5a2938a5926bab6c081c Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Tue, 10 Mar 2026 21:19:07 +0100 Subject: [PATCH 31/32] disable built-in Firefox password manager via policy Co-Authored-By: Claude Opus 4.6 --- nix/home/firefox.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/home/firefox.nix b/nix/home/firefox.nix index a6c5f56..2322e40 100644 --- a/nix/home/firefox.nix +++ b/nix/home/firefox.nix @@ -41,6 +41,8 @@ in EmailTracking = true; }; + PasswordManagerEnabled = false; + GenerativeAI = { Chatbot = false; LinkPreviews = false; From 6637149dbbdc706e78f8f3a0c39b0b77644df834 Mon Sep 17 00:00:00 2001 From: Maximilian Wolf <69987866+MaxWolf-01@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:28:02 +0100 Subject: [PATCH 32/32] restore-working: add ownership restore, sparse files, and post-restore verification - Run with --restore-owner-by-name for correct ownership across reinstalls - Add --sparse for zero-filled file optimization (free, no downside) - Warn if not running as root (ownership silently lost otherwise) - Add verification pass: dry-run with --overwrite if-changed to detect mismatches - Avoid redundant restic snapshots --json call when resolving latest Co-Authored-By: Claude Opus 4.6 --- bin/restore-working | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/bin/restore-working b/bin/restore-working index b3752c5..eed33ee 100755 --- a/bin/restore-working +++ b/bin/restore-working @@ -14,6 +14,7 @@ set -euo pipefail BACKUP_DIR="$HOME/.dotfiles/secrets/backup/restic/working" info() { echo -e "\033[0;32m>>>\033[0m $*"; } +warn() { echo -e "\033[0;33m!!!\033[0m $*"; } die() { echo -e "\033[0;31mx\033[0m $*" >&2; exit 1; } source "$BACKUP_DIR/_common" @@ -35,19 +36,44 @@ done command -v restic &>/dev/null || die "restic not found" if [[ "$SNAPSHOT" == "latest" ]]; then - SNAPSHOT_ID=$(restic snapshots --json | jq -r '.[-1].id') + snapshot_json=$(restic snapshots --json) + SNAPSHOT_ID=$(echo "$snapshot_json" | jq -r '.[-1].id') [[ -n "$SNAPSHOT_ID" && "$SNAPSHOT_ID" != "null" ]] || die "No snapshots found" - SNAPSHOT_DATE=$(restic snapshots --json | jq -r '.[-1].time[:19]') + SNAPSHOT_DATE=$(echo "$snapshot_json" | jq -r '.[-1].time[:19]') info "Latest snapshot: ${SNAPSHOT_ID:0:8} ($SNAPSHOT_DATE)" else SNAPSHOT_ID="$SNAPSHOT" info "Using snapshot: ${SNAPSHOT_ID:0:8}" fi +if [[ $EUID -ne 0 ]]; then + warn "Not running as root — file ownership will not be restored." + warn "Run with sudo to preserve ownership." +fi + read -rp "Restore snapshot ${SNAPSHOT_ID:0:8} to $HOME? [y/N] " response [[ "$response" =~ ^[Yy]$ ]] || exit 0 info "Restoring..." -restic restore "$SNAPSHOT_ID" --target / --overwrite always +restic restore "$SNAPSHOT_ID" \ + --target / \ + --sparse \ + --overwrite always \ + --restore-owner-by-name + +info "Verifying restore (dry-run diff against disk)..." +verify_log=/tmp/restore-verify.log +restic restore "$SNAPSHOT_ID" \ + --target / \ + --overwrite if-changed \ + --dry-run \ + --verbose=2 > "$verify_log" 2>&1 + +updated=$(grep -c '^updated' "$verify_log" 2>/dev/null || true) +if [[ "$updated" -eq 0 ]]; then + info "Verification passed — all files match snapshot." +else + warn "$updated files differ from snapshot. Check $verify_log" +fi info "Done."