feat: home-manager entry point and per-user shims

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-28 06:48:38 +02:00
commit 7c9ce3fe10
3 changed files with 928 additions and 0 deletions

896
home.nix Normal file
View file

@ -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" <<EOF
#separator:Comma
#html:false
#deck:$deck
#notetype:Basic
#tags column:3
EOF
fi
}
seed_csv "$ANKI_DIR/dev/cards.csv" "Dev"
seed_csv "$ANKI_DIR/french/cards.csv" "French"
seed_csv "$ANKI_DIR/protocols/cards.csv" "Protocols"
for d in exports sources; do
[ -e "$ANKI_DIR/$d/.gitkeep" ] || touch "$ANKI_DIR/$d/.gitkeep"
done
'';
# --- Ghostty terminal theming (derived from the active theme) ---
#
# The omni theme modules theme Alacritty (programs.alacritty.settings.
# colors) but historically left Ghostty — the default terminal — on its
# built-in palette, so `omni-theme <name>` 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 <name>`.
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 <leader>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 = "<C-h>",
down = "<C-j>",
up = "<C-k>",
right = "<C-l>",
last_active = "<C-\\>",
next = "<C-Space>",
},
})
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" ];
};
}