ADR-012 — Terminal Maturity on macOS
Date: 2026-04-15
Status: Research / Proposed
Context: Current setup is a near-empty .zshrc, iTerm2, and basic SSH config. Goal is to mature the terminal experience with privacy, productivity, and homelab management in mind.
1. AI Interaction: VS Code vs Terminal
| Aspect | VS Code (Claude Code extension) | Terminal (claude CLI) |
|---|---|---|
| Context | Sees open files, selections, workspace | Sees only what you pipe or describe |
| Editing | Inline diffs, click-to-apply | Writes files directly |
| Multitasking | Side-by-side with code | Full-screen focus, tmux-friendly |
| Homelab ops | Awkward for SSH/infra tasks | Natural for SSH, ansible, scripts |
Verdict: Use both. VS Code for code-heavy work (IaC, playbooks). Terminal for infra ops, debugging, SSH sessions. They complement each other; the CLI is better when you're already in a terminal flow.
2. How People Use Their Terminal Effectively
Core Productivity Stack (install all of these)
| Tool | What it does | Install |
|---|---|---|
| Starship | Fast, informative prompt (git branch, k8s context, etc.) | brew install starship |
| fzf | Fuzzy finder for files, history, everything | brew install fzf |
| zoxide | Smart cd - learns your directories, z proj jumps there |
brew install zoxide |
| bat | cat with syntax highlighting and line numbers |
brew install bat |
| eza | Modern ls with git status, icons, tree view |
brew install eza |
| fd | Fast find replacement |
brew install fd |
| ripgrep | Fast grep replacement (already used by Claude Code) |
brew install ripgrep |
| atuin | Shell history stored in SQLite, full-text search, sync across machines | brew install atuin |
| tldr | Simplified man pages with practical examples | brew install tldr |
Homelab-Specific Tools
| Tool | What it does | Install |
|---|---|---|
| pvetui | TUI for Proxmox management (multi-cluster, SSH integration) | Go binary from GitHub |
| lazydocker | TUI for Docker container management over SSH | brew install lazydocker |
| sshtmux | SSH terminal manager with tmux integration | pip install sshtmux |
| k9s | TUI for Kubernetes (if you ever move to k8s) | brew install k9s |
Workflow Patterns
- tmux/zellij sessions: persistent terminal sessions that survive disconnects. Name sessions per-task (
tmux new -s vault-debug). - Aliases for common ops:
alias lxc='ssh [email protected] pct',alias logs='ssh [email protected] journalctl'. - fzf + SSH: fuzzy-select which host to SSH into from your config.
- Shell scripts in
~/bin/: you already do this (backup-to-cajita.sh, mount scripts). Keep going.
3. Self-Hosted Terminal Solutions
For accessing your terminal from anywhere (browser-based):
| Solution | Notes |
|---|---|
| Termix | Best option. Self-hosted, open-source, Docker deploy. SSH terminal + tunneling + file editing + system monitoring. Split-screen up to 4 terminals. |
| Nexterm | SSH + VNC + RDP in one interface. Good for mixed protocol homelab. Open-source. |
| WebSSH (bifrost0x) | Lightweight web-based SSH with SFTP file manager. Single Docker container. |
| Sshwifty | Minimal web SSH/Telnet client. Very lightweight. |
| ttyd | Share any CLI tool as a web page. Dead simple. |
Recommendation: Termix is the most feature-complete for a homelab. Deploy it on an LXC behind Caddy + NetBird VPN for secure remote access without exposing SSH to the internet.
Note: You already have NetBird VPN, so you can access any self-hosted terminal securely from anywhere without Cloudflare tunnels.
4. Leveraging .zshrc (and Related Config)
Your current .zshrc is essentially empty. Here's a structured approach:
Recommended .zshrc Structure
# --- Package manager ---
eval "$(/opt/homebrew/bin/brew shellenv)"
# --- Prompt ---
eval "$(starship init zsh)"
# --- Smart tools ---
eval "$(zoxide init zsh)" # z <partial-dir>
eval "$(fzf --zsh)" # Ctrl+R history, Ctrl+T files
eval "$(atuin init zsh)" # better shell history
# --- Aliases: general ---
alias ls='eza --icons --group-directories-first'
alias ll='eza -la --icons --group-directories-first'
alias cat='bat --paging=never'
alias tree='eza --tree --icons'
alias grep='rg'
alias find='fd'
# --- Aliases: homelab ---
alias chizuru='ssh [email protected]'
alias cajita='ssh [email protected]'
alias lxcs='ssh [email protected] pct list'
alias vsh='vault-ssh' # custom function
# --- Aliases: git ---
alias gs='git status'
alias gd='git diff'
alias gl='git log --oneline -20'
alias gp='git push'
# --- Functions ---
# SSH into any LXC by ID
lxc-ssh() {
ssh [email protected] "pct exec $1 -- bash"
}
# Quick Vault read
vault-read() {
VAULT_ADDR=https://vault.eva-00.network vault kv get "$1"
}
# --- Zsh plugins (via Zinit or manual) ---
# zsh-autosuggestions: suggests commands as you type (grey text)
# zsh-syntax-highlighting: colors valid/invalid commands
# fzf-tab: replaces default tab completion with fzf
# --- PATH ---
export PATH="$HOME/bin:$PATH"
export PATH="$PATH:$HOME/.rvm/bin"
export PATH="/opt/homebrew/opt/ruby/bin:$PATH"
Plugin Management
Skip Oh My Zsh (bloated, slow). Use Zinit to cherry-pick just the plugins you want:
# Install: bash -c "$(curl --fail --show-error --silent --location https://raw.githubusercontent.com/zdharma-continuum/zinit/HEAD/scripts/install.sh)"
zinit light zsh-users/zsh-autosuggestions
zinit light zsh-users/zsh-syntax-highlighting
zinit light Aloxaf/fzf-tab
File Organization
| File | Purpose |
|---|---|
~/.zshrc |
Main config (prompt, tools, plugins) |
~/.zshenv |
Environment variables (PATH, EDITOR, etc.) |
~/.config/starship.toml |
Prompt customization |
~/.config/ghostty/config |
Terminal emulator config |
~/bin/ |
Custom scripts (already using this) |
~/.ssh/config |
SSH host definitions (see section 5) |
5. SSH Config Keywords
Your current SSH config is minimal. Here's how to level it up:
Expanded ~/.ssh/config
# --- Global defaults ---
Host *
AddKeysToAgent yes
UseKeyChain yes
IdentityFile ~/.ssh/id_ed25519_github
ServerAliveInterval 60
ServerAliveCountMax 3
Compression yes
# --- Forgejo ---
Host forgejo
HostName code.eva-00.network
User git
IdentityFile ~/.ssh/id_ed25519_forgejo
# --- Proxmox host ---
Host chizuru
HostName 192.168.1.125
User root
# --- PBS ---
Host cajita
HostName 192.168.1.196
User root
# --- LXC pattern: ssh lxc-100, lxc-107, etc. ---
# (requires ProxyJump through Proxmox host)
Host lxc-*
User root
ProxyJump chizuru
# Proxmox pct exec handles the actual LXC connection
# Alternative: if LXCs have their own IPs, map them directly
# --- Homelab wildcard ---
Host 192.168.1.*
User root
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
LogLevel ERROR
Key SSH Keywords
| Keyword | What it does |
|---|---|
Host |
Alias name (e.g., ssh chizuru instead of ssh [email protected]) |
HostName |
Real IP/hostname |
ProxyJump |
Bounce through a bastion host (e.g., ProxyJump chizuru to reach internal hosts) |
LocalForward |
Port forwarding (e.g., access a service on LXC via localhost:8080) |
IdentityFile |
Per-host SSH key |
ServerAliveInterval |
Keep connections alive (critical for homelab SSH sessions) |
Match |
Conditional config (e.g., different settings when on VPN vs local) |
6. Best Terminal Emulator for macOS
Comparison
| Terminal | GPU Accel | Native macOS | Open Source | Privacy | Config Format | Tabs/Splits |
|---|---|---|---|---|---|---|
| Ghostty | Yes (Metal) | Yes (Swift/AppKit) | Yes (MIT) | No telemetry | key=value | Native |
| iTerm2 | No | Yes | Yes (GPLv2) | No telemetry | GUI + plist | Yes |
| WezTerm | Yes | Cross-platform | Yes (MIT) | No telemetry | Lua | Built-in mux |
| Kitty | Yes | Cross-platform | Yes (GPLv3) | No telemetry | key=value | Yes (kittens) |
| Alacritty | Yes | Cross-platform | Yes (Apache) | No telemetry | TOML | No (use tmux) |
| Warp | Yes | Yes | No | Account required, telemetry | GUI | Yes |
Recommendation: Ghostty
Why Ghostty over iTerm2 (your current terminal):
- GPU-accelerated via Metal (smooth 120Hz on ProMotion displays)
- Feels truly native macOS (Swift/AppKit, not Electron)
- Fast startup, low memory
- Simple ~/.config/ghostty/config file (version-controllable)
- Built by Mitchell Hashimoto (HashiCorp founder) - actively maintained
- 1M+ downloads/week as of 2026
- Zero telemetry, no account, no subscription
Ghostty is the closest thing to Warp's polish without any of the privacy concerns. It won't have Warp's AI features built-in, but you already have Claude Code for that.
Install: brew install --cask ghostty
Basic Ghostty Config (~/.config/ghostty/config)
font-family = JetBrains Mono
font-size = 14
theme = catppuccin-mocha
window-padding-x = 8
window-padding-y = 8
cursor-style = bar
shell-integration = zsh
7. File Sync: rsync, rclone, and the Ecosystem
rsync is powerful but the flag soup (-avzhP --delete --exclude ...) is hard to remember. There's no killer TUI/wrapper the community has rallied around — the ecosystem is fragmented. Here's the real landscape and what to actually use.
The state of rsync extensibility (as of 2026)
rsync itself hasn't meaningfully evolved its UX in decades. It's single-threaded, flag-heavy, and there's no built-in profile/task system. The community response:
| Tool | What it is | Status |
|---|---|---|
| rclone | Modern rsync alternative with parallelism, web GUI, 70+ backends | Actively maintained, widely adopted |
| RsyncUI | Native macOS SwiftUI GUI for rsync with saved profiles | Actively maintained |
| rsyncy | Progress bar wrapper for rsync | Small but maintained |
| tui-rsync | Python TUI for rsync profiles | Barely maintained |
| backupmenu | Bash menu for tar/rsync backups | Basic, Linux-only |
| Timeshift | Linux snapshot tool (rsync + hardlinks under the hood) | Linux-only, proves rsync needs a wrapper |
The gap: There is no good terminal-native rsync profile manager or TUI. This is a genuine hole in the ecosystem.
Use the right tool for each job
| Scenario | Best tool | Why |
|---|---|---|
| Ad-hoc local/external drive sync | Shell functions (below) or rclone | Don't memorize flags |
| Many files over fast network (LAN/10Gbps) | rclone | 4x faster than rsync — parallel transfers saturate bandwidth |
| Large files that change slightly | rsync | Delta transfer — only sends changed bytes, rclone sends whole files |
| Recurring sync profiles (click-to-run) | RsyncUI | Saved tasks with GUI |
| Scripted server backups | rsync (raw) | Your senku script is correct — delta-based, hardlink-friendly |
| Cloud storage sync | rclone | 70+ backends (S3, GDrive, etc.), rsync can't do this |
| Bidirectional sync | rclone bisync | rsync is one-way only |
rclone on macOS
rclone works natively on macOS. Install and use:
brew install rclone
Why rclone over rsync for local/external drive transfers:
- Parallel transfers: --transfers=16 processes 16 files at once (rsync does 1 at a time)
- On Jeff Geerling's 10Gbps LAN benchmark: rsync = 128 MB/s, rclone = 1 GB/s (4x faster)
- Built-in web GUI: rclone rcd --rc-web-gui — browser-based file manager
- Progress bars built-in (no wrapper needed)
- Same syntax for local, remote, and cloud transfers
The tradeoff: rclone transfers whole files, not deltas. For a 10GB VM image where 1MB changed, rsync sends 1MB while rclone re-sends 10GB. For many small/medium files (photos, documents, music), rclone wins.
Common rclone commands:
# Copy with progress (like rsync -avhP)
rclone copy ~/Photos /Volumes/USB/Photos --progress --transfers=16
# Sync (mirror, like rsync --delete)
rclone sync ~/Music /Volumes/Backup/Music --progress --transfers=16
# Dry run first (like rsync -n)
rclone sync ~/Music /Volumes/Backup/Music --dry-run
# Bidirectional sync (rsync can't do this)
rclone bisync ~/Documents /Volumes/USB/Documents --progress
# Web GUI — browse and manage transfers visually
rclone rcd --rc-web-gui
Shell functions for when you do use rsync
For delta-heavy or server-to-server transfers where rsync is still better:
# --- rsync helpers (no flags to remember) ---
# Copy files with progress (the one you'll use most)
# Usage: rscopy ~/Documents/taxes /Volumes/USB-Drive/
rscopy() {
rsync -avhP "$1" "$2"
}
# Mirror a folder exactly (deletes files on dest that aren't on source)
# Usage: rsmirror ~/Music /Volumes/Backup/Music
rsmirror() {
echo "DRY RUN first — showing what would change:"
rsync -avhn --delete "$1/" "$2/"
echo ""
read -q "REPLY?Proceed? (y/n) " && echo && rsync -avh --delete --info=progress2 "$1/" "$2/"
}
# Sync to/from a homelab host
# Usage: push chizuru ~/scripts /opt/scripts
push() { rsync -avhP "$2" "root@$1:$3"; }
# Usage: pull chizuru /var/log/syslog ~/Desktop/
pull() { rsync -avhP "root@$1:$2" "$3"; }
# Resume a large interrupted transfer
# Usage: rsresume ~/big-folder /Volumes/External/big-folder
rsresume() {
rsync -avhP --partial --append-verify "$1" "$2"
}
Key design decisions:
- rsmirror always does a dry-run first and asks for confirmation (because --delete is destructive)
- rscopy uses -P (progress + partial resume) so you can Ctrl+C and re-run without starting over
- push/pull use your SSH config aliases so push chizuru just works
- Trailing slash handling: rsmirror adds / to source automatically so it mirrors contents, not the folder itself
- Functions prefixed with rs to avoid collisions with system commands
fzf-powered interactive picker
For when you want to pick what to sync interactively (works with either rsync or rclone):
# Interactive sync — fuzzy-pick source and destination
# Usage: rspick (then pick from mounted volumes and common dirs)
rspick() {
local dirs=()
# Add mounted volumes (external drives, NFS, SMB)
for vol in /Volumes/*(N); do
[[ "$vol" != "/Volumes/Macintosh HD" ]] && dirs+=("$vol")
done
# Add common directories
dirs+=("$HOME/Documents" "$HOME/Desktop" "$HOME/Downloads" "$HOME/git" "$HOME/Music" "$HOME/Pictures")
echo "Select SOURCE:"
local src=$(printf '%s\n' "${dirs[@]}" | fzf --prompt="Source > ")
[[ -z "$src" ]] && return 1
echo "Select DESTINATION:"
local dst=$(printf '%s\n' "${dirs[@]}" | fzf --prompt="Dest > ")
[[ -z "$dst" ]] && return 1
echo ""
echo " From: $src"
echo " To: $dst"
echo ""
# Use rclone for local-to-local (faster), rsync for remote
if [[ "$src" == /* && "$dst" == /* ]]; then
echo "Using rclone (parallel, faster for local transfers):"
rclone sync "$src/" "$dst/" --dry-run --progress
echo ""
read -q "REPLY?Proceed? (y/n) " && echo && rclone sync "$src/" "$dst/" --progress --transfers=16
else
echo "DRY RUN (rsync):"
rsync -avhn "$src/" "$dst/"
echo ""
read -q "REPLY?Proceed? (y/n) " && echo && rsync -avh --info=progress2 "$src/" "$dst/"
fi
}
RsyncUI for saved profiles
If you have recurring syncs (e.g., "Photos to USB every week"):
brew install --cask rsyncui
Native macOS SwiftUI app. Save named profiles, run them with a click. Free, open-source, no telemetry. Good complement to the shell functions — not a replacement.
Quick reference card
| I want to... | Command |
|---|---|
| Copy many files fast (local/external) | rclone copy ~/src /dest -P --transfers=16 |
| Mirror folder exactly (local) | rclone sync ~/src /dest -P --transfers=16 |
| Bidirectional sync | rclone bisync ~/src /dest -P |
| Web GUI for transfers | rclone rcd --rc-web-gui |
| Copy with delta (large files, remote) | rscopy ~/src host:/dest |
| Mirror with safety check (rsync) | rsmirror ~/src /dest |
| Push files to homelab host | push chizuru ~/file /remote/path |
| Pull files from homelab host | pull chizuru /remote/path ~/local/ |
| Resume interrupted transfer | rsresume ~/big-folder /dest |
| Interactive pick source/dest | rspick |
| Recurring sync profiles (GUI) | RsyncUI app |
Decision
Phase 1 - Immediate (low effort, high impact)
- Install Ghostty, replace iTerm2 as daily driver
- Install core CLI tools:
brew install starship fzf zoxide bat eza fd ripgrep atuin tldr rclone - Set up
.zshrcwith the structure above - Expand
~/.ssh/configwith host aliases
Phase 2 - Short term
- Install zsh plugins via Zinit (autosuggestions, syntax-highlighting, fzf-tab)
- Install homelab tools:
lazydocker,pvetui - Customize Starship prompt for homelab context
Phase 3 - Optional
- Deploy Termix on an LXC for browser-based SSH access
- Set up Atuin sync across machines
- Dotfiles repo to version-control all config
Sources
- Best Terminal Emulators for Developers 2026
- Ghostty Review 2026
- Ghostty vs WezTerm 2026
- Best Ghostty Config for macOS 2026
- Modern Zsh Setup Without Oh My Zsh
- Elevate Your Terminal: From OMZ to Starship 2026
- Modern Linux Shell Toolkit
- Turbocharge Mac Terminal: Starship + Ghostty
- pvetui: TUI for Proxmox
- Termix: Self-Hosted SSH Platform
- Nexterm: Self-Hosted Web Terminal
- SSH Config Complete Guide 2026
- SSH ProxyJump Tutorial
- 10 Best Open Source Warp Alternatives
- 10 Best Terminal Tools for Home Labs
- RsyncUI - macOS GUI for rsync
- rsyncy - progress bar for rsync
- Fuzzy search and download with rsync + fzf
- rsync on Mac for external drives
- 4x faster sync with rclone vs rsync — Jeff Geerling
- rclone vs rsync comparison
- rclone bisync documentation
- 5 advanced rsync tips — Red Hat
- Advanced rsync for large backups