diff --git a/2025-06-04_zsh-gnu-stow.md b/2025-06-04_zsh-gnu-stow.md deleted file mode 100644 index 2e431c7..0000000 --- a/2025-06-04_zsh-gnu-stow.md +++ /dev/null @@ -1,401 +0,0 @@ ---- - -date: 2025-06-04 -title: zsh and GNU stow -tags: - -- software -- linux -- development - ---- - -In [my last post exploring `nvim`](https://blog.mcknight.tech/2025/05/21/nvim/), I mentioned some potential next projects on my agenda. -Well, I wasted no time continuing down the path of trying to perfect my shell experience. I very quickly updated my -[dotfiles repository](https://forge.mcknight.tech/d_mcknight/dotfiles) to be compatible with GNU `stow` and then went on to work on my -`.zshrc` file. Neither of these are major projects, so I figured they can share this one post. - -## GNU stow - -There are a few ways to use GNU stow and I always recommend people to -[RTFM](https://www.gnu.org/software/stow/manual/stow.html#Invoking-Stow) if you ever run into problems or have questions about -CLI arguments. Alternatively, this package is old and stable enough that Claude or ChatGPT can easily answer any questions you -may have. For my use, I am [already keeping my dotfiles](https://blog.mcknight.tech/2024/06/21/Dotfiles/) in `~/.dotfiles`, so -it makes sense for me to make that repository look like my home directory. By default, `stow` will apply the contents of -my stow directory (`~/.dotfiels`) to its parent directory (`~/`). - -You can see my [dotfiles repository](https://forge.mcknight.tech/d_mcknight/dotfiles/src/commit/e23e66f801b0549d6195d7115ed6f033ed4318e6) -now contains the same files it did before, but they are organized as if the repository root is my Home directory. One potential downside -of this organization is that it adds complexity if I wanted to apply only specific dotfiles or directories, but I can't think of anything -here that I would want to selectively apply to any environments. It is also worth noting that I had to remove any existing files or -symlinks before running `stow .` from my `~/.dotfiles` directory, otherwise `stow` would refuse to overwrite existing files. There may be -an argument to force overwrite destination files, but I prefer to manually delete things, just to make sure I'm not deleting/overwriting -something I want to keep. - -### Some dotfiles updates - -There are a few updates to my dotfiles that I never documented in my -[original dotfiles post](https://blog.mcknight.tech/2024/06/21/Dotfiles/), or [Neovim post](https://blog.mcknight.tech/2025/05/21/nvim/). -This may not be exhaustive, but here are some of the highlights: - -#### `alacritty.toml` - -I have been using [Alacritty](https://alacritty.org/) for my regular terminal emulator, -so I have [some customizations](https://forge.mcknight.tech/d_mcknight/dotfiles/src/commit/e23e66f801b0549d6195d7115ed6f033ed4318e6/.config/alacritty/alacritty.toml) -I like to apply. Below is my configuration with annotations explaining everything. - -```toml -[general] -# Apply a GitHub dark theme for a consistent look -import = ["./github_dark_high_contrast.toml" ] - -[colors.primary] -# Override background to a neutral dark color -background = "#111111" - -[colors.normal] -# I picked this color when configuring tmux, override terminal text color to match -cyan = '#008b8b' - -[font.normal] -# Use a Nerd Font for extra symbols. I have this included in my `dotfiles` repository -family = 'JetBrainsMono Nerd Font Mono' -style = 'Regular' - -[cursor] -# I prefer a blinking input cursor -blink_interval = 500 -blink_timeout = 0 - -[cursor.style] -# I tried `Underline`, but I think I prefer the default block -blinking = "Always" -#shape = "Underline" - -[window] -# Make the window slightly transparent to peek at what's behind -opacity = 0.95 -# Hide the top bar because I never close the terminal and use gTile to position it on screen -decorations = "None" -# I have `level` set, but it doesn't appear to do anything in Cinnamon :/ -level = "AlwaysOnTop" -# dynamic_padding splits extra vertical/horizontal space which makes tmux and nvim status bars look a little nicer -dynamic_padding = true -``` - -#### JetBrains Nerd Font - -[Nerd Fonts](https://www.nerdfonts.com/font-downloads) let you get a consistent font -that includes extra characters like filetype icons, emojis, and ligatures (special -characters for things like `==`, `->`, and other character combinations). Since I -have this configured in my terminal config, it makes sense to make sure the font is -always available so I just include it in my dotfiles repository. - -## zsh - -[`zsh`](https://www.zsh.org/) is a shell, like [`bash`](https://www.gnu.org/software/bash/), -but with some different features that are interesting. It's worth nothing that I fully intend on using `bash` -for scripting since it is far more ubiquitous than `zsh` and I am more familiar with it and its quirks. -I will also note that zsh is NOT a [POSIX shell](https://en.wikipedia.org/wiki/POSIX); -this is a common complaint that I see. Personally, I think I am okay with this since I -haven't run into any issues thus far and `zsh` is good enough to be the default shell in popular -operating systems, including macOS and TrueNAS. - -There are a couple reasons I decided to try `zsh`, the first being tab completion which -I find helpful when completing a path or command where there are only a couple options to tab through. -The other reasons I wanted to experiment with `zsh` are a bit more complex. - -### RC Files - -[Run Commands files](https://en.wikipedia.org/wiki/RUNCOM) are basically files that -are executed when a program starts. I -[previously detailed my .bashrc file](https://blog.mcknight.tech/2024/03/27/Shell-Customizations/#BASH-Configuration), -which is executed whenever I open a new `bash` shell. -I wanted to experiment with `zsh` configuration because it feels a little more modern -and powerful to me compared to `bash`. For example, my `bash` shell prompt looks like: - -```bash - color_off="\[\033[0m\]" # Text Reset - - # Regular Colors - black="\[\033[0;30m\]" - red="\[\033[0;31m\]" - green="\[\033[0;32m\]" - yellow="\[\033[0;33m\]" - blue="\[\033[0;34m\]" - purple="\[\033[0;35m\]" - cyan="\[\033[0;36m\]" - white="\[\033[0;37m\]" - - path_color=$blue - chrome_color=$purple - context_color=$cyan - prompt_symbol=@ # 🚀💲 - prompt='\$' - if [ "$EUID" -eq 0 ]; then # Change prompt colors for root user - context_color=$red - prompt_symbol=💀 - fi - PROMPT_COMMAND='if [[ $? != 0 && $? != 130 ]];then echo -e "⚠️ \a";else echo -e "\a";fi' - PS1="$chrome_color┌──"'${debian_chroot:+('${path_color}'$debian_chroot'${chrome_color}')─}${VIRTUAL_ENV:+('${path_color}'$(realpath $VIRTUAL_ENV --relative-to $PWD --relative-base /home)'${chrome_color}')─}'"[${context_color}\u${chrome_color}${prompt_symbol}${context_color}\h${chrome_color}]─(${path_color}\w${chrome_color})\n${chrome_color}└${context_color}${prompt}${color_off} " - PS2="$chrome_color└>$color_off " -``` - -and in zsh: - -```zsh -function precmd { - # Check previous command output and notify - if [[ $? != 0 && $? != 130 ]];then - echo -e "⚠️ \a" - else - echo -e "\a" - fi - - if [ "$EUID" -eq 0 ];then - chrome_color="{red}" - else - chrome_color="{magenta}" - fi - - # Static prefix - prefix="%F$chrome_color┌──[%F{cyan}%n%F$chrome_color@%F{cyan}%m%F$chrome_color]-" - - # Calculate extra path - if [[ ${debian_chroot} ]]; then - path_extra="(%F{red}${debian_chroot}%F$chrome_color)-" - elif [[ ${VIRTUAL_ENV} ]]; then - rel_venv=$(realpath $VIRTUAL_ENV --relative-to $PWD --relative-base /home) - path_extra="[%F{blue}${rel_venv}%F$chrome_color]-" - else; - path_extra="" - fi - - # Static suffix - suffix="(%F{blue}%~%F${chrome_color})"$'\n'"└%F{cyan}%#%F{white} " - - PROMPT=$prefix$path_extra$suffix - PS2="%F$chrome_color└%F{cyan}>%F{white} " -``` - -I find the `zsh` version to be much more readable and easier to modify since I have a -method to generate the prompt instead of a single variable to cram everything into. -I believe I have these two prompts looking identical in all cases and it only took me -about an hour to get my `zshrc` working identically to my `bashrc`; this included -moving some refactoring from `.bashrc` into `.bash_aliases` and `.profile`. I also -made sure `.profile` is always sourced in `bash` and `zsh` shells to avoid duplicating -code in rc files. I considered using a common `aliases` file, but decided against it since -I use different aliases for different shells (i.e. `sudosu` is shell-specific). - -### `zsh` Plugins - -Another interesting feature of `zsh` is that it supports plugins. -Now just like with Neovim, there are a number of different plugin managers that can be -used with `zsh`. I don't know if there is a "best" choice, but after some light reading -on the popular options I could fine and some [LLM summary comparisons](https://search.brave.com/search?q=zinit+vs+omz&source=desktop&summary=1&conversation=f5495011020a89faf13bf1), -I settled on [Zinit](https://github.com/zdharma-continuum/zinit) as a lightweight and -apparently maintained option. - -#### OMZ extract - -This convenience command lets me extract files without having to remember the syntax for -extracting `.tar.xz`, `.zip`, `.tar.gz`, etc. A simple `extract ` - -#### OMZ colored-man-pages - -This adds some color to man pages which I think makes it a little easier to skim to find -CLI args and section headers. Its not the *best* IMO, but something is better than nothing -here when trying to skim through what can be pretty dense documentation. - -#### OMZ encode64 - -It isn't every day that I need to get a b64-encoded representation of a string, but its -handy to be able to do so quickly and easily. - -#### OMZ pip - -I like having tab completion for pip. I haven't used it too much yet, but I already see -how this will save me from trying to `pip isntall` when I really mean `pip install`. I -do this more than I'd like to admit. Other than that, its nice to have reminders for the -less commonly used flags. - -#### OMZ sudo - -The Oh My Zsh sudo plugin adds a convenience keybind (`esc`+`esc`) to prepend `sudo` to -the current command or the previous command if the input is empty. I find this to be -convenient as it is fairly common to re-run the previous command with elevated privileges -or to prepend `sudo` if I forgot to start with that. - -#### zsh-autosuggestions - -This plugin works much like suggestions in an IDE, providing a suggested command completion -that can be filled in with a bound key (I am using `Shift`+`Tab`). -I find this mapping more convenient than the default `->`, since I can reach it without -moving my fingers from the home row and it is easy to remember `tab` and `shift`+`tab` -are both a kind of completion. - -#### zsh-syntax-highlighting - -This plugin highlights syntax as you type in a command. This clearly identifies unresolved -commands or files to help catch errors before trying to run an incomplete command. It also -helps to identify un-escaped characters in a quoted string. - -### `.zshrc` - -Now that I've explained it in parts, here's my `.zshrc` file in its entirety: - -``` -# Lines configured by zsh-newuser-install -HISTFILE=~/.histfile -HISTSIZE=1000 -SAVEHIST=1000 -setopt autocd notify -unsetopt beep -bindkey -v -# End of lines configured by zsh-newuser-install - -# SSH completion -zstyle ':completion:*:(ssh|scp|ftp|sftp|rsync):*' hosts $hosts - -# The following lines were added by compinstall -zstyle :compinstall filename '/home/d_mcknight/.zshrc' - -autoload -Uz compinit -compinit -# End of lines added by compinstall - -# Source common envvars -[ -f ~/.profile ] && source ~/.profile - -# Prompt -function precmd { - # Check previous command output and notify - if [[ $? != 0 && $? != 130 ]];then - echo -e "⚠️ \a" - else - echo -e "\a" - fi - - if [ "$EUID" -eq 0 ];then - chrome_color="{red}" - else - chrome_color="{magenta}" - fi - - # Static prefix - prefix="%F$chrome_color┌──[%F{cyan}%n%F$chrome_color@%F{cyan}%m%F$chrome_color]-" - - # Calculate extra path - if [[ ${debian_chroot} ]]; then - path_extra="(%F{red}${debian_chroot}%F$chrome_color)-" - elif [[ ${VIRTUAL_ENV} ]]; then - rel_venv=$(realpath $VIRTUAL_ENV --relative-to $PWD --relative-base /home) - path_extra="[%F{blue}${rel_venv}%F$chrome_color]-" - else; - path_extra="" - fi - - # Static suffix - suffix="(%F{blue}%~%F${chrome_color})"$'\n'"└%F{cyan}%#%F{white} " - - PROMPT=$prefix$path_extra$suffix - PS2="%F$chrome_color└%F{cyan}>%F{white} " -} - -# Aliases -alias .=source -alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"' -alias k9=k9s -alias rsync="rsync -e 'ssh -o RemoteCommand=none'" -alias sudosu="sudo ZDOTDIR=$(dirname ${0:a}) zsh" -alias ll='ls -alFh' -alias ls="ls --color=auto" -alias lsl="ls --color=auto -lah" -alias ssh=ssh -# Use tmux for local unelevated shells -if [ -z "${SUDO_USER}" ] && [ -z "${SSH_CONNECTION}" ] && [ -z "${TERM_PROGRAM}" ]; then - tmux new -A -s local_tmux -fi - -# Define a function to use autossh with a custom bashrc -function assh() { - remote_file=$(mktemp) - if $(ssh "$@" "cat > ${remote_file}" < ~/.bashrc > /dev/null 2>&1); then - # Successfully copied bashrc to the remote. Source it upon ssh - autossh -t "$@" "bash --rcfile ${remote_file}; rm ${remote_file}" - else - # SSH Config specifies a RemoteCommand; connect normally - autossh "$@" - fi -} -# Use ssh completion for autossh -compdef autossh=ssh -compdef assh=ssh - -# Custom dircolors -[ -f ~/.dircolors ] && eval "$(dircolors ~/.dircolors)" - -# Kubernetes Completion -which kubectl 1> /dev/null && source <(kubectl completion zsh) -which helm 1> /dev/null && source <(helm completion zsh) - -# doctl completion -which doctl 1> /dev/null && source <(doctl completion zsh) - -# Start in the home directory -cd ~ - -# Ensure zinit is installed -ZINIT_HOME="${XDG_DATA_HOME:-${HOME}/.local/share}/zinit/zinit.git" -[ ! -d $ZINIT_HOME ] && mkdir -p "$(dirname $ZINIT_HOME)" -[ ! -d $ZINIT_HOME/.git ] && git clone https://github.com/zdharma-continuum/zinit.git "$ZINIT_HOME" -source "${ZINIT_HOME}/zinit.zsh" - -# Load zinit plugins -zinit snippet OMZP::extract -zinit snippet OMZP::colored-man-pages -zinit snippet OMZP::encode64 -zinit snippet OMZP::gh -zinit snippet OMZP::pip -zinit snippet OMZP::sudo - -zinit light zsh-users/zsh-autosuggestions -zinit light zsh-users/zsh-syntax-highlighting - -# Key bindings config -KEYTIMEOUT=5 -# ^[ for esc; ^I for tab -bindkey '^[[Z' autosuggest-accept -``` - -#### Some notes on `autossh` - -I have only been using `autossh` for a couple hours at this point, based on internet recommendations -that it will do better at resuming with `tmux` (via `tmux-resurrect`). Based on initial testing, it -appears to be working but I don't yet know if it is markedly better than plain `ssh`. - -Not necessarily related to `autossh`, the `assh` function I included allows for connecting to a -server and applying my `.bashrc` without making permanent changes to the remote server. This does -not apply to connections that use a `RemoteCommand` in the SSH config, which is intentional; I have -remotes that run a `tmux` session for remote connections and I wouldn't want to mess with shell -configurations when multiple connections will be attaching the same `tmux` session. - -### Conclusion - -I don't think my shell configuration will ever be "done", but I've reached a point where I'm -satisfied for now. GNU `stow` has simplified my dotfile management and made it easier to -manage more configurations as I add tools to my repertoire. I now have `zsh` looking like -my `bash` shell and all of my aliases and `PATH` management better organized to minimize -redundant code in shell-specific config files. I've enabled a few zsh plugins that create a -more pleasant shell experience with extra text highlighting and shortkeys. - -I have no immediate plans for what to work on next, though I -[still have some ideas](https://blog.mcknight.tech/2025/05/21/nvim/#What-to-do-next). I may continue my search for a good -visual file manager in the terminal, or try out Pop!_OS for its window tiling features, -although I might wait for their [Cosmic DE](https://system76.com/cosmic/) to graduate to -beta and try that. - -I also still have some [IDE exploration to do](https://blog.mcknight.tech/2025/05/18/Code-Server/#Future-Plans). -As I spend more time using `nvim`, I am starting to use it more for coding tasks and it may become my primary "IDE". -In any case, I am actively messing with my `nvim` configuration, so I probably have enough thoughts for another post -about that.