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.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..d206bda 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) @@ -38,39 +39,70 @@ 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 curl debootstrap gdisk +curl -sLO https://raw.githubusercontent.com/MaxWolf-01/dotfiles/master/bin/ubuntu-install +sudo bash ubuntu-install ``` -Restart shell, then set host and run Home Manager: +
+If apt doesn't work (pre-release ISO) ```bash -./setup host zephyrus -nix run home-manager/master -- switch --flake ~/.dotfiles#$NIX_HOST -gh auth login -w +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 + +```bash +sudo apt-get update && sudo apt-get install -y git +git clone https://github.com/MaxWolf-01/dotfiles.git ~/.dotfiles +cd ~/.dotfiles && ./setup host zephyrus # dirs, symlinks, nix, HM switch ``` -After first run, use `hmswitch` to apply changes. +Restart shell (nix needs it on first install), then re-run `./setup host zephyrus`. -Place your age key at `~/.local/secrets/age-key.txt` (copy from another machine or backup), then: ```bash -./setup secrets -./setup ubuntu -./setup get_claude +./setup ubuntu # NVIDIA drivers, codecs, btop, cleanup +sudo reboot +./setup gpu # nix GPU driver symlinks +gh auth login -w +./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. + All `./setup` functions are idempotent — safe to re-run.
-Other common setup functions for the daily driver +Other setup functions ```bash ./setup docker ./setup nvidia_container_toolkit ./setup get_vibetyper -./setup tiling_shell ```
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/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..eed33ee --- /dev/null +++ b/bin/restore-working @@ -0,0 +1,79 @@ +#!/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 $*"; } +warn() { echo -e "\033[0;33m!!!\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_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=$(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 / \ + --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." diff --git a/bin/ubuntu-install b/bin/ubuntu-install new file mode 100755 index 0000000..291466b --- /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 +# 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 + --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 + +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-$(basename "$DISK")" + 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: git clone dotfiles, then ./setup host $HOSTNAME" diff --git a/claude/settings.json b/claude/settings.json index 1956d40..7f1a46c 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,32 @@ "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" + } + }, + "obsidian-skills": { + "source": { + "source": "github", + "repo": "kepano/obsidian-skills" + } + } + }, "alwaysThinkingEnabled": true, "autoUpdatesChannel": "latest", "additionalWorkingDirectories": [ @@ -403,5 +413,6 @@ "feedbackSurveyState": { "lastShownTime": 1754064350328 }, - "effortLevel": "high" + "effortLevel": "high", + "skipDangerousModePermissionPrompt": true } 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/firejail/firefox.local b/firejail/firefox.local new file mode 100644 index 0000000..c6ee47b --- /dev/null +++ b/firejail/firefox.local @@ -0,0 +1,13 @@ +# 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 + +# 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/common.nix b/nix/home/common.nix index f0528a9..648fab2 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; @@ -249,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" ]; - }; } diff --git a/nix/home/desktop.nix b/nix/home/desktop.nix index 21024ef..e62d671 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 = [ ./firefox.nix ./ghostty.nix ./newsboat.nix ]; home.file.".icons".source = ../../desktop/icons; @@ -15,17 +14,17 @@ 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 ]; + xdg.configFile."xdg-terminals.list".text = "com.mitchellh.ghostty.desktop\n"; + xdg.desktopEntries.vesktop = { name = "Vesktop"; genericName = "Discord Client"; @@ -35,4 +34,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/firefox.nix b/nix/home/firefox.nix new file mode 100644 index 0000000..2322e40 --- /dev/null +++ b/nix/home/firefox.nix @@ -0,0 +1,130 @@ +{ 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 = "/usr/bin/firejail ${firefoxBin} %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; + + policies = { + DisableTelemetry = true; + DisableFirefoxStudies = true; + DisableRemoteImprovements = true; + DontCheckDefaultBrowser = true; + NoDefaultBookmarks = true; + OverrideFirstRunPage = ""; + OverridePostUpdatePage = ""; + + EnableTrackingProtection = { + Value = true; + Cryptomining = true; + Fingerprinting = true; + EmailTracking = true; + }; + + PasswordManagerEnabled = false; + + 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"; + + # 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; + }; + }; + }; +} diff --git a/nix/home/ghostty.nix b/nix/home/ghostty.nix index b546abd..47e0b6a 100644 --- a/nix/home/ghostty.nix +++ b/nix/home/ghostty.nix @@ -8,6 +8,19 @@ 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" + "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" + ]; }; }; diff --git a/nix/home/gnome.nix b/nix/home/gnome.nix index 5a28ef3..ff6511b 100644 --- a/nix/home/gnome.nix +++ b/nix/home/gnome.nix @@ -1,16 +1,33 @@ -{ 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/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" @@ -18,15 +35,118 @@ ]; }; - # Screen timeout: 1 hour before blanking + "org/gnome/desktop/default-applications/terminal" = { + exec = "ghostty"; + }; + + "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 = lib.hm.gvariant.mkUint32 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 = 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; + power-button-action = "interactive"; + }; + + # 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/freedesktop/ibus/panel/emoji" = { + hotkey = [ "" ]; + unicode-hotkey = [ "" ]; + load-emoji-at-startup = false; + }; + + # 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/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 d8bcb7d..08047cd 100644 --- a/nix/home/hosts/zephyrus.nix +++ b/nix/home/hosts/zephyrus.nix @@ -2,12 +2,12 @@ { home.username = "max"; home.homeDirectory = "/home/max"; - home.stateVersion = "24.05"; + home.stateVersion = "26.05"; imports = [ ../desktop.nix ../gnome.nix ../timers.nix - ../x11.nix + ../wayland.nix ]; } diff --git a/nix/home/tmux.nix b/nix/home/tmux.nix index b4008ff..151008d 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 @@ -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" 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 c6983d5..b52d6ec 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" - 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 ~/repos/tools - 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,31 +39,38 @@ minimal() { link ruff ~/.config/ruff link oyo/config.toml ~/.config/oyo/config.toml - # https://github.com/ahkohd/oyo - cargo install oyo + # --- 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 \ - gcc \ - zsh - sudo chsh -s "$(which zsh)" "$USER" + curl \ + gcc sudo mkdir -p /usr/local/bin sudo ln -sf ~/bin/* /usr/local/bin/ 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 @@ -95,8 +81,12 @@ minimal() { fi 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; } + touch ~/.zshrc + nix run home-manager/master -- switch --flake ~/.dotfiles#"$NIX_HOST" + 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() { @@ -110,73 +100,58 @@ gpu() { ubuntu() { sudo apt-get update + + # System-level packages not available via Nix/HM sudo apt-get install -y \ + firejail \ 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 + # Remove bloat (brltty grabs USB serial devices) + sudo apt-get remove -y orca brltty - obsidian - obsidian_vault desktop shallow + # 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 - # 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 + # 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 CXX=/usr/bin/g++ -j"$(nproc)" + sudo make -C /tmp/btop install + rm -rf /tmp/btop + 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." } # todo https://github.com/nix-community/nix-on-droid-app @@ -255,9 +230,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 @@ -274,29 +248,39 @@ 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 - # find ~/.dotfiles/secrets/bin -type f -exec ln -sf {} ~/bin/ \; -} + 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" + ssh-add ~/.ssh/id_ed25519 + fi -vim_copilot() { - git clone --depth 1 https://github.com/github/copilot.vim.git ~/.vim/pack/github/start/copilot.vim + sops -d ~/.dotfiles/secrets/api_keys/wakatime.cfg > ~/.wakatime.cfg } obsidian_vault() { @@ -333,78 +317,10 @@ 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 } -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 {} \; @@ -413,29 +329,12 @@ 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 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 &)