Auto-sync from catchthesethighs

This commit is contained in:
Aaron D. Lee
2025-12-16 08:53:28 -05:00
parent 212e94de76
commit 26894bc40b
8 changed files with 319 additions and 116 deletions

View File

@@ -5,6 +5,64 @@ All notable changes to this dotfiles project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.0] - 2025-12-16
### Added
#### Shell Startup Optimization
- **Deferred Loading** - Heavy plugins and functions load after first prompt displays
- **Cached Command Checks** - `_has_cmd()` function caches `command -v` results for faster lookups
- **Lazy kubectl** - kubectl completions only load when first used (saves ~200-500ms)
- **Background Tasks** - Dotfiles sync check runs fully async with `&!`
- **Compile Script** (`dotfiles-compile.sh`) - Pre-compile zsh files to `.zwc` bytecode for 20-50ms speedup
#### Smart Path Resolution
- **`_df_run()` Helper** - All aliases use full path with fallback to PATH
- Fixes "command not found" errors on fresh installs
- Scripts work even before symlinks are created or PATH is set
#### New Aliases
- `dfcompile` - Compile zsh files for faster startup
- `dfcd` - Navigate to dotfiles directory (replaces `df` to avoid disk utility conflict)
### Changed
#### Script Reorganization
- Renamed `bin/shell-stats.sh``bin/dotfiles-stats.sh`
- Renamed `bin/vault.sh``bin/dotfiles-vault.sh`
- Renamed `bin/update-dotfiles.sh``bin/dotfiles-update.sh`
- Moved `bin/setup-wizard.sh``setup/setup-wizard.sh`
- Moved `bin/setup-espanso.sh``setup/setup-espanso.sh`
- Removed deprecated `bin/deploy-zshtheme-systemwide.sh`
#### Alias System Overhaul
- All command aliases now use `_df_run()` function wrapper
- Uses full path `$_df_dir/bin/script.sh` with fallback to PATH lookup
- Better error messages when scripts not found
- Removed `df` alias (conflicts with disk free utility)
#### .zshrc Optimizations
- Reduced default plugins (removed `docker`, `docker-compose`, `kubectl` from immediate load)
- Disabled oh-my-zsh auto-update check on every load (`DISABLE_AUTO_UPDATE="true"`)
- Tool aliases (eza, bat) now set up in deferred loading
- FZF configuration deferred until after prompt
- Vault secrets loaded with full path to avoid command_not_found issues
- Background sync check uses full script path
### Fixed
- **Fresh Install Bug** - "Command not found: dotfiles-sync.sh" error on new user accounts
- **Path Resolution** - Scripts now work before `~/.local/bin` is in PATH
- **Smart Suggest Conflicts** - Background tasks no longer trigger `command_not_found_handler`
### Removed
- `df` alias (conflicted with `df` disk utility)
- Heavy plugins from default load (now lazy-loaded)
- `bin/deploy-zshtheme-systemwide.sh` (redundant with installer)
---
## [1.0.0] - 2025-12-15 ## [1.0.0] - 2025-12-15
### Added ### Added
@@ -14,8 +72,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Dynamic MOTD** (`motd.zsh`) - Compact system info on shell start (uptime, CPU, memory, docker, git status) - **Dynamic MOTD** (`motd.zsh`) - Compact system info on shell start (uptime, CPU, memory, docker, git status)
- **Command Palette** (`command-palette.zsh`) - Raycast-style fuzzy launcher triggered by Ctrl+Space or Ctrl+P - **Command Palette** (`command-palette.zsh`) - Raycast-style fuzzy launcher triggered by Ctrl+Space or Ctrl+P
- **Smart Suggestions** (`smart-suggest.zsh`) - Typo correction for 100+ common mistakes + alias recommendations - **Smart Suggestions** (`smart-suggest.zsh`) - Typo correction for 100+ common mistakes + alias recommendations
- **Shell Analytics** (`shell-stats.sh`) - Dashboard showing command usage, suggestions, and activity heatmap - **Shell Analytics** (`dotfiles-stats.sh`) - Dashboard showing command usage, suggestions, and activity heatmap
- **Secrets Vault** (`vault.sh`) - Encrypted storage for API keys using age/gpg - **Secrets Vault** (`dotfiles-vault.sh`) - Encrypted storage for API keys using age/gpg
- **Password Manager Integration** (`password-manager.zsh`) - Unified CLI for 1Password, LastPass, Bitwarden - **Password Manager Integration** (`password-manager.zsh`) - Unified CLI for 1Password, LastPass, Bitwarden
- **Dotfiles Sync** (`dotfiles-sync.sh`) - Multi-machine synchronization with watch mode - **Dotfiles Sync** (`dotfiles-sync.sh`) - Multi-machine synchronization with watch mode
- **Dotfiles Doctor** (`dotfiles-doctor.sh`) - Health checker with auto-fix capability - **Dotfiles Doctor** (`dotfiles-doctor.sh`) - Health checker with auto-fix capability
@@ -86,35 +144,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Checking Your Version ### Checking Your Version
```bash ```bash
dfv
# or
dotfiles-version.sh dotfiles-version.sh
``` ```
### Updating ### Updating
```bash ```bash
cd ~/.dotfiles dfu
git pull origin main # or
./install.sh --skip-deps dotfiles-update.sh
```
Or use:
```bash
update-dotfiles.sh
``` ```
--- ---
## Roadmap ## Roadmap
### Planned for 1.1.0 ### Planned for 1.2.0
- [ ] Multiple theme support with live preview - [ ] Multiple theme support with live preview
- [ ] Project scaffolding templates - [ ] Project scaffolding templates
- [ ] SSH key generation helper - [ ] SSH key generation helper
- [ ] Machine profiles (work, personal, server) - [ ] Machine profiles (work, personal, server)
### Planned for 1.2.0 ### Planned for 1.3.0
- [ ] Dynamic MOTD/welcome screen
- [ ] Remote machine bootstrap script - [ ] Remote machine bootstrap script
- [ ] Neovim configuration support - [ ] Neovim configuration support

