---
date: 2024-03-27
title: Shell Customizations and SSH
tags: 
  - homelab
  - linux
  - bash
  - development
---

Taking a detour from actual deployments, I recently started experimenting with different shell customizations.
Until now, I've just used whatever default shell I have available on a given system, but a recent live stream
from [Lawrence Systems](https://www.youtube.com/@LAWRENCESYSTEMS) mentioned shell customizations and tmux and
lead me down this rabbithole. I'll also mention SSH configuration as its something that greatly helps my daily
productivity and is tangentially related to some of my customizations.

## tmux
I only recently tried using tmux and I don't know why I went so long without it! The 
[tmux wiki](https://github.com/tmux/tmux/wiki) offers a complete explanation of what tmux is and all of the 
things it can do; in short, it manages processes and terminals. `tmux` has a pretty steep learning curve, but
it only took me a day to get the hang of a few basic shortcuts; `ctrl`+`b`, or the "prefix key" enables 
interacting with `tmux` and then a command can be issued. Some of the commands I find most useful are:
- `?` - Shows a list of tmux key bindings
- `"` - Splits the window vertically and adds a pane
- `up`/`down` - Navigates between vertical panes; if you hold `ctrl`+`b` then it moves the split up and down
- `d` - Detaches the temux session; easy to remember since `ctrl`+`d` is how you detach an SSH session

I'm not (yet) using tmux by default for local sessions, but I do have it enabled to run when I SSH into some
systems; this is the primary reason I really like `tmux`. I can ssh into my server from my laptop and start a
 command (i.e.a very long rsync process) without worrying about disowning the process before my laptop goes
 to sleep; when I ssh back in, perhaps from another computer, I have the same terminal history and the same
 process attached. I will go into more detail in the "ssh" section of this post, but all I had to do is run 
 `tmux new -A -s ssh_tmux` when I connect to attach a tmux session named `ssh_tmux`, creating it if it doesn't 
 exist.

## ssh
For SSH keys, I have one key pair I use for personal systems and another for work. I won't go into detail about
key management or key rotation and instead will focus on SSH configuration. My SSH config simply contains global
config options and includes other configuration files for specific hosts. I find this makes it easier to manage
hosts since I can group hosts in different files. My `~/.ssh/config` looks like:
```text
Include config.d/*
Host *
	AddKeysToAgent yes
	IdentitiesOnly yes
```
- `AddKeysToAgent` adds keys to my ssh agent so I don't need to enter a passcode after the first time I use a key
- `IdentitiesOnly` prevents SSH from trying to infer an identity file to use if it isn't specified in SSH config
  or explicitly supplied as an argument.

> You can find descriptions of all the SSH config options in the [BSD manpages](https://man.openbsd.org/ssh_config).

A couple example hosts look like:
```text
Host work.server
	HostName <server IP Address>
	User <remote username
	IdentityFile ~/.ssh/id_rsa_work
	RemoteCommand tmux new -A -s ssh_tmux
 	RequestTTY yes
...
Host mcknight.unraid
	HostName 192.168.1.100
	User root
	IdentityFile ~/.ssh/id_rsa_home
	RemoteCommand tmux new -A -s ssh_tmux
```
- `RemoteCommand` is executed on the remote system upon SSH connection. This can cause some odd quirks since the
  command is executed immediately upon connection. I only ran into trouble if I needed to change a password upon
  connection (unlikely situation if you're connecting with SSH keys) and with rsync (more on that below). This 
  can be overridden by connecting with `ssh mcknight.unraid -o RemoteCommand=None`.
- `RequestTTY` I found was required on one Ubuntu Server in order for tmux to start properly. I don't know exactly
  why this is necessary, but it solved a problem.


To work around rsync issues, I aliased the command in my `.bashrc`: `alias rsync="rsync -e 'ssh -o RemoteCommand=none'"`.
This might cause some problems with fully local transfers, but I don't alias this on servers where I would move
enough data around to necessitate using rsync.

## BASH Configuration
I use `bash` primarily since its available in pretty much every Linux distro, Windows, and MacOS (not that I use
MacOS). I mostly use a standard `.bashrc` that ships with Mint, but I recently updated the PS1 and PS2 variables
using [Tom from Lawrence Systems'](https://github.com/lawrencesystems/dotfiles/blob/master/.bash_profile) as
inspiration.

My shell prompt looks like:
<pre><font color="#9E7199">┌──[</font><font color="#06989A">22:27</font><font color="#9E7199">]─[</font><font color="#06989A">d_mcknight</font><font color="#9E7199">@</font><font color="#06989A">MCKNIGHT-FW13</font><font color="#9E7199">]─(</font><font color="#1D6CBF">~</font><font color="#9E7199">)</font>
<font color="#9E7199">└</font><font color="#06989A">$</font> 
</pre>

And a snippet from .bashrc related to that:
```shell
    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 "⚠️";fi'
    PS1="$chrome_color┌──[${context_color}\A${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 '

    export VIRTUAL_ENV_DISABLE_PROMPT=1
```
There's a lot going on in the prompt, but I will highlight pieces of it; keep in mind that the snippets below may 
not be valid on their own and that some of the color formatting may be lost as I cut up the long prompt string.

- `PROMPT_COMMAND='if [[ $? != 0 && $? != 130 ]];then echo "⚠️";fi'` isn't part of the prompt string, but rather an 
expression evaluated before the prompt string. This renders a ⚠️ if the previous command fails and isn't just an
empty command.

- `[${context_color}\A${chrome_color}]` prints the current time in 24H format. Helpful if you open a terminal and
want to quickly reference when you ran the last command and when it completed.

- `${debian_chroot:+('${path_color}'$debian_chroot'${chrome_color}')─}`
and `${VIRTUAL_ENV:+('${path_color}'$(realpath $VIRTUAL_ENV --relative-to $PWD --relative-base /home'${chrome_color}')─})`
print an active chroot or Python venv path, if active. I print the venv path relative to the current directory 
if within `home`; I find this helpful when I have multiple projects to make sure I know which one I'm looking at.
Note that I also specify `export VIRTUAL_ENV_DISABLE_PROMPT=1` to suppress the default `(venv)` prompt prefix.

- `[${context_color}\u${chrome_color}${prompt_symbol}${context_color}\h${chrome_color}]` looks like the default shell, 
`user@hostname` except when root the `@` is replaced with `💀` as an extra visual reminder that its a root shell.

- `(${path_color}\w${chrome_color})` shows the current shell path like the default shell

- `\n${chrome_color}└${context_color}${prompt}${color_off} ` continues to the next line and inserts the `$` or `#`
prompt, followed by a space.

- `PS2=$chrome_color'└>$color_off '` updates the prefix used when there's a newline in a command. I like that this
makes each line start vertically aligned and it keeps the chrome colored differently from the inputs.

## Further Reading
SSH, BASH, and tmux are all over a decade old with plenty of good documentation and write-ups that I've referenced.
My current `.bashrc` is in [this gist](https://gist.github.com/d-mcknight/176899ca924b5b4cfdf7692e36ca568e) and 
I will try to keep that updated as I make changes; maybe one day I'll promote it to an actual repository with other
config files.
