faf2242539
The flake's origin (ssh://git@code.emmathe.dev) must resolve on every host. Add a matchBlock for code.emmathe.dev: user git, Port 30009 (Gitea's non-default SSH port -- the critical bit), the dedicated ~/.ssh/code.emmathe.dev key, and identitiesOnly. The work box keeps its own ssh config (programs.ssh forced off there) which already has the entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
242 lines
8.3 KiB
Nix
242 lines
8.3 KiB
Nix
# Interactive shell: zsh + tmux. Wanted on every host.
|
|
{
|
|
lib,
|
|
pkgs,
|
|
inputs,
|
|
...
|
|
}:
|
|
{
|
|
imports = [
|
|
# Prebuilt nix-index database -> working command-not-found
|
|
# ("cmd not found -> which nix package provides it"), no manual indexing.
|
|
inputs.nix-index-database.homeModules.default
|
|
];
|
|
|
|
programs.zsh = {
|
|
enable = true;
|
|
enableCompletion = true;
|
|
enableVteIntegration = true;
|
|
autosuggestion.enable = true;
|
|
# Bind Up/Down for history-substring-search in BOTH cursor-key modes: CSI
|
|
# (^[[A/^[[B -- normal mode, and what the Linux TTY sends) and SS3
|
|
# (^[OA/^[OB -- application mode, used by foot, tmux and iTerm2). Binding
|
|
# only the default CSI form leaves it dead at the prompt in foot/iTerm2.
|
|
historySubstringSearch = {
|
|
enable = true;
|
|
searchUpKey = [
|
|
"^[[A"
|
|
"^[OA"
|
|
];
|
|
searchDownKey = [
|
|
"^[[B"
|
|
"^[OB"
|
|
];
|
|
};
|
|
history = {
|
|
append = true; # append, don't overwrite, on shell exit
|
|
size = 100000; # in-memory (HISTSIZE)
|
|
save = 100000; # on-disk (SAVEHIST)
|
|
ignoreDups = true; # drop consecutive duplicates
|
|
ignoreSpace = true; # leading-space commands stay out of history
|
|
expireDuplicatesFirst = true;
|
|
share = true; # live-share history across sessions
|
|
extended = true; # record timestamps
|
|
};
|
|
oh-my-zsh = {
|
|
enable = true;
|
|
plugins = [
|
|
"git"
|
|
"man"
|
|
"sudo" # double-Esc prefixes the last command with sudo
|
|
"colored-man-pages"
|
|
"extract" # `extract <archive>` for any format
|
|
];
|
|
theme = "robbyrussell";
|
|
};
|
|
syntaxHighlighting.enable = true;
|
|
initContent = lib.mkMerge [
|
|
# Auto-start tmux in every interactive terminal -- foot, iTerm2, the WSL
|
|
# shell, the Linux console -- so a new terminal lands straight in the
|
|
# multiplexer (session "main": attach if present, else create). Panes run
|
|
# a plain non-login zsh (tmux's default-command "${SHELL}"). Order 200
|
|
# runs before oh-my-zsh/compinit so the exec replaces the shell before
|
|
# that setup is wasted. Guards, each preventing a real breakage:
|
|
# interactive only -> don't hijack scp / `ssh host cmd` / scripted shells
|
|
# $TMUX empty -> a pane's zsh won't re-exec tmux (infinite loop)
|
|
# not SSH -> don't force inbound SSH logins into a server tmux
|
|
# not VS Code -> its integrated terminal manages itself
|
|
# tmux on PATH -> a failed exec would otherwise kill the login shell
|
|
(lib.mkOrder 200 ''
|
|
if [[ $- == *i* ]] \
|
|
&& [[ -z "$TMUX" ]] \
|
|
&& [[ -z "$SSH_CONNECTION" && -z "$SSH_TTY" ]] \
|
|
&& [[ "$TERM_PROGRAM" != "vscode" ]] \
|
|
&& command -v tmux >/dev/null 2>&1; then
|
|
exec tmux new-session -A -s main
|
|
fi
|
|
'')
|
|
# Prefix the prompt with the hostname over SSH (mkAfter).
|
|
(lib.mkOrder 1500 ''
|
|
if [ "$SSH_CLIENT" ] || [ "$SSH_TTY" ]; then
|
|
export PS1="%M $PS1"
|
|
fi
|
|
'')
|
|
];
|
|
shellAliases = {
|
|
# eza's zsh integration also defines these; set explicitly so the
|
|
# icons/git intent is obvious.
|
|
ls = "eza --icons --git";
|
|
ll = "eza --icons --git -l";
|
|
la = "eza --icons --git -la";
|
|
lt = "eza --icons --git --tree";
|
|
cls = "clear";
|
|
};
|
|
};
|
|
|
|
# Fuzzy finder: Ctrl-R fuzzy history, Ctrl-T files, Alt-C cd.
|
|
programs.fzf = {
|
|
enable = true;
|
|
enableZshIntegration = true;
|
|
};
|
|
|
|
# Frecency directory jumping: `z <fragment>`.
|
|
programs.zoxide = {
|
|
enable = true;
|
|
enableZshIntegration = true;
|
|
};
|
|
|
|
# Per-project environments auto-loaded on cd, with the Nix dev-shell cache.
|
|
programs.direnv = {
|
|
enable = true;
|
|
nix-direnv.enable = true;
|
|
};
|
|
|
|
# Modern ls (drives the ls aliases above).
|
|
programs.eza = {
|
|
enable = true;
|
|
git = true;
|
|
icons = "auto"; # boolean form is deprecated
|
|
};
|
|
|
|
# Syntax-highlighting pager, used as `bat` (acts like cat when piped).
|
|
programs.bat.enable = true;
|
|
|
|
# command-not-found backed by the prebuilt nix-index DB (module imported
|
|
# above). `comma` runs an uninstalled program once: `, cowsay hi`.
|
|
programs.nix-index.enable = true;
|
|
programs.nix-index-database.comma.enable = true;
|
|
|
|
# Nicer nixos-rebuild/home-manager (diffs) + a weekly user-GC timer.
|
|
programs.nh = {
|
|
enable = true;
|
|
flake = "$HOME/code/nixfiles";
|
|
clean = {
|
|
enable = true;
|
|
dates = "weekly";
|
|
extraArgs = "--keep 5 --keep-since 3d";
|
|
};
|
|
};
|
|
|
|
programs.tmux = {
|
|
enable = true;
|
|
reverseSplit = true;
|
|
terminal = "tmux-direct";
|
|
newSession = true;
|
|
keyMode = "vi";
|
|
historyLimit = 500000;
|
|
mouse = true;
|
|
escapeTime = 10; # was the 500ms default -> laggy ESC in vim
|
|
focusEvents = true; # let vim see focus changes (autoread)
|
|
baseIndex = 1; # sets both base-index and pane-base-index
|
|
|
|
plugins = with pkgs.tmuxPlugins; [
|
|
sensible
|
|
vim-tmux-navigator # Ctrl-h/j/k/l across vim splits and tmux panes
|
|
yank
|
|
{
|
|
# Catppuccin Mocha statusline (v2 API: flavour + window options must be
|
|
# set before the plugin loads, which home-manager does for plugin
|
|
# extraConfig; the status modules below go in the main extraConfig,
|
|
# which HM appends after all plugins).
|
|
plugin = catppuccin;
|
|
extraConfig = ''
|
|
set -g @catppuccin_flavor 'mocha'
|
|
set -g @catppuccin_window_status_style 'rounded'
|
|
'';
|
|
}
|
|
resurrect # save/restore sessions
|
|
{
|
|
plugin = continuum; # auto-save + restore on tmux start (after resurrect)
|
|
extraConfig = ''
|
|
set -g @continuum-restore 'on'
|
|
'';
|
|
}
|
|
];
|
|
# `reverseSplit = true` already binds s -> vertical and v -> horizontal
|
|
# split (the dotfiles' vim-style splits).
|
|
extraConfig = ''
|
|
# Run a non-login shell in new panes/windows.
|
|
set -g default-command "''${SHELL}"
|
|
|
|
# Drop the stock split keys in favour of the s/v binds above.
|
|
unbind %
|
|
unbind '"'
|
|
|
|
# Alt+Arrow pane navigation
|
|
bind -n M-Left select-pane -L
|
|
bind -n M-Right select-pane -R
|
|
bind -n M-Up select-pane -U
|
|
bind -n M-Down select-pane -D
|
|
|
|
# Tell tmux which capabilities the foot terminal supports, so truecolor,
|
|
# synchronised output, the system clipboard (OSC 52), window titles and
|
|
# cursor styling all pass through.
|
|
set -as terminal-features ",foot*:RGB"
|
|
set -as terminal-features ",foot*:sync"
|
|
set -as terminal-features ",foot*:clipboard"
|
|
set -as terminal-features ",foot*:title"
|
|
set -as terminal-features ",foot*:ccolour"
|
|
set -as terminal-features ",foot*:cstyle"
|
|
|
|
# No home-manager options for these.
|
|
set -g renumber-windows on
|
|
set -g set-clipboard on
|
|
|
|
# Catppuccin v2 statusline. Must run after the plugin has loaded;
|
|
# home-manager appends this extraConfig after the whole plugin list.
|
|
set -g status-left-length 100
|
|
set -g status-right-length 100
|
|
set -g status-left ""
|
|
set -g status-right "#{E:@catppuccin_status_application}"
|
|
set -ag status-right "#{E:@catppuccin_status_session}"
|
|
'';
|
|
};
|
|
|
|
# Add the key to the agent on first use, so the passphrase is typed once per
|
|
# login session rather than per commit/push (commit signing uses this agent).
|
|
# The work box keeps its own ssh config (see work/default.nix), so this only
|
|
# manages ~/.ssh/config on the personal hosts.
|
|
programs.ssh = {
|
|
enable = true;
|
|
addKeysToAgent = "yes";
|
|
# macOS: also cache in the login keychain (no prompt after first unlock).
|
|
# UseKeychain is unknown to non-Apple openssh, so only emit it on Darwin.
|
|
extraConfig = lib.optionalString pkgs.stdenv.hostPlatform.isDarwin ''
|
|
UseKeychain yes
|
|
'';
|
|
# Gitea remote (the flake's origin) -- required on every host. Pins the
|
|
# dedicated key so the right identity is offered. identitiesOnly avoids
|
|
# "too many authentication failures" when the agent holds several keys.
|
|
matchBlocks."code.emmathe.dev" = {
|
|
user = "git";
|
|
port = 30009; # Gitea listens on a non-default SSH port
|
|
identityFile = "~/.ssh/code.emmathe.dev";
|
|
identitiesOnly = true;
|
|
};
|
|
};
|
|
|
|
# Run a user ssh-agent on Linux (macOS provides one via launchd). EDaaS also
|
|
# enables this in the work module; both being true merges cleanly.
|
|
services.ssh-agent.enable = lib.mkIf pkgs.stdenv.hostPlatform.isLinux true;
|
|
}
|