View File

@@ -357,9 +357,11 @@ All dotfiles commands have convenient aliases:
| `dfu` | `dotfiles-update.sh` | Update dotfiles | | `dfu` | `dotfiles-update.sh` | Update dotfiles |
| `dfv` | `dotfiles-version.sh` | Version info | | `dfv` | `dotfiles-version.sh` | Version info |
| `dfstats` | `dotfiles-stats.sh` | Shell analytics | | `dfstats` | `dotfiles-stats.sh` | Shell analytics |
| `dfcompile` | `dotfiles-compile.sh` | Compile zsh for speed |
| `vault` | `dotfiles-vault.sh` | Secrets manager | | `vault` | `dotfiles-vault.sh` | Secrets manager |
| `reload` | `source ~/.zshrc` | Reload shell | | `reload` | `source ~/.zshrc` | Reload shell |
| `dfc` | `dotfiles-cli` | CLI with subcommands | | `dfc` | `dotfiles-cli` | CLI with subcommands |
| `dotfiles` | `cd ~/.dotfiles` | Go to dotfiles dir |
## 🗑️ Uninstalling ## 🗑️ Uninstalling

View File

@@ -409,7 +409,7 @@ All dotfiles commands have convenient aliases defined in `~/.dotfiles/zsh/aliase
| Alias | Full Command | Description | | Alias | Full Command | Description |
|-------|--------------|-------------| |-------|--------------|-------------|
| `dotfiles` / `df` | `cd ~/.dotfiles` | Go to dotfiles directory | | `dotfiles` / `dfcd` | `cd ~/.dotfiles` | Go to dotfiles directory |
| `dfd` / `doctor` | `dotfiles-doctor.sh` | Run health check | | `dfd` / `doctor` | `dotfiles-doctor.sh` | Run health check |
| `dffix` | `dotfiles-doctor.sh --fix` | Auto-fix issues | | `dffix` | `dotfiles-doctor.sh --fix` | Auto-fix issues |
| `dfs` / `dfsync` | `dotfiles-sync.sh` | Interactive sync | | `dfs` / `dfsync` | `dotfiles-sync.sh` | Interactive sync |
@@ -421,6 +421,7 @@ All dotfiles commands have convenient aliases defined in `~/.dotfiles/zsh/aliase
| `dfstats` / `stats` | `dotfiles-stats.sh` | Shell analytics | | `dfstats` / `stats` | `dotfiles-stats.sh` | Shell analytics |
| `tophist` | `dotfiles-stats.sh --top` | Top commands | | `tophist` | `dotfiles-stats.sh --top` | Top commands |
| `suggest` | `dotfiles-stats.sh --suggest` | Alias suggestions | | `suggest` | `dotfiles-stats.sh --suggest` | Alias suggestions |
| `dfcompile` | `dotfiles-compile.sh` | Compile zsh for speed |
### Vault Commands ### Vault Commands
@@ -451,6 +452,7 @@ dfc update # Update dotfiles
dfc version # Show version dfc version # Show version
dfc stats # Shell analytics dfc stats # Shell analytics
dfc vault # Secrets manager dfc vault # Secrets manager
dfc compile # Compile zsh for speed
dfc edit # Open in editor dfc edit # Open in editor
dfc cd # Go to dotfiles dir dfc cd # Go to dotfiles dir
dfc help # Show help dfc help # Show help
@@ -458,6 +460,61 @@ dfc help # Show help
--- ---
## Shell Optimization
The `.zshrc` is optimized for fast startup while maintaining full functionality.
### Measuring Startup Time
```bash
# Quick measurement
time zsh -i -c exit
# More accurate (requires hyperfine)
hyperfine 'zsh -i -c exit'
```
### Compile Zsh Files
Pre-compile `.zsh` files to bytecode for 20-50ms speedup:
```bash
dfcompile # Compile all zsh files
dfcompile --clean # Remove compiled files
```
### Loading Strategy
| Phase | What Loads | Timing |
|-------|------------|--------|
| **Immediate** | PATH, history, oh-my-zsh, basic aliases, keybindings | Blocks prompt |
| **Deferred** | Tool aliases (eza, bat), FZF, smart-suggest, snapper, vault | After first prompt |
| **Background** | Dotfiles sync check | Fully async |
| **Lazy** | NVM, kubectl, virtualenvwrapper | When first used |
### Profiling
To debug slow startup, edit `~/.zshrc`:
```zsh
# Uncomment at the TOP of file:
zmodload zsh/zprof
# Uncomment at the BOTTOM of file:
zprof
```
Then run `zsh -i -c exit` to see timing breakdown.
### Tips for Fast Startup
1. **Run `dfcompile`** after installation
2. **Avoid adding** `command -v` checks in `.zshrc` (use `_has_cmd` cache instead)
3. **Use lazy loading** for heavy tools (NVM, kubectl already lazy-loaded)
4. **Keep `.zshrc.local`** minimal
---
## Customization ## Customization
### Adding Aliases ### Adding Aliases

