diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..aea534b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,59 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +NixOS dotfiles using Flakes + Home Manager for a single user (`jashton`) across multiple hosts (`thinkpad`, `desktop`, `framework`). Pinned to `nixos-26.05`. + +## Common Commands + +```bash +# Rebuild and switch the current system (thinkpad) +sudo nixos-rebuild switch --flake ~/.dotfiles#thinkpad + +# Check the flake for errors without applying +nix flake check + +# Update flake inputs (e.g. nixpkgs, home-manager) +nix flake update + +# Open the flake entrypoint in nvim +enix # alias: nvim ~/.dotfiles/flake.nix + +# Open the home-manager user config in nvim +ehome # alias: nvim ~/.dotfiles/home/jashton.nix + +# List registered ports +ports # alias: cat ~/.dotfiles/ports.registry +``` + +## Architecture + +The repo is structured as a standard Nix Flakes configuration. `flake.nix` defines `nixosConfigurations` for each host and wires in Home Manager as a NixOS module. Each host's Home Manager is pointed at a single shared user config (`home/jashton.nix`). + +``` +flake.nix # Entrypoint; defines per-host nixosConfigurations +hosts//default.nix # Host-specific config (imports hardware.nix + system/) +hosts//hardware.nix # nixos-generate-config output (don't manually edit) +system/ # Shared NixOS system config (all hosts) + default.nix # Aggregates: packages, core, network, security, services, virtualisation + packages.nix # System-level packages + allowUnfree + core/ # Zsh, fonts, nix settings, locale, boot, audio, power + security/ # Users, sudo, polkit, TPM2, systemd hardening + services/ # Display manager (GDM), Xserver, i3 WM, picom, printing, Bluetooth + network/ # Pi-hole config + virtualisation/ # VM/container support +home/jashton.nix # Home Manager user config; shared across all hosts + home/programs/ # Per-program HM config: alacritty, git, nvim + home/desktop/ # Desktop environment: i3, polybar, rofi, dunst, gnome dconf +``` + +## Key Design Decisions + +- **Dual desktop environment**: Both GNOME (GDM display manager) and i3 (window manager via Xserver) are configured and co-exist. GNOME extensions are declared via `dconf.settings` in `home/desktop/gnome.nix`. +- **Shell scripts as Nix packages**: Custom scripts (`find-open-port`, `lock-screen`, `power-menu`, `weather-cache`) are defined inline in `home/jashton.nix` using `pkgs.writeShellScriptBin` so they are on `$PATH` without a separate scripts directory. +- **Port registry**: `ports.registry` is a plain-text file tracking assigned dev ports 8000–8999. The `find-open-port` script appends to it; don't assign ports manually without checking it first. +- **Zsh configured at system level**: `programs.zsh` (aliases, Oh My Zsh, history) lives in `system/core/env.nix`. The `~/.zshrc` written by Home Manager is intentionally a stub to suppress the zsh-newuser-install prompt. +- **Host hardware files**: `hosts/thinkpad/hardware.nix` is auto-generated by `nixos-generate-config`. Edits here (e.g. Howdy IR camera path `/dev/video66`) are intentional overrides — don't regenerate and overwrite. +- **`stateVersion`**: Both `system.stateVersion` and `home.stateVersion` are set to `"26.05"`. Do not change these. diff --git a/home/desktop/default.nix b/home/desktop/default.nix index 9fe99b6..9a9e383 100644 --- a/home/desktop/default.nix +++ b/home/desktop/default.nix @@ -3,6 +3,7 @@ { imports = [ ./dunst.nix + ./gnome.nix ./i3.nix ./polybar.nix ./rofi.nix diff --git a/home/desktop/gnome.nix b/home/desktop/gnome.nix new file mode 100644 index 0000000..96726b5 --- /dev/null +++ b/home/desktop/gnome.nix @@ -0,0 +1,29 @@ +{ config, lib, pkgs, ... }: + +{ + dconf.settings = { + "org/gnome/desktop/interface" = { + color-scheme = "prefer-dark"; + accent-color = "purple"; + }; + + "org/gnome/shell" = { + enabled-extensions = [ + pkgs.gnomeExtensions.blur-my-shell.extensionUuid + pkgs.gnomeExtensions.caffeine.extensionUuid + pkgs.gnomeExtensions.clipboard-indicator.extensionUuid + pkgs.gnomeExtensions.dash-to-panel.extensionUuid + pkgs.gnomeExtensions.gjs-osk.extensionUuid + pkgs.gnomeExtensions.gsconnect.extensionUuid + pkgs.gnomeExtensions.just-perfection.extensionUuid + pkgs.gnomeExtensions.removable-drive-menu.extensionUuid + ]; + }; + }; + + qt = { + enable = true; + platformTheme.name = "gnome"; + style.name = "adwaita-dark"; + }; +} diff --git a/home/desktop/i3.nix b/home/desktop/i3.nix index 31cf976..d22027d 100644 --- a/home/desktop/i3.nix +++ b/home/desktop/i3.nix @@ -6,6 +6,7 @@ config = null; extraConfig = (builtins.readFile ./i3/config) + '' exec --no-startup-id ${pkgs.feh}/bin/feh --bg-fill ${./assets/samuraii.jpg} + exec --no-startup-id ${pkgs.picom}/bin/picom --backend glx --vsync ''; }; } diff --git a/home/jashton.nix b/home/jashton.nix index a5379b3..730ae1d 100644 --- a/home/jashton.nix +++ b/home/jashton.nix @@ -13,24 +13,24 @@ # This file exists solely to prevent the zsh-newuser-install prompt. ''; - home.sessionVariables = { - WINIT_X11_SCALE_FACTOR = "1.0"; - }; - home.packages = with pkgs; [ aider-chat animdl asciinema claude-code discord + ffmpeg freetube gemini-cli intelli-shell jrnl keepass + libreoffice mc scrot spotify + vlc + yt-dlp (pkgs.writeShellScriptBin "find-open-port" '' if [ -z "$1" ]; then diff --git a/home/programs/alacritty.nix b/home/programs/alacritty.nix index 5d8e8de..5a5f084 100644 --- a/home/programs/alacritty.nix +++ b/home/programs/alacritty.nix @@ -16,7 +16,7 @@ family = "Hack Nerd Font"; style = "Mono"; }; - size = 8.0; + size = 14.0; }; colors = { diff --git a/hosts/thinkpad/hardware.nix b/hosts/thinkpad/hardware.nix index b39e66f..2cd5f09 100644 --- a/hosts/thinkpad/hardware.nix +++ b/hosts/thinkpad/hardware.nix @@ -8,7 +8,7 @@ [ (modulesPath + "/installer/scan/not-detected.nix") ]; - boot.initrd.availableKernelModules = [ "xhci_pci" "thunderbolt" "nvme" "usb_storage" "usbhid" "sd_mod" ]; + boot.initrd.availableKernelModules = [ "xhci_pci" "thunderbolt" "nvme" "usb_storage" "usbhid" "sd_mod" "uvcvideo" ]; boot.initrd.kernelModules = [ ]; boot.kernelModules = [ "kvm-intel" ]; boot.extraModulePackages = [ ]; @@ -25,12 +25,46 @@ }; swapDevices = - [ { device = "/dev/disk/by-uuid/332e7db3-3f68-4fd7-be2a-0d091ac936ae"; } - ]; + [ { device = "/dev/disk/by-uuid/332e7db3-3f68-4fd7-be2a-0d091ac936ae"; } ]; + + services.udev.extraRules = '' + SUBSYSTEM=="video4linux", ATTRS{interface}=="Integrated IR Camera", ATTR{index}=="0", SYMLINK+="video-ir" + ''; + + services.howdy = { + enable = true; + control = "sufficient"; + settings = { + video = { + device_path = "/dev/video-ir"; + }; + }; + }; + + services.linux-enable-ir-emitter.enable = true; + + powerManagement.resumeCommands = '' + ${pkgs.linux-enable-ir-emitter}/bin/linux-enable-ir-emitter run + ''; nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; hardware = { cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; - bluetooth.enable = true; + sensor.iio.enable = true; + + bluetooth = { + enable = true; + powerOnBoot = true; + + settings = { + General = { + Experimental = true; + KernelExperimental = true; + + ControllerMode = "dual"; + Enable = "Source,Sink,Media,Socket"; + }; + }; + }; }; } diff --git a/system/core/audio.nix b/system/core/audio.nix new file mode 100644 index 0000000..d9921b2 --- /dev/null +++ b/system/core/audio.nix @@ -0,0 +1,78 @@ +{ config, lib, pkgs, ... }: + +{ + security.rtkit.enable = true; + services.avahi.enable = true; + services.pulseaudio.enable = false; + + systemd.user.services.mpris-proxy = { + description = "Mpris proxy"; + after = [ "network.target" "sound.target" ]; + wantedBy = [ "default.target" ]; + }; + + services.pipewire = { + enable = true; + alsa = { + enable = true; + support32Bit = true; + }; + pulse.enable = true; + wireplumber.enable = true; + raopOpenFirewall = true; + + # Dynamic, optimized buffer sizes to eliminate data starvation + extraConfig.pipewire = { + "context.properties" = { + "default.clock.rate" = 48000; + "default.clock.quantum" = 1024; + "default.clock.min-quantum" = 512; + "default.clock.max-quantum" = 2048; + }; + + "10-airplay" = { + "context-modules" = [ + { + name = "libpipewire-module-raop-discover"; + } + ]; + }; + }; + + wireplumber.extraConfig = { + "10-bluetooth-policy" = { + "wireplumber.settings" = { + "bluetooth.autoswitch-to-headset" = false; + }; + }; + + # Properly disables node suspension using WirePlumber 0.5+ syntax + "10-disable-suspend" = { + "monitor.bluez.rules" = [ + { + matches = [ + { + "node.name" = "~bluez_output.*"; + } + ]; + actions = { + update-props = { + "session.suspend-timeout-seconds" = 0; + }; + }; + } + ]; + }; + + # Force High Quality Audio profiles ONLY (Blocks Handsfree Mic Downgrades) + "11-bluetooth-enhancements" = { + "monitor.bluez.properties" = { + "bluez5.roles" = [ "a2dp_sink" "a2dp_source" ]; + "bluez5.codecs" = [ "aac" "aptx" "sbc" ]; + "bluez5.enable-sbc-xq" = false; + "bluez5.enable-hw-volume" = true; + }; + }; + }; + }; +} diff --git a/system/core/default.nix b/system/core/default.nix index e77f7ff..1529585 100644 --- a/system/core/default.nix +++ b/system/core/default.nix @@ -2,6 +2,7 @@ { imports = [ + ./audio.nix ./boot.nix ./env.nix ./locale.nix diff --git a/system/core/env.nix b/system/core/env.nix index f7f0816..30fca68 100644 --- a/system/core/env.nix +++ b/system/core/env.nix @@ -8,6 +8,10 @@ nerd-fonts.hack ]; + environment.sessionVariables = { + NIXOS_OZONE_WL = "1"; + }; + nix.settings = { experimental-features = [ "nix-command" "flakes" ]; }; @@ -26,10 +30,10 @@ shellAliases = { enix = "nvim $HOME/.dotfiles/flake.nix"; - unix = "sudo nixos-rebuild switch --flake $HOME/.dotfiles#thinkpad"; ehome = "nvim $HOME/.dotfiles/home/jashton.nix"; l = "ls -la"; ports = "cat $HOME/.dotfiles/ports.registry"; + accentum = "bluetoothctl connect 80:C3:BA:50:43:DF"; }; ohMyZsh = { diff --git a/system/packages.nix b/system/packages.nix index 7c8625c..7e6828c 100644 --- a/system/packages.nix +++ b/system/packages.nix @@ -13,7 +13,16 @@ firefox git github-cli + gnomeExtensions.blur-my-shell + gnomeExtensions.caffeine + gnomeExtensions.clipboard-indicator + gnomeExtensions.dash-to-panel + gnomeExtensions.gjs-osk + gnomeExtensions.gsconnect + gnomeExtensions.just-perfection + gnomeExtensions.removable-drive-menu gnome-keyring + gnome-network-displays google-chrome libnotify mypy @@ -23,13 +32,54 @@ picom polkit_gnome pulseaudioFull + rnote tldr tmux unrar unzip + v4l-utils vim wget xclip + xournalpp + + (pkgs.writeShellScriptBin "unix" '' + set -u + + DOTFILES="$HOME/.dotfiles" + ERRFILE=$(mktemp) + trap "rm -f '$ERRFILE'" EXIT + + echo "Building NixOS..." + sudo nixos-rebuild switch --flake "$DOTFILES#thinkpad" 2>&1 | tee "$ERRFILE" + STATUS=''${PIPESTATUS[0]} + + if [ "$STATUS" -ne 0 ]; then + PATTERN=$(find "$DOTFILES" -type f -name "*.nix" -printf "%f\n" | sort -u | tr '\n' '|' | sed 's/|$//') + MATCHES=$(grep -E "$PATTERN" "$ERRFILE" | sort -u) + if [ -n "$MATCHES" ]; then + printf '\nErrors in repo files:\n%s\n' "$MATCHES" + fi + exit 1 + fi + + GEN=$(readlink /nix/var/nix/profiles/system | sed 's/system-\([0-9]*\)-link/\1/') + VER=$(nixos-version) + TIMESTAMP=$(date '+%Y-%m-%d %H:%M') + + cd "$DOTFILES" + + if [ -z "$(${pkgs.git}/bin/git status --porcelain)" ]; then + echo "Generation $GEN active — no config changes to commit." + exit 0 + fi + + ${pkgs.git}/bin/git add -A + ${pkgs.git}/bin/git commit -m "nixos: gen $GEN @ $(hostname) — $TIMESTAMP + +$VER" + ${pkgs.git}/bin/git push + '') ]; programs.firefox.enable = true; diff --git a/system/security/default.nix b/system/security/default.nix index fb10b70..7921311 100644 --- a/system/security/default.nix +++ b/system/security/default.nix @@ -10,5 +10,11 @@ security = { polkit.enable = true; rtkit.enable = true; + + tpm2 = { + enable = true; + pkcs11.enable = true; + tctiEnvironment.enable = true; + }; }; } diff --git a/system/security/users.nix b/system/security/users.nix index 7696ac7..d69740d 100644 --- a/system/security/users.nix +++ b/system/security/users.nix @@ -6,7 +6,7 @@ isNormalUser = true; description = "Josh Ashton"; group = "jashton"; - extraGroups = [ "networkmanager" "wheel" ]; + extraGroups = [ "networkmanager" "wheel" "tss" "video" ]; shell = pkgs.zsh; }; diff --git a/system/services/default.nix b/system/services/default.nix index 6cc037c..8a7c124 100644 --- a/system/services/default.nix +++ b/system/services/default.nix @@ -2,8 +2,12 @@ { services = { - displayManager = { - ly.enable = true; + displayManager.gdm.enable = true; + desktopManager.gnome.enable = true; + gnome = { + core-apps.enable = true; + core-developer-tools.enable = true; + games.enable = true; }; xserver = { @@ -27,13 +31,6 @@ }; }; - picom = { - enable = true; - backend = "glx"; - vSync = true; - }; - - libinput = { enable = true; }; @@ -46,13 +43,5 @@ gvfs.enable = true; gnome.gnome-keyring.enable = true; blueman.enable = true; - pipewire = { - enable = true; - alsa = { - enable = true; - support32Bit = true; - }; - pulse.enable = true; - }; }; }