diff --git a/home.nix b/home.nix new file mode 100644 index 0000000..469bec3 --- /dev/null +++ b/home.nix @@ -0,0 +1,896 @@ +{ + config, + pkgs, + lib, + inputs, + settings, + osConfig, + ... +}: + +let + features = osConfig.omni.features; + featureEnabled = name: features.${name} or false; + + # Launches rpi-imager as root while preserving the Wayland session. + # `sudo -A` shows a graphical password prompt via SUDO_ASKPASS; `env` + # is executed as root and re-injects the env vars sudo would otherwise + # strip — no sudoers env_keep changes needed. + rpiImagerLauncher = pkgs.writeShellScriptBin "rpi-imager-launcher" '' + export SUDO_ASKPASS=${pkgs.lxqt.lxqt-openssh-askpass}/bin/lxqt-openssh-askpass + exec sudo -A ${pkgs.coreutils}/bin/env \ + WAYLAND_DISPLAY="$WAYLAND_DISPLAY" \ + XDG_RUNTIME_DIR="/run/user/$(id -u)" \ + XDG_SESSION_TYPE=wayland \ + ${pkgs.rpi-imager}/bin/rpi-imager "$@" + ''; +in +{ + home.username = settings.user; + home.homeDirectory = "/home/${settings.user}"; + home.stateVersion = settings.stateVersion; + + programs.home-manager.enable = true; + + # ~/dev/CLAUDE.md — workspace-level instructions for Claude across + # the aiolabs/AIO stack. ~/dev/ isn't itself a git repo (it's a + # workspace of sibling repos), so we track this file from /etc/nixos + # instead. mkOutOfStoreSymlink → mutable: edits in + # /etc/nixos/files/dev-CLAUDE.md take effect immediately without a + # rebuild; commit changes from /etc/nixos. + home.file."dev/CLAUDE.md".source = + config.lib.file.mkOutOfStoreSymlink /etc/nixos/files/dev-CLAUDE.md; + + # ~/Anki — spaced-repetition source tree. README is managed (symlink to + # /etc/nixos/files/anki-README.md, edit + commit there). Deck dirs and CSV + # stubs are seeded once via activation and then owned by the user — Anki's + # own collection lives at ~/.local/share/Anki2/ and is unrelated. + home.file."Anki/README.md".source = + config.lib.file.mkOutOfStoreSymlink /etc/nixos/files/anki-README.md; + + home.activation.ankiBootstrap = lib.hm.dag.entryAfter [ "writeBoundary" ] '' + ANKI_DIR="$HOME/Anki" + mkdir -p "$ANKI_DIR/dev" "$ANKI_DIR/french" "$ANKI_DIR/protocols" "$ANKI_DIR/exports" "$ANKI_DIR/sources" + + seed_csv() { + local path="$1" deck="$2" + if [ ! -e "$path" ]; then + cat > "$path" <` appeared to do nothing in a + # Ghostty window. Derive Ghostty's config from the same Alacritty colors + # the active theme sets, so the default terminal tracks the theme with + # exact parity. Gated on the Alacritty palette being present (themes set + # it only when the coding/media feature is on); otherwise Ghostty keeps + # its own defaults, matching Alacritty's behaviour. + xdg.configFile."ghostty/config" = + let + ac = config.programs.alacritty.settings.colors or null; + hex = s: "#" + lib.removePrefix "#" s; + ansi = lib.optionals (ac != null) [ + ac.normal.black + ac.normal.red + ac.normal.green + ac.normal.yellow + ac.normal.blue + ac.normal.magenta + ac.normal.cyan + ac.normal.white + ac.bright.black + ac.bright.red + ac.bright.green + ac.bright.yellow + ac.bright.blue + ac.bright.magenta + ac.bright.cyan + ac.bright.white + ]; + palette = lib.concatStringsSep "\n" ( + lib.imap0 (i: c: "palette = ${toString i}=${hex c}") ansi + ); + cursor = lib.optionalString (ac != null && ac ? cursor) '' + cursor-color = ${hex ac.cursor.cursor} + cursor-text = ${hex ac.cursor.text} + ''; + in + lib.mkIf (ac != null && ac ? normal && ac ? bright) { + # Ghostty writes a default-template config on first launch, so this + # path usually pre-exists as a plain file home-manager would refuse to + # clobber. We own it declaratively now (like alacritty.toml), so force. + force = true; + text = '' + # Generated by /etc/nixos/home.nix from the active omni theme's + # Alacritty palette. Do not edit — switch with `omni-theme `. + background = ${hex ac.primary.background} + foreground = ${hex ac.primary.foreground} + ${cursor}${palette} + ''; + }; + + # --- Packages gated by omni.features --- + + home.packages = + with pkgs; + # Core (always installed) + [ + ghostty + alacritty + starship + ranger + yazi + btop + htop + + # CLI essentials + ripgrep + fd + bat + eza + fzf + zoxide + jq + yq + curl + wget + qrencode + openssl + imagemagick + + # ZSA Moonlander flashing GUI (needs hardware.keyboard.zsa.enable) + keymapp + + # Wayland tools + wl-clipboard + wlr-randr + + # SD-card / RPi flasher + rpi-imager + rpiImagerLauncher + + # Screenshot + grim + slurp + swappy + + # Fonts + nerd-fonts.jetbrains-mono + nerd-fonts.fira-code + nerd-fonts.hack + noto-fonts + noto-fonts-color-emoji + liberation_ttf + ] + + # Development tools + ++ lib.optionals (featureEnabled "coding") [ + vscode + lazygit + gh + git-lfs + delta + meld + httpie + wf-recorder + ] + + # Media + ++ lib.optionals (featureEnabled "media") [ + mpv + imv + ffmpeg + nvtopPackages.full + ] + + # Browsers (always include firefox; extras gated on office/communication) + ++ [ firefox ] + ++ lib.optionals (featureEnabled "office" || featureEnabled "communication") [ + chromium + brave + ] + + # Communication + ++ lib.optionals (featureEnabled "communication") [ + discord + slack + telegram-desktop + ] + + # Productivity + ++ lib.optionals (featureEnabled "office") [ + obsidian + zathura + libreoffice + ]; + + # --- Programs (always enabled) --- + + programs.git = { + enable = true; + signing.format = "openpgp"; + settings = { + user.name = settings.gitName; + user.email = settings.gitEmail; + alias = { + st = "status"; + co = "checkout"; + br = "branch"; + cm = "commit -m"; + lg = "log --graph --oneline --decorate"; + unstage = "reset HEAD --"; + last = "log -1 HEAD"; + }; + init.defaultBranch = "main"; + pull.rebase = false; + push.autoSetupRemote = true; + merge.conflictStyle = "diff3"; + diff.colorMoved = "default"; + }; + }; + + programs.delta = { + enable = true; + enableGitIntegration = true; + options = { + features = "decorations"; + side-by-side = true; + navigate = true; + }; + }; + + programs.jujutsu = { + enable = true; + settings = { + user = { + name = settings.gitName; + email = settings.gitEmail; + }; + ui.default-command = "log"; + ui.diff-formatter = ":git"; + }; + }; + + programs.bash = { + enable = true; + + shellAliases = { + ll = "eza -la"; + ls = "eza"; + l = "eza -lah"; + tree = "eza --tree"; + ".." = "cd .."; + "..." = "cd ../.."; + g = "git"; + lg = "lazygit"; + cat = "bat"; + grep = "rg"; + find = "fd"; + rebuild = "sudo nixos-rebuild switch --flake /etc/nixos#omni"; + update = "nix flake update"; + clean = "nix-collect-garbage -d"; + c = "opencode"; + cx = "claude --dangerously-skip-permissions"; + qr = "qrencode -t ansiutf8"; + # Duplex A4 to the Brother HL-L3240CDW — long-edge (portrait) default, + # short-edge for landscape. Append the file: `print doc.pdf`. + print = "lp -d Brother_HL_L3240CDW_series -o media=A4 -o sides=two-sided-long-edge"; + print-landscape = "lp -d Brother_HL_L3240CDW_series -o media=A4 -o sides=two-sided-short-edge"; + }; + + initExtra = '' + eval "$(starship init bash)" + eval "$(zoxide init bash)" + source ${pkgs.fzf}/share/fzf/key-bindings.bash + source ${pkgs.fzf}/share/fzf/completion.bash + if [ -n "$IN_NIX_SHELL" ]; then + export PS1="[nix-shell] $PS1" + fi + + # Route the terminal command through the same wrapper walker uses. + alias rpi-imager=rpi-imager-launcher + + # Passive backstop for the claude-memory weekly snapshot timer — + # if drift has gone uncommitted for >7 days, surface it on shell + # startup. The systemd.user.timers.claude-memory-snapshot unit + # is the primary mechanism; this is a visibility-only fallback. + _claude_memory_drift_check() { + [ -d "$HOME/.claude/.git" ] || return + local last drift + last=$(git -C "$HOME/.claude" log -1 --format=%ct 2>/dev/null) || return + drift=$(( ($(date +%s) - last) / 86400 )) + if [ "$drift" -gt 7 ] && [ -n "$(git -C "$HOME/.claude" status --porcelain 2>/dev/null)" ]; then + echo "[claude-memory] $drift days since last snapshot, uncommitted drift in ~/.claude/" + fi + } + _claude_memory_drift_check + ''; + }; + + programs.starship = { + enable = true; + settings = { + format = lib.mkDefault '' + [╭─](bold green)$username[@](bold yellow)$hostname [in ](bold white)$directory$git_branch$git_status$cmd_duration + [╰─](bold green)$character + ''; + character = { + success_symbol = lib.mkDefault "[➜](bold green)"; + error_symbol = lib.mkDefault "[➜](bold red)"; + }; + directory = { + truncation_length = 3; + truncate_to_repo = true; + style = lib.mkDefault "bold cyan"; + }; + git_branch = { + style = lib.mkDefault "bold purple"; + symbol = " "; + }; + git_status = { + style = lib.mkDefault "bold red"; + ahead = "⇡\${count}"; + diverged = "⇕⇡\${ahead_count}⇣\${behind_count}"; + behind = "⇣\${count}"; + }; + cmd_duration = { + min_time = 500; + format = " took [$duration](bold yellow)"; + }; + }; + }; + + programs.alacritty = { + enable = true; + settings = { + window = { + padding = { + x = 10; + y = 10; + }; + opacity = 0.95; + decorations = "none"; + }; + font = { + normal = { + family = "JetBrainsMono Nerd Font"; + style = "Regular"; + }; + size = 12.0; + }; + cursor = { + style = "Block"; + unfocused_hollow = true; + }; + }; + }; + + # Neovim is provided by the lazyvim-nix home-manager module + # (wired through `home-manager.sharedModules` in flake.nix). The + # previous setup half-installed lazy.nvim and then required a + # `lua/config/lazy.lua` file that was never created on a fresh + # install — see commit history. lazyvim-nix tracks LazyVim + # releases upstream and pins every plugin via the nix store, so + # there is no runtime download. extras opt in to LazyVim's + # language packs (LSP servers, formatters, treesitter parsers). + programs.lazyvim = { + enable = true; + # Treat nvim/vim/vi as aliases the way the old programs.neovim + # block did. + extraPackages = with pkgs; [ + ripgrep # used by telescope live-grep + fd # used by telescope file-find + lazygit # for the LazyVim lazygit integration + nixd # module-aware Nix LSP (alongside nil from extras.lang.nix) + ]; + extras.lang.nix.enable = true; + extras.lang.julia.enable = true; + + # Disable LazyVim's format-on-save globally. `vim.g.autoformat` + # is LazyVim's documented switch; set it false here rather than + # toggling per-buffer with uf each session. Re-enable at + # runtime with `:lua vim.g.autoformat = true` if needed. + config.options = '' + vim.g.autoformat = false + ''; + + # Plain-text accounting: treesitter grammars for syntax highlighting + treesitterParsers = with pkgs.vimPlugins.nvim-treesitter-parsers; [ + beancount + ledger + ]; + + # ftplugin support (completion, alignment, checker integration) + plugins.beancount-ledger = '' + return { + { "nathangrigg/vim-beancount", ft = "beancount" }, + { "ledger/vim-ledger", ft = "ledger" }, + } + ''; + + # nixd alongside nil_ls (which extras.lang.nix already wires). Both + # attach to .nix buffers — nil is faster for general lookups, nixd + # is module-aware and resolves omni.* options + jump-to-definition + # into the flake's modules. The options.* exprs anchor nixd's eval + # engine at /etc/nixos so it can introspect the live module tree. + plugins.nixd = '' + return { + "neovim/nvim-lspconfig", + opts = { + servers = { + nixd = { + settings = { + nixd = { + nixpkgs = { + expr = "import (builtins.getFlake \"/etc/nixos\").inputs.nixpkgs { }", + }, + options = { + nixos = { + expr = "(builtins.getFlake \"/etc/nixos\").nixosConfigurations.omni.options", + }, + ["home-manager"] = { + expr = "(builtins.getFlake \"/etc/nixos\").nixosConfigurations.omni.options.home-manager.users.value.padreug", + }, + }, + }, + }, + }, + }, + }, + } + ''; + + # Seamless C-h/j/k/l navigation between nvim splits and tmux panes. + # Pairs with tmuxPlugins.vim-tmux-navigator on the tmux side (already + # wired below at programs.tmux.plugins) — both use the same `is_vim` + # heuristic, so tmux forwards C-hjkl to nvim when nvim is the focused + # pane and nvim hands control back to tmux at split edges. + plugins.nvim-tmux-navigation = '' + return { + { + "alexghergh/nvim-tmux-navigation", + event = "VeryLazy", + config = function() + require("nvim-tmux-navigation").setup({ + disable_when_zoomed = true, + keybindings = { + left = "", + down = "", + up = "", + right = "", + last_active = "", + next = "", + }, + }) + end, + }, + } + ''; + }; + # Make `defaultEditor = true` work without re-enabling + # programs.neovim (which would conflict with lazyvim-nix). + home.sessionVariables = { + EDITOR = "nvim"; + # pi-coding-agent: the binary is store-pinned, so its self-update + # check is noise — omni-rebuild is the only thing that bumps it. + PI_SKIP_VERSION_CHECK = "1"; + # User-installed pi packages can't live in the read-only nix store; + # the README flags this var as "useful for Nix/Guix where store + # paths tokenize poorly". + PI_PACKAGE_DIR = "${config.home.homeDirectory}/.pi/packages"; + PI_TELEMETRY = "0"; + }; + home.shellAliases = { + vi = "nvim"; + vim = "nvim"; + }; + + programs.firefox = { + enable = true; + # Adopt the new XDG profile path (the 26.05 default) ahead of the + # stateVersion bump. The existing ~/.mozilla/firefox must be moved to + # $XDG_CONFIG_HOME/mozilla/firefox so the profile carries over. + configPath = "${config.xdg.configHome}/mozilla/firefox"; + profiles.default.settings = { + "privacy.donottrackheader.enabled" = true; + "privacy.trackingprotection.enabled" = true; + "privacy.trackingprotection.socialtracking.enabled" = true; + }; + }; + + programs.chromium = { + enable = true; + extensions = [ + { id = "aeblfdkhhhdcdjpifhhbdiojplfjncoa"; } # 1Password + { id = "nopnfneeeclpahhclgfbhkianfmickdj"; } # Claude + { id = "ccpjjenaollgbhnejgfmapdmjmadpgio"; } # Copy URL + { id = "kpgefcfmnafjgpblomihpgcdlhiodkdc"; } # nos2x (Nostr) + { id = "dneaehbmnbhcippjikoajpoabadpodje"; } # Old Reddit Redirect + { id = "kbmfpngjjgdllneeigpgjifpgocmfgmb"; } # Reddit Enhancement Suite + { id = "ddkjiahejlhfcafbddmgiahcphecmpfh"; } # uBlock Origin Lite + { id = "dbepggeogbaibhgnhhndojpepiihcmeb"; } # Vimium + ]; + }; + + # Override rpi-imager's .desktop so launchers (walker, etc.) go through + # the wrapper that preserves the Wayland session. Matches the upstream + # filename so this entry replaces the one from the rpi-imager package. + xdg.desktopEntries."com.raspberrypi.rpi-imager" = { + name = "Raspberry Pi Imager"; + comment = "Tool for writing images to SD cards for Raspberry Pi"; + exec = "rpi-imager-launcher %u"; + icon = "rpi-imager"; + categories = [ "Utility" ]; + mimeType = [ + "x-scheme-handler/rpi-imager" + "application/vnd.raspberrypi.imager-manifest+json" + ]; + type = "Application"; + startupNotify = false; + }; + + # Electron apps default to the basic_text password store when no flag + # is passed, which corrupts the existing gnome-libsecret-encrypted DB. + # The hyprland autostart already passes --password-store=gnome-libsecret; + # these overrides make launches from walker/file managers match. + xdg.desktopEntries.signal = { + name = "Signal"; + comment = "Private messaging from your desktop"; + exec = "signal-desktop --password-store=gnome-libsecret %U"; + icon = "signal-desktop"; + categories = [ + "Network" + "InstantMessaging" + "Chat" + ]; + mimeType = [ + "x-scheme-handler/sgnl" + "x-scheme-handler/signalcaptcha" + ]; + terminal = false; + type = "Application"; + settings.StartupWMClass = "signal"; + }; + + xdg.desktopEntries.element-desktop = { + name = "Element"; + genericName = "Matrix Client"; + comment = "Feature-rich client for Matrix.org"; + exec = "element-desktop --password-store=gnome-libsecret %u"; + icon = "element"; + categories = [ + "Network" + "InstantMessaging" + "Chat" + ]; + mimeType = [ + "x-scheme-handler/element" + "x-scheme-handler/io.element.desktop" + ]; + type = "Application"; + settings.StartupWMClass = "Element"; + }; + + xdg.desktopEntries.ferdium = { + name = "Ferdium"; + comment = "Desktop app bringing all your messaging services into one installable"; + exec = "ferdium --password-store=gnome-libsecret %U"; + icon = "ferdium"; + categories = [ + "Network" + "InstantMessaging" + ]; + mimeType = [ "x-scheme-handler/ferdium" ]; + terminal = false; + type = "Application"; + settings.StartupWMClass = "Ferdium"; + }; + + # Set chromium as default browser for xdg + xdg.mimeApps = { + enable = true; + defaultApplications = { + "text/html" = "chromium-browser.desktop"; + "x-scheme-handler/http" = "chromium-browser.desktop"; + "x-scheme-handler/https" = "chromium-browser.desktop"; + "x-scheme-handler/about" = "chromium-browser.desktop"; + "x-scheme-handler/unknown" = "chromium-browser.desktop"; + }; + }; + + # VS Code — uncomment if needed + # programs.vscode = lib.mkIf (featureEnabled "coding") { + # enable = true; + # profiles.default.extensions = with pkgs.vscode-extensions; [ + # pkief.material-icon-theme + # zhuangtongfa.material-theme + # rust-lang.rust-analyzer + # golang.go + # ms-python.python + # ms-vscode.cpptools + # dbaeumer.vscode-eslint + # esbenp.prettier-vscode + # bradlc.vscode-tailwindcss + # eamodio.gitlens + # vscodevim.vim + # yzhang.markdown-all-in-one + # jnoortheen.nix-ide + # ]; + # profiles.default.userSettings = { + # "workbench.colorTheme" = lib.mkDefault "One Dark Pro"; + # "workbench.iconTheme" = lib.mkDefault "material-icon-theme"; + # "editor.fontFamily" = lib.mkDefault "'JetBrainsMono Nerd Font', monospace"; + # "editor.fontSize" = lib.mkDefault 14; + # "editor.fontLigatures" = lib.mkDefault true; + # "editor.formatOnSave" = lib.mkDefault true; + # "editor.minimap.enabled" = lib.mkDefault false; + # "editor.rulers" = lib.mkDefault [ 80 120 ]; + # "terminal.integrated.fontFamily" = lib.mkDefault "'JetBrainsMono Nerd Font'"; + # "vim.enableNeovim" = lib.mkDefault true; + # }; + # }; + + programs.direnv = { + enable = true; + nix-direnv.enable = true; + enableBashIntegration = true; + }; + + programs.zoxide = { + enable = true; + enableBashIntegration = true; + }; + + programs.bat = { + enable = true; + config = { + theme = lib.mkDefault "TwoDark"; + pager = lib.mkDefault "less -FR"; + }; + }; + + programs.fzf = { + enable = true; + enableBashIntegration = true; + defaultCommand = lib.mkDefault "fd --type f --hidden --follow --exclude .git"; + defaultOptions = lib.mkDefault [ + "--height 40%" + "--layout reverse" + "--border" + "--inline-info" + "--color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899'" + ]; + }; + + programs.btop = { + enable = true; + settings = { + color_theme = lib.mkDefault "tokyo-night"; + theme_background = lib.mkDefault false; + update_ms = lib.mkDefault 1000; + }; + }; + + programs.gpg.enable = true; + services.gpg-agent = { + enable = true; + pinentry.package = pkgs.pinentry-gtk2; + }; + + programs.tmux = { + enable = true; + shell = "${pkgs.bash}/bin/bash"; + terminal = "tmux-256color"; + prefix = "C-a"; + mouse = true; + keyMode = "vi"; + baseIndex = 1; + escapeTime = 0; + historyLimit = 1000000; + + plugins = with pkgs.tmuxPlugins; [ + yank + vim-tmux-navigator + tmux-thumbs + tmux-fzf + fzf-tmux-url + + { + plugin = resurrect; + extraConfig = '' + set -g @resurrect-strategy-nvim 'session' + set -g @resurrect-capture-pane-contents 'on' + ''; + } + { + plugin = continuum; + extraConfig = '' + set -g @continuum-restore 'on' + set -g @continuum-save-interval '15' + ''; + } + { + plugin = tmux-sessionx; + extraConfig = '' + set -g @sessionx-bind 'o' + set -g @sessionx-bind-zo-new-window 'ctrl-y' + set -g @sessionx-auto-accept 'off' + set -g @sessionx-window-height '85%' + set -g @sessionx-window-width '75%' + set -g @sessionx-zoxide-mode 'on' + set -g @sessionx-custom-paths-subdirectories 'false' + set -g @sessionx-filter-current 'false' + ''; + } + { + plugin = tmux-floax; + extraConfig = '' + set -g @floax-width '80%' + set -g @floax-height '80%' + set -g @floax-border-color 'magenta' + set -g @floax-text-color 'blue' + set -g @floax-bind 'p' + set -g @floax-change-path 'true' + ''; + } + ]; + + extraConfig = '' + set -ga terminal-overrides ',*256col*:RGB' + set -g detach-on-destroy off + set -g renumber-windows on + set -g set-clipboard on + set -g status-position top + + # Forward modified keys (Shift+Enter, Ctrl+Enter, …) in CSI-u + # format. Required for pi-coding-agent (and other TUIs that + # distinguish modified Enter); see + # https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/tmux.md + set -g extended-keys on + set -g extended-keys-format csi-u + set -g pane-active-border-style 'fg=magenta,bg=default' + set -g pane-border-style 'fg=brightblack,bg=default' + + set -g @fzf-url-fzf-options '-p 60%,30% --prompt=" " --border-label=" Open URL "' + set -g @fzf-url-history-limit '2000' + + # --- window / session bindings --- + bind ^X lock-server + bind ^C new-window -c "$HOME" + bind ^D detach + bind '*' list-clients + + bind H previous-window + bind L next-window + bind r command-prompt "rename-window %%" + bind R source-file ~/.config/tmux/tmux.conf \; display "reloaded" + bind ^A last-window + bind ^W list-windows + bind w list-windows + bind z resize-pane -Z + bind ^L refresh-client + bind | split-window -h -c "#{pane_current_path}" + bind s split-window -v -c "#{pane_current_path}" + bind v split-window -h -c "#{pane_current_path}" + bind '"' choose-window + + # pane resize (repeatable); pane-nav handled by vim-tmux-navigator via C-hjkl + bind -r h resize-pane -L 5 + bind -r j resize-pane -D 5 + bind -r k resize-pane -U 5 + bind -r l resize-pane -R 5 + + bind : command-prompt + bind P set pane-border-status + bind x swap-pane -D + bind S choose-session + bind K send-keys "clear" \; send-keys "Enter" + bind-key -T copy-mode-vi v send-keys -X begin-selection + + bind-key g popup -E -w 95% -h 95% -d '#{pane_current_path}' ${pkgs.lazygit}/bin/lazygit + ''; + }; + + xdg = { + enable = true; + userDirs = { + enable = true; + createDirectories = true; + setSessionVariables = true; + }; + }; + + gtk = { + enable = true; + theme = { + name = lib.mkDefault "Catppuccin-Mocha-Standard-Blue-dark"; + package = lib.mkDefault ( + pkgs.catppuccin-gtk.override { + variant = "mocha"; + } + ); + }; + gtk4.theme = null; + iconTheme = { + name = lib.mkDefault "Papirus-Dark"; + package = lib.mkDefault pkgs.papirus-icon-theme; + }; + }; + + qt = { + enable = true; + platformTheme.name = lib.mkDefault "gtk3"; + style.name = lib.mkDefault "gtk2"; + }; + + home.pointerCursor = { + package = pkgs.catppuccin-cursors.mochaDark; + name = "catppuccin-mocha-dark-cursors"; + size = 24; + gtk.enable = true; + x11.enable = true; + }; + + # Weekly refresh + digest of ~/dev/refs/ reference repos. + # Manifest at ~/dev/refs/refs.toml; see ~/dev/refs/README.md. + systemd.user.services.refs-refresh = { + Unit.Description = "Refresh and digest reference repos"; + Service = { + Type = "oneshot"; + ExecStart = [ + # `-` prefix: a per-repo refresh error must not abort the digests + # (a oneshot stops on the first failing ExecStart otherwise). + "-%h/dev/refs/bin/refresh" + "%h/dev/refs/bin/digest" + "%h/dev/refs/bin/upstream-digest" + ]; + }; + }; + + systemd.user.timers.refs-refresh = { + Unit.Description = "Weekly reference-repo refresh"; + Timer = { + OnCalendar = "Mon 09:00"; + Persistent = true; + }; + Install.WantedBy = [ "timers.target" ]; + }; + + # Weekly snapshot of ~/.claude/ memory drift. + # Auto-commits + pushes to padreug/claude-memory if there's any drift since + # last run. No-op on a clean tree. Script at ~/.claude/bin/snapshot. + systemd.user.services.claude-memory-snapshot = { + Unit.Description = "Weekly snapshot of ~/.claude/ memory"; + Service = { + Type = "oneshot"; + ExecStart = "%h/.claude/bin/snapshot"; + }; + }; + + systemd.user.timers.claude-memory-snapshot = { + Unit.Description = "Weekly snapshot of ~/.claude/ memory"; + Timer = { + OnCalendar = "Mon 09:30"; # 30 min after refs-refresh + Persistent = true; + }; + Install.WantedBy = [ "timers.target" ]; + }; +} diff --git a/users/padreug/home-manager.nix b/users/padreug/home-manager.nix new file mode 100644 index 0000000..4983660 --- /dev/null +++ b/users/padreug/home-manager.nix @@ -0,0 +1,16 @@ +# users/user/home-manager.nix +# +# Shim that forwards to the top-level home.nix via the module-system +# `imports` mechanism. The user name "user" matches settings.user in +# settings.nix (a placeholder — a real deployment would edit +# settings.nix and rename this dir to users//). +# +# home.nix expects `settings` as a function arg. mksystem passes it +# via `extraHmArgs = { inherit settings; }` from flake.nix, which +# becomes part of home-manager.extraSpecialArgs and reaches home.nix +# transparently through the imports mechanism. +{ ... }: + +{ + imports = [ ../../home.nix ]; +} diff --git a/users/user/home-manager.nix b/users/user/home-manager.nix new file mode 100644 index 0000000..4983660 --- /dev/null +++ b/users/user/home-manager.nix @@ -0,0 +1,16 @@ +# users/user/home-manager.nix +# +# Shim that forwards to the top-level home.nix via the module-system +# `imports` mechanism. The user name "user" matches settings.user in +# settings.nix (a placeholder — a real deployment would edit +# settings.nix and rename this dir to users//). +# +# home.nix expects `settings` as a function arg. mksystem passes it +# via `extraHmArgs = { inherit settings; }` from flake.nix, which +# becomes part of home-manager.extraSpecialArgs and reaches home.nix +# transparently through the imports mechanism. +{ ... }: + +{ + imports = [ ../../home.nix ]; +}