View File

@@ -6,7 +6,7 @@
# ============================================================================ # ============================================================================
# --- Version --- # --- Version ---
DOTFILES_VERSION="1.0.0" DOTFILES_VERSION="1.1.0"
# --- GitHub Settings --- # --- GitHub Settings ---
DOTFILES_GITHUB_USER="adlee-was-taken" DOTFILES_GITHUB_USER="adlee-was-taken"

View File

@@ -26,6 +26,7 @@ SKIP_DEPS=false
DEPS_ONLY=false DEPS_ONLY=false
UNINSTALL=false UNINSTALL=false
UNINSTALL_PURGE=false UNINSTALL_PURGE=false
RUN_WIZARD=false
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
@@ -41,10 +42,14 @@ for arg in "$@"; do
--purge) --purge)
UNINSTALL_PURGE=true UNINSTALL_PURGE=true
;; ;;
--wizard)
RUN_WIZARD=true
;;
--help|-h) --help|-h)
echo "Usage: $0 [OPTIONS]" echo "Usage: $0 [OPTIONS]"
echo echo
echo "Options:" echo "Options:"
echo " --wizard Run interactive setup wizard (recommended)"
echo " --skip-deps Skip dependency installation (useful for re-runs)" echo " --skip-deps Skip dependency installation (useful for re-runs)"
echo " --deps-only Only install dependencies, then exit" echo " --deps-only Only install dependencies, then exit"
echo " --uninstall Remove symlinks and restore backups" echo " --uninstall Remove symlinks and restore backups"
@@ -57,6 +62,7 @@ for arg in "$@"; do
echo echo
echo "Examples:" echo "Examples:"
echo " ./install.sh # Full install" echo " ./install.sh # Full install"
echo " ./install.sh --wizard # Interactive wizard"
echo " ./install.sh --skip-deps # Re-run without checking deps" echo " ./install.sh --skip-deps # Re-run without checking deps"
echo " ./install.sh --uninstall # Remove symlinks" echo " ./install.sh --uninstall # Remove symlinks"
echo " ./install.sh --uninstall --purge # Remove everything" echo " ./install.sh --uninstall --purge # Remove everything"
@@ -800,6 +806,28 @@ main() {
do_uninstall do_uninstall
fi fi
# Handle wizard mode
if [[ "$RUN_WIZARD" == true ]]; then
load_config
local wizard_script="$DOTFILES_DIR/setup/setup-wizard.sh"
# If dotfiles not yet cloned, clone first
if [[ ! -f "$wizard_script" ]]; then
detect_os
install_dependencies
clone_or_update_dotfiles
load_config
wizard_script="$DOTFILES_DIR/setup/setup-wizard.sh"
fi
if [[ -f "$wizard_script" ]]; then
exec bash "$wizard_script"
else
print_error "Wizard script not found: $wizard_script"
exit 1
fi
fi
print_header print_header
detect_os detect_os
@@ -844,6 +872,7 @@ main() {
echo " dfs / dfsync - Sync dotfiles" echo " dfs / dfsync - Sync dotfiles"
echo " dfu / dfupdate - Update dotfiles" echo " dfu / dfupdate - Update dotfiles"
echo " dfstats / stats - Shell analytics" echo " dfstats / stats - Shell analytics"
echo " dfcompile - Compile zsh for speed"
echo " vault - Secrets manager" echo " vault - Secrets manager"
echo echo
echo -e "${BLUE}To uninstall:${NC}" echo -e "${BLUE}To uninstall:${NC}"

View File

@@ -25,39 +25,45 @@ _df_run() {
fi fi
} }
# --- Core Dotfiles Commands ---
alias dotfiles='cd ~/.dotfiles'
alias dfcd='cd ~/.dotfiles'
# Note: 'df' not aliased to avoid conflict with disk free utility
# Doctor - health check # Doctor - health check
function dfd() { _df_run dotfiles-doctor.sh "$@"; } dfd() { _df_run dotfiles-doctor.sh "$@"; }
function doctor() { _df_run dotfiles-doctor.sh "$@"; } doctor() { _df_run dotfiles-doctor.sh "$@"; }
function dffix() { _df_run dotfiles-doctor.sh --fix "$@"; } dffix() { _df_run dotfiles-doctor.sh --fix "$@"; }
# Sync - multi-machine synchronization # Sync - multi-machine synchronization
function dfs() { _df_run dotfiles-sync.sh "$@"; } dfs() { _df_run dotfiles-sync.sh "$@"; }
function dfsync() { _df_run dotfiles-sync.sh "$@"; } dfsync() { _df_run dotfiles-sync.sh "$@"; }
function dfpush() { _df_run dotfiles-sync.sh --push "$@"; } dfpush() { _df_run dotfiles-sync.sh --push "$@"; }
function dfpull() { _df_run dotfiles-sync.sh --pull "$@"; } dfpull() { _df_run dotfiles-sync.sh --pull "$@"; }
function dfstatus() { _df_run dotfiles-sync.sh --status "$@"; } dfstatus() { _df_run dotfiles-sync.sh --status "$@"; }
# Update - pull latest and reinstall # Update - pull latest and reinstall
function dfu() { _df_run dotfiles-update.sh "$@"; } dfu() { _df_run dotfiles-update.sh "$@"; }
function dfupdate() { _df_run dotfiles-update.sh "$@"; } dfupdate() { _df_run dotfiles-update.sh "$@"; }
# Version - check version info # Version - check version info
function dfv() { _df_run dotfiles-version.sh "$@"; } dfv() { _df_run dotfiles-version.sh "$@"; }
function dfversion() { _df_run dotfiles-version.sh "$@"; } dfversion() { _df_run dotfiles-version.sh "$@"; }
# Stats - shell analytics # Stats - shell analytics
function dfstats() { _df_run dotfiles-stats.sh "$@"; } dfstats() { _df_run dotfiles-stats.sh "$@"; }
function tophist() { _df_run dotfiles-stats.sh --top "$@"; } stats() { _df_run dotfiles-stats.sh "$@"; }
function suggest() { _df_run dotfiles-stats.sh --suggest "$@"; } tophist() { _df_run dotfiles-stats.sh --top "$@"; }
suggest() { _df_run dotfiles-stats.sh --suggest "$@"; }
# Vault - secrets management # Vault - secrets management
function vault() { _df_run dotfiles-vault.sh "$@"; } vault() { _df_run dotfiles-vault.sh "$@"; }
function vls() { _df_run dotfiles-vault.sh list "$@"; } vls() { _df_run dotfiles-vault.sh list "$@"; }
function vget() { _df_run dotfiles-vault.sh get "$@"; } vget() { _df_run dotfiles-vault.sh get "$@"; }
function vset() { _df_run dotfiles-vault.sh set "$@"; } vset() { _df_run dotfiles-vault.sh set "$@"; }
# Compile - compile zsh files for speed # Compile - compile zsh files for speed
function dfcompile() { _df_run dotfiles-compile.sh "$@"; } dfcompile() { _df_run dotfiles-compile.sh "$@"; }
# --- Quick Edit Aliases --- # --- Quick Edit Aliases ---
alias zshrc='${EDITOR:-vim} ~/.zshrc' alias zshrc='${EDITOR:-vim} ~/.zshrc'

View File

@@ -127,10 +127,11 @@ _palette_get_actions() {
printf "%s\t%s\t%s\t%s\n" "$ICON_EDIT" "action" "Edit .zshrc" "${EDITOR:-vim} ~/.zshrc" printf "%s\t%s\t%s\t%s\n" "$ICON_EDIT" "action" "Edit .zshrc" "${EDITOR:-vim} ~/.zshrc"
printf "%s\t%s\t%s\t%s\n" "$ICON_EDIT" "action" "Edit dotfiles.conf" "${EDITOR:-vim} $DOTFILES_DIR/dotfiles.conf" printf "%s\t%s\t%s\t%s\n" "$ICON_EDIT" "action" "Edit dotfiles.conf" "${EDITOR:-vim} $DOTFILES_DIR/dotfiles.conf"
printf "%s\t%s\t%s\t%s\n" "$ICON_EDIT" "action" "Edit theme" "${EDITOR:-vim} $DOTFILES_DIR/zsh/themes/adlee.zsh-theme" printf "%s\t%s\t%s\t%s\n" "$ICON_EDIT" "action" "Edit theme" "${EDITOR:-vim} $DOTFILES_DIR/zsh/themes/adlee.zsh-theme"
printf "%s\t%s\t%s\t%s\n" "$ICON_SCRIPT" "action" "Dotfiles doctor" "dotfiles-doctor.sh" printf "%s\t%s\t%s\t%s\n" "$ICON_SCRIPT" "action" "Dotfiles doctor" "dfd"
printf "%s\t%s\t%s\t%s\n" "$ICON_SCRIPT" "action" "Dotfiles sync" "dotfiles-sync.sh" printf "%s\t%s\t%s\t%s\n" "$ICON_SCRIPT" "action" "Dotfiles sync" "dfs"
printf "%s\t%s\t%s\t%s\n" "$ICON_SCRIPT" "action" "Shell stats" "shell-stats.sh" printf "%s\t%s\t%s\t%s\n" "$ICON_SCRIPT" "action" "Shell stats" "dfstats"
printf "%s\t%s\t%s\t%s\n" "$ICON_SCRIPT" "action" "Vault list" "vault.sh list" printf "%s\t%s\t%s\t%s\n" "$ICON_SCRIPT" "action" "Compile zsh" "dfcompile"
printf "%s\t%s\t%s\t%s\n" "$ICON_SCRIPT" "action" "Vault list" "vault list"
printf "%s\t%s\t%s\t%s\n" "$ICON_ACTION" "action" "Clear screen" "clear" printf "%s\t%s\t%s\t%s\n" "$ICON_ACTION" "action" "Clear screen" "clear"
printf "%s\t%s\t%s\t%s\n" "$ICON_DIR" "action" "Home" "cd ~" printf "%s\t%s\t%s\t%s\n" "$ICON_DIR" "action" "Home" "cd ~"
printf "%s\t%s\t%s\t%s\n" "$ICON_DIR" "action" "Dotfiles" "cd $DOTFILES_DIR" printf "%s\t%s\t%s\t%s\n" "$ICON_DIR" "action" "Dotfiles" "cd $DOTFILES_DIR"

View File

@@ -1,102 +1,157 @@
#!/usr/bin/env zsh #!/usr/bin/env zsh
# ============================================================================ # ============================================================================
# MOTD — Width-Safe, Color-Safe, Reload-Safe # MOTD (Message of the Day) - Dynamic System Info
# ============================================================================
# Displays system information on shell startup
#
# Functions:
# show_motd - Compact box format
# show_motd_mini - Single line format
# ============================================================================ # ============================================================================
# ---------------------------- Guards ----------------------------------------- # Only run in interactive shells
[[ -o interactive ]] || return 0
[[ -o interactive ]] || return # ============================================================================
[[ -n $__MOTD_SHOWN ]] && return # Colors (ANSI escape codes)
typeset -g __MOTD_SHOWN=1 # ============================================================================
# ---------------------------- Configuration ---------------------------------- _M_RESET=$'\033[0m'
_M_BOLD=$'\033[1m'
_M_DIM=$'\033[2m'
_M_BLUE=$'\033[38;5;39m'
_M_CYAN=$'\033[38;5;51m'
_M_GREEN=$'\033[38;5;82m'
_M_YELLOW=$'\033[38;5;220m'
_M_GREY=$'\033[38;5;242m'
BOX_WIDTH=78 # total width INCLUDING borders # ============================================================================
INNER_WIDTH=$(( BOX_WIDTH - 2 )) # Info Gathering
LABEL_WIDTH=12 # ============================================================================
# ---------------------------- Colors ----------------------------------------- _motd_uptime() {
local up=$(uptime 2>/dev/null)
autoload -Uz colors && colors if [[ "$up" =~ "up "([^,]+) ]]; then
echo "${match[1]}" | sed 's/^ *//'
C_DIM="%F{242}" else
C_HEAD="%B%F{39}" echo "?"
C_LABEL="%F{51}" fi
C_OK="%F{82}"
C_RESET="%f%b"
# ---------------------------- Low-level helpers ------------------------------
repeat_char() {
local ch="$1" n="$2"
printf '%*s' "$n" '' | tr ' ' "$ch"
} }
pad_right() { _motd_load() {
local s="$1" w="$2" if [[ -f /proc/loadavg ]]; then
printf '%-*s' "$w" "$s" awk '{print $1}' /proc/loadavg
else
uptime | awk -F'load average:' '{print $2}' | awk -F, '{print $1}' | xargs
fi
} }
center_text() { _motd_mem() {
local s="$1" w="$2" free -h 2>/dev/null | awk '/^Mem:/ {print $3 "/" $2}' || echo "N/A"
local len=${#s}
(( len >= w )) && { print "${s[1,w]}"; return }
local pad=$(( (w - len) / 2 ))
printf '%*s%s%*s' "$pad" '' "$s" "$(( w - len - pad ))" ''
} }
# ---------------------------- Box primitives --------------------------------- _motd_disk() {
df -h / 2>/dev/null | awk 'NR==2 {print $4 " free"}' || echo "N/A"
box_top() {
print "${C_DIM}$(repeat_char '─' $INNER_WIDTH)${C_RESET}"
} }
box_bottom() { # ============================================================================
print "${C_DIM}$(repeat_char '─' $INNER_WIDTH)${C_RESET}" # Box Drawing - Fixed Width
# ============================================================================
# Fixed box width
_M_WIDTH=62
_motd_line() {
local char="$1"
local i
local line=""
for ((i=0; i<_M_WIDTH; i++)); do
line+="$char"
done
echo "$line"
} }
box_blank() { _motd_pad() {
print "${C_DIM}$(repeat_char ' ' $INNER_WIDTH)${C_RESET}" # Pad a plain string to exact width
local str="$1"
local width="$2"
local len=${#str}
if (( len >= width )); then
echo "${str:0:$width}"
else
printf "%-${width}s" "$str"
fi
} }
box_line() { # ============================================================================
local content="$1" # Main Display Function
content="$(pad_right "$content" "$INNER_WIDTH")" # ============================================================================
print "${C_DIM}${C_RESET}${content}${C_DIM}${C_RESET}"
show_motd() {
[[ -n "$_MOTD_SHOWN" && "$1" != "--force" ]] && return 0
typeset -g _MOTD_SHOWN=1
local hostname="${HOST:-$(hostname -s 2>/dev/null)}"
local datetime=$(date '+%a %b %d %H:%M')
local uptime=$(_motd_uptime)
local load=$(_motd_load)
local mem=$(_motd_mem)
local disk=$(_motd_disk)
local hline=$(_motd_line '─')
local inner=$((_M_WIDTH - 2))
echo ""
# Top border
echo "${_M_GREY}${hline}${_M_RESET}"
# Header: hostname + datetime
local h_left="${hostname}"
local h_right="${datetime}"
local h_pad=$((inner - ${#h_left} - ${#h_right}))
local h_spaces=""
for ((i=0; i<h_pad; i++)); do h_spaces+=" "; done
echo "${_M_GREY}${_M_RESET} ${_M_BOLD}${_M_BLUE}${_M_RESET} ${_M_BOLD}${hostname}${_M_RESET}${h_spaces}${_M_DIM}${datetime}${_M_RESET} ${_M_GREY}${_M_RESET}"
# Separator
echo "${_M_GREY}${hline}${_M_RESET}"
# Stats line - build with exact spacing
local s1="▲up:${uptime}"
local s2="◆load:${load}"
local s3="◇mem:${mem}"
local s4="${disk}"
local stats_content="${s1} ${s2} ${s3} ${s4}"
local stats_pad=$((inner - ${#stats_content} - 1))
local stats_spaces=""
for ((i=0; i<stats_pad; i++)); do stats_spaces+=" "; done
echo "${_M_GREY}${_M_RESET} ${_M_DIM}${_M_RESET}up:${uptime} ${_M_DIM}${_M_RESET}load:${load} ${_M_DIM}${_M_RESET}mem:${mem} ${_M_DIM}${_M_RESET}${disk}${stats_spaces}${_M_GREY}${_M_RESET}"
# Bottom border
echo "${_M_GREY}${hline}${_M_RESET}"
echo ""
} }
# ---------------------------- Content builders ------------------------------- # ============================================================================
# Mini Format (Single Line)
# ============================================================================
header_line() { show_motd_mini() {
local text="$1" [[ -n "$_MOTD_SHOWN" && "$1" != "--force" ]] && return 0
box_line "$(center_text "$text" "$INNER_WIDTH")" typeset -g _MOTD_SHOWN=1
local hostname="${HOST:-$(hostname -s 2>/dev/null)}"
local uptime=$(_motd_uptime)
local mem=$(_motd_mem)
echo "${_M_DIM}──${_M_RESET} ${_M_BOLD}${hostname}${_M_RESET} ${_M_DIM}${_M_RESET} up:${uptime} ${_M_DIM}${_M_RESET} mem:${mem} ${_M_DIM}──${_M_RESET}"
} }
row_line() { # ============================================================================
local label="$1" value="$2" # Aliases
local left="$(pad_right "$label" "$LABEL_WIDTH")" # ============================================================================
box_line " ${left} ${value}"
}
# ---------------------------- Info providers ---------------------------------
get_os() { uname -sr }
get_uptime() { uptime | sed 's/.*up *//' | cut -d',' -f1 }
get_load() { uptime | awk -F'load average:' '{print $2}' | xargs }
get_mem() { free -h | awk '/Mem:/ {print $3 "/" $2}' }
get_disk() { df -h / | awk 'NR==2 {print $3 "/" $2 " (" $5 ")"}' }
# ---------------------------- Render -----------------------------------------
box_top
header_line "${C_HEAD}$(hostname)${C_RESET} - ${C_DIM}$(get_os)${C_RESET}"
box_blank
row_line "${C_LABEL}Uptime${C_RESET}" "$(get_uptime)"
row_line "${C_LABEL}Load${C_RESET}" "$(get_load)"
row_line "${C_LABEL}Memory${C_RESET}" "$(get_mem)"
row_line "${C_LABEL}Disk${C_RESET}" "$(get_disk)"
box_blank
box_line " ${C_OK}System up to date${C_RESET}"
box_bottom
print
alias motd='show_motd --force'
alias motd-mini='show_motd_mini --force'