896 lines
27 KiB
Nix
896 lines
27 KiB
Nix
{
|
|
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" ];
|
|
};
|
|
}
|