diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b949072..b859272 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,8 @@ jobs: nix build --no-link \ .#checks.x86_64-linux.nix-quality \ .#checks.x86_64-linux.lua-format \ - .#checks.x86_64-linux.zsh-syntax + .#checks.x86_64-linux.zsh-syntax \ + .#checks.x86_64-linux.bash-syntax - name: Evaluate WSL configuration run: nix eval --raw '.#nixosConfigurations.nixos-wsl.config.system.build.toplevel.drvPath' diff --git a/.gitignore b/.gitignore index a3ab5e1..8525522 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Local/work-specific configuration files .zshenv.local +.bashrc.local git/.config/git/config.local # Herdr runtime state (written into the symlinked config dir; only config.toml is tracked) diff --git a/bash/.bash_profile b/bash/.bash_profile new file mode 100644 index 0000000..e053780 --- /dev/null +++ b/bash/.bash_profile @@ -0,0 +1,3 @@ +# Login shells read this instead of ~/.bashrc, so source ~/.bashrc here to keep +# login and non-login interactive shells identical. +[ -f "$HOME/.bashrc" ] && . "$HOME/.bashrc" diff --git a/bash/.bashrc b/bash/.bashrc new file mode 100644 index 0000000..1658d26 --- /dev/null +++ b/bash/.bashrc @@ -0,0 +1,115 @@ +# Minimal bash configuration, kept as a portable fallback to zsh/.zshrc. +# +# Layout mirrors the zsh config: environment first (so non-interactive login +# shells get PATH too, the job zsh/.zshenv does), interactive-only setup after +# the guard. Login shells reach this file through ~/.bash_profile. + +# --------------------------------------------------------------------------- +# Environment (mirrors zsh/.zshenv) +# --------------------------------------------------------------------------- +export EDITOR="nvim" +export VISUAL="nvim" +export DOTFILES="${DOTFILES:-$HOME/dotfiles}" + +export PATH="$HOME/.local/bin:$PATH" +export PATH="$HOME/go/bin:$PATH" +# This is required for asdf (0.16 or later) +export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH" +# reorder PATH to make sure Nix's home-manager PATH comes before +# system path, so it uses nix installed package first +export PATH="$HOME/.nix-profile/bin:$PATH" + +export CLOUDSDK_PYTHON=/usr/bin/python3 + +# GPG TTY for commit signing. `tty` writes "not a tty" to stdout and fails when +# stdin is not a terminal, so assign only on success and keep any inherited value. +if __tty=$(tty 2>/dev/null); then + export GPG_TTY="$__tty" +fi +unset __tty + +# Home Manager session variables, when this machine is managed by Nix. +# The first file sourced sets a guard, so the second is a no-op. +for __hm_vars in \ + "$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh" \ + "/etc/profiles/per-user/$USER/etc/profile.d/hm-session-vars.sh"; do + [ -r "$__hm_vars" ] && . "$__hm_vars" +done +unset __hm_vars + +# --------------------------------------------------------------------------- +# Interactive shells only +# --------------------------------------------------------------------------- +case $- in + *i*) ;; + *) return ;; +esac + +# History. mcfly bails out with an error unless the file already exists, so +# create it here rather than waiting for the first shell to exit. +HISTFILE="${HISTFILE:-$HOME/.bash_history}" +[ -e "$HISTFILE" ] || (umask 077 && : > "$HISTFILE") +HISTSIZE=50000 +HISTFILESIZE=100000 +HISTCONTROL=ignoreboth:erasedups +HISTIGNORE="ls:ll:la:cd:pwd:exit:clear:history" +HISTTIMEFORMAT="%F %T " +shopt -s histappend cmdhist + +# Shell behaviour (options unknown to bash 3.2 are skipped quietly) +shopt -s checkwinsize globstar autocd cdspell dirspell no_empty_cmd_completion 2>/dev/null + +# vi keybindings, matching the zsh vi-mode plugin +set -o vi +# Keys bash leaves unbound in vi-insert but zsh's viins keymap provides +bind -m vi-insert '"\C-l": clear-screen' 2>/dev/null +bind -m vi-insert '"\C-a": beginning-of-line' 2>/dev/null +bind -m vi-insert '"\C-e": end-of-line' 2>/dev/null +bind -m vi-insert '"\C-k": kill-line' 2>/dev/null +# Open the current buffer line in $EDITOR, as ^x^e does in zsh +bind -m vi-insert '"\C-x\C-e": edit-and-execute-command' 2>/dev/null + +# bash-completion (Nix profile, system, or Homebrew) +for __bc in \ + "$HOME/.nix-profile/share/bash-completion/bash_completion" \ + "/etc/profiles/per-user/$USER/share/bash-completion/bash_completion" \ + /usr/share/bash-completion/bash_completion \ + /opt/homebrew/etc/profile.d/bash_completion.sh \ + /usr/local/etc/profile.d/bash_completion.sh; do + if [ -r "$__bc" ]; then + . "$__bc" + break + fi +done +unset __bc + +# Prompt: user@host:cwd (git-branch) +__bash_git_branch() { + local branch + branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 0 + printf ' (%s)' "$branch" +} +PS1='\[\e[32m\]\u@\h\[\e[0m\]:\[\e[34m\]\w\[\e[33m\]$(__bash_git_branch)\[\e[0m\]\$ ' + +# General aliases +alias v="nvim" + +# eza as default lister +if command -v eza >/dev/null 2>&1; then + alias ls='eza --group-directories-first --icons=auto' + alias ll='eza -l --group-directories-first --icons=auto --git' + alias la='eza -la --group-directories-first --icons=auto --git' + alias lt='eza --tree --level=2 --icons=auto' +fi + +# Load local-specific configuration if it exists. Deliberately before the two +# integrations below: on bash < 5.1 (macOS /bin/bash is 3.2) PROMPT_COMMAND is a +# plain string, so a bare assignment here would wipe their hooks. Both append to +# whatever it leaves behind, and it can still set MCFLY_*/_ZO_* to configure them. +[ -f "$DOTFILES/bash/.bashrc.local" ] && . "$DOTFILES/bash/.bashrc.local" + +# zoxide: smarter cd, provides `z` and `zi` +command -v zoxide >/dev/null 2>&1 && eval "$(zoxide init bash)" + +# mcfly: fuzzy ^R history search (after zoxide, so it wraps that prompt hook) +command -v mcfly >/dev/null 2>&1 && eval "$(mcfly init bash)" diff --git a/docs/README.nix.md b/docs/README.nix.md index 87767ad..2fc0432 100644 --- a/docs/README.nix.md +++ b/docs/README.nix.md @@ -96,7 +96,7 @@ Before activation, customize `home.username` and `home.homeDirectory` in `home/d ### Development Tools - **Editors**: Neovim - **Terminal**: Herdr, Wezterm, Yazi -- **Shell**: Zsh with completion and syntax highlighting +- **Shell**: Zsh (primary, oh-my-zsh + mcfly) with a minimal Bash fallback (zoxide + mcfly) - **Version Control**: Git, GitHub CLI, Lazygit - **CLI Tools**: ripgrep, fd, bat, fzf, jq, and more - **Rust Toolchain**: rustc, Cargo, Clippy, and rustfmt (managed by Nix) @@ -105,6 +105,7 @@ Before activation, customize `home.username` and `home.homeDirectory` in `home/d ### Configuration Management - **Modular**: Shared modules + platform-specific overrides - **Symlinked**: Your existing dotfiles are symlinked (edit directly, no rebuild needed) +- **Stow-compatible**: each top-level config directory is also a GNU Stow package, so the same repo works on non-Nix machines (`stow bash zsh nvim`) - **Reproducible**: Same environment across all machines - **Declarative**: Everything in version control @@ -117,7 +118,7 @@ dotfiles/ ├── home/ # Home Manager configurations │ ├── modules/ # Shared modules │ │ ├── common.nix # Base settings -│ │ ├── shell.nix # Zsh configuration +│ │ ├── shell.nix # Zsh + Bash configuration │ │ ├── dev-tools.nix # Development packages │ │ ├── neovim.nix # Neovim + LSPs │ │ └── terminal.nix # Herdr, Wezterm, Yazi @@ -140,7 +141,8 @@ dotfiles/ │ ├── herdr/.config/herdr/ │ ├── wezterm/.config/wezterm/ │ ├── yazi/.config/yazi/ -│ └── zsh/ +│ ├── zsh/ +│ └── bash/ # Minimal fallback shell │ └── docs/ ├── DEPLOYMENT.md # Detailed deployment guide @@ -248,7 +250,7 @@ home-manager switch --rollback **Current approach**: Manual management for simplicity - Generate SSH keys manually on each machine: `ssh-keygen -t ed25519` - Keep sensitive credentials outside version control -- Use local overrides (`.zshenv.local` pattern) for machine-specific secrets +- Use local overrides (`.zshenv.local` / `.bashrc.local` pattern) for machine-specific secrets ### Development Shells Create project-specific environments: diff --git a/flake.nix b/flake.nix index 185d7e4..e9d8e57 100644 --- a/flake.nix +++ b/flake.nix @@ -339,6 +339,17 @@ zsh -n ${self}/zsh/.zshrc touch "$out" ''; + + bash-syntax = + pkgs.runCommand "bash-syntax" + { + nativeBuildInputs = [ pkgs.bash ]; + } + '' + bash -n ${self}/bash/.bash_profile + bash -n ${self}/bash/.bashrc + touch "$out" + ''; } ); diff --git a/home/modules/common.nix b/home/modules/common.nix index cc1e3b2..bbac733 100644 --- a/home/modules/common.nix +++ b/home/modules/common.nix @@ -25,6 +25,5 @@ # Misc utilities wget curl - mcfly ]; } diff --git a/home/modules/shell.nix b/home/modules/shell.nix index 6a1525d..ffd901f 100644 --- a/home/modules/shell.nix +++ b/home/modules/shell.nix @@ -2,10 +2,17 @@ { home = { - # Install oh-my-zsh package (not managed by Home Manager's programs.zsh.oh-my-zsh) - # This allows your .zshrc to have full control packages = with pkgs; [ + # Install oh-my-zsh package (not managed by Home Manager's programs.zsh.oh-my-zsh) + # This allows your .zshrc to have full control oh-my-zsh + + # Completions for the bash fallback (programs.bash is off, see below) + bash-completion + + # Shell integrations wired up in zsh/.zshrc and bash/.bashrc + mcfly + zoxide ]; file = { @@ -19,12 +26,14 @@ # Symlink .zshenv ".zshenv".source = config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/dotfiles/zsh/.zshenv"; - }; - }; - # Bash configuration (fallback) - programs.bash = { - enable = true; - enableCompletion = true; + # Bash fallback. programs.bash stays disabled so these stay plain symlinks + # into the repo, which keeps them usable via GNU Stow on non-Nix machines. + ".bashrc".source = + config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/dotfiles/bash/.bashrc"; + + ".bash_profile".source = + config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/dotfiles/bash/.bash_profile"; + }; }; } diff --git a/zsh/.zshenv b/zsh/.zshenv index 9284849..de855d3 100755 --- a/zsh/.zshenv +++ b/zsh/.zshenv @@ -6,8 +6,12 @@ export PATH="$HOME/go/bin:$PATH" export PATH="$HOME/.nix-profile/bin:$PATH" export CLOUDSDK_PYTHON=/usr/bin/python3 -# GPG TTY for commit signing -export GPG_TTY=$(tty) +# GPG TTY for commit signing. `tty` writes "not a tty" to stdout when stdin is +# not a terminal, so assign only on success and keep any inherited value. +if __tty=$(tty 2>/dev/null); then + export GPG_TTY=$__tty +fi +unset __tty # General aliases alias v="nvim"