diff --git a/.gitignore b/.gitignore index 56aa211..abd19fb 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,9 @@ id_* rustdesk/ *_scan_*.txt *.m4a + +# Git templates (contains private commit hooks) +git-templates/ + +# Taskwarrior config (contains private task data references) +task/ diff --git a/scripts/bin/rpi-flasher b/scripts/bin/rpi-flasher new file mode 100755 index 0000000..be948c4 --- /dev/null +++ b/scripts/bin/rpi-flasher @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Script Name: rpi-flasher +# Description: Launch Raspberry Pi Imager via XWayland +# AppImage only bundles xcb (X11) plugin, needs XWayland on Wayland sessions + +export DISPLAY="${DISPLAY:-:0}" + +# Find XWayland auth file if XAUTHORITY isn't set +if [ -z "${XAUTHORITY:-}" ]; then + for f in /run/user/"$(id -u)"/xauth_*; do + [ -f "$f" ] && export XAUTHORITY="$f" && break + done +fi + +# Allow root to connect (needed when imager escalates via pkexec to write SD card) +xhost +si:localuser:root 2>/dev/null || true + +# Copy xauth cookie to ~/.Xauthority so pkexec'd root process can find it +xauth extract - "$DISPLAY" 2>/dev/null | xauth -f "$HOME/.Xauthority" merge - 2>/dev/null || true + +QT_QPA_PLATFORM=xcb /home/e/opt/raspberrypi/imager_2.0.6_amd64.AppImage "$@" diff --git a/scripts/bin/zzz b/scripts/bin/zzz new file mode 100755 index 0000000..25126cf --- /dev/null +++ b/scripts/bin/zzz @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# zzz - Sleep timer with notification +# Usage: zzz -s 5 (5 seconds) +# zzz -m 2 (2 minutes) +# zzz 30 (30 seconds, default unit) +# zzz -q -s 10 (quiet - no voice, just ding) + +set -euo pipefail + +QUIET=false +SECONDS_TOTAL=0 + +usage() { + echo "Usage: zzz [-s seconds] [-m minutes] [-q] [seconds]" + echo " -s SEC Sleep for SEC seconds" + echo " -m MIN Sleep for MIN minutes" + echo " -q Quiet mode (ding only, no voice)" + echo " NUM Bare number = seconds" + exit 1 +} + +# Parse args +while [[ $# -gt 0 ]]; do + case "$1" in + -s) SECONDS_TOTAL="$2"; shift 2 ;; + -m) SECONDS_TOTAL=$(( $2 * 60 )); shift 2 ;; + -q) QUIET=true; shift ;; + -h|--help) usage ;; + *) + if [[ "$1" =~ ^[0-9]+$ ]]; then + SECONDS_TOTAL="$1"; shift + else + echo "Unknown argument: $1" >&2 + usage + fi + ;; + esac +done + +if [[ "$SECONDS_TOTAL" -le 0 ]]; then + usage +fi + +# Display what we're doing +if [[ "$SECONDS_TOTAL" -ge 60 ]]; then + MINS=$(( SECONDS_TOTAL / 60 )) + SECS=$(( SECONDS_TOTAL % 60 )) + if [[ "$SECS" -gt 0 ]]; then + echo "💤 Sleeping ${MINS}m ${SECS}s..." + else + echo "💤 Sleeping ${MINS}m..." + fi +else + echo "💤 Sleeping ${SECONDS_TOTAL}s..." +fi + +sleep "$SECONDS_TOTAL" + +# Notification +paplay /usr/share/sounds/Oxygen-Im-New-Mail.ogg 2>/dev/null & + +if [[ "$QUIET" == false ]]; then + echo "Sleep finished" | piper -m ~/.local/share/piper-voices/en_US-lessac-medium.onnx --output-raw 2>/dev/null \ + | aplay -r 22050 -f S16_LE -t raw -q 2>/dev/null \ + || spd-say -w "Sleep finished" 2>/dev/null || true +fi + +echo "⏰ Done!" diff --git a/tmux/.tmux.conf.local b/tmux/.tmux.conf.local index 4fb31a2..a8ab59c 100644 --- a/tmux/.tmux.conf.local +++ b/tmux/.tmux.conf.local @@ -546,8 +546,10 @@ bind g display-popup -E -w 45% -h 50% -T " Prices " "bash -c '~/.local/bin/price bind v display-popup -E -w 80% -h 80% -T " Vitals " "btop" # notes (prefix + n) - opens helix scratchpad bind n display-popup -E -w 80% -h 80% -T " Notes " "hx ~/.claude/scratchpad/notes.md" -# docker (prefix + d) -bind d display-popup -E -w 80% -h 80% -T " Docker " "lazydocker" +# detach (prefix + d) - restore default that was accidentally overwritten +bind d detach-client +# docker (prefix + D) - capital D to preserve detach on lowercase d +bind D display-popup -E -w 80% -h 80% -T " Docker " "lazydocker" # weather (prefix + w) bind w display-popup -E -w 60% -h 70% -T " Weather " "bash -c 'curl -s wttr.in?0; echo; echo \" Press any key...\"; read -n1'" # agenda (prefix + a) - cal.com events diff --git a/zsh/.aliases b/zsh/.aliases index 89f6846..a3d4183 100644 --- a/zsh/.aliases +++ b/zsh/.aliases @@ -93,10 +93,14 @@ ide() { # TMUX-RECON Aliases # ---------------------------- -# Clipboard (conditional on xsel or xclip) +# Clipboard (Wayland-first, fallback to X11) # ---------------------------- -if command -v xsel &> /dev/null; then +if command -v wl-copy &> /dev/null && [[ -n "$WAYLAND_DISPLAY" ]]; then + # Wayland - use wl-clipboard + alias pbcopy='wl-copy' + alias pbpaste='wl-paste' +elif command -v xsel &> /dev/null; then alias pbcopy='xsel --input --clipboard' alias pbpaste='xsel --output --clipboard' elif command -v xclip &> /dev/null; then @@ -119,7 +123,7 @@ cpy() { cat "$1" | pbcopy } alias d='docker' -alias dc='docker-compose' +alias dc='docker compose' # alias h='history' alias f='fabric' #f() { @@ -130,7 +134,7 @@ alias j='journalctl -f' # Note: jj is now a script in ~/scripts/ (upgraded from alias) # For quick clipboard pretty-print, still use: pbpaste | jq . | pbcopy alias jjj='pbpaste | jq .' # Quick view (no copy back) -alias k='kill $(ps aux | fzf | awk '\''{print $2}'\'')' +alias kill='kill $(ps aux | fzf | awk '\''{print $2}'\'')' # alias k9='kill -9 **' alias nano='hx' alias nf='fzf -m --preview="bat --color=always --style=numbers --line-range:300 {}" --bind "enter:become(hx {+})"' @@ -149,6 +153,7 @@ elif [[ -n "$BASH_VERSION" ]]; then fi alias sa='source ~/.aliases' +alias snaabu="sudo /home/$USER/.pdtm/go/bin/naabu" alias ta='tmux attach -t' alias trim="awk '{\$1=\$1;print}'" @@ -373,6 +378,7 @@ alias gc='git commit' alias gco='git checkout' alias ga='git add' alias gst='git rev-parse --git-dir > /dev/null 2>&1 && git status || eza' +alias grv='git remote -v' copy-line() { @@ -727,6 +733,14 @@ alias run-backup='sudo ~/.claude/context/personal/scripts/full-system-backup.sh alias vpn-help='bat ~/.local/share/doc/protonvpn-automation-quickref.md 2>/dev/null || cat ~/.local/share/doc/protonvpn-automation-quickref.md' +# Kill stale ProtonVPN connections (fixes zombie openvpn after Plasma partial shutdown) +vpn-kill() { + nmcli connection show --active | grep -i proton | while read -r line; do + name=$(echo "$line" | awk '{print $1" "$2}') + nmcli connection down "$name" 2>/dev/null && echo "Disconnected: $name" + done +} + # TabMan - Browser tab manager alias tabman="bun ~/github/tabman/index.ts" @@ -741,4 +755,68 @@ alias reddit-dl="bun /home/e/opt/redlib/reddit-dl.ts" alias inotify-check='echo "Instances: $(find /proc/*/fd -lname "anon_inode:inotify" 2>/dev/null | wc -l) / $(cat /proc/sys/fs/inotify/max_user_instances)"' # SimpleX Bot - E2EE automation -alias simplex='~/opt/simplex-bot/simplex' +alias sbot='~/opt/simplex-bot/simplex' + +# ---------------------------- +# GPG Encryption Helpers +# ---------------------------- +# Simple file encryption/decryption using your default GPG key +# Usage: gpg-encrypt notes.md → creates notes.md.gpg +# gpg-decrypt notes.md.gpg → creates notes.md + +gpg-encrypt() { + local file="$1" + if [[ -z "$file" ]]; then + echo "Usage: gpg-encrypt " >&2 + return 1 + fi + if [[ ! -f "$file" ]]; then + echo "Error: File '$file' not found" >&2 + return 1 + fi + gpg --output "${file}.gpg" --encrypt --default-recipient-self "$file" \ + && echo "✅ Encrypted: ${file}.gpg" +} + +gpg-decrypt() { + local file="$1" + if [[ -z "$file" ]]; then + echo "Usage: gpg-decrypt " >&2 + return 1 + fi + if [[ ! -f "$file" ]]; then + echo "Error: File '$file' not found" >&2 + return 1 + fi + local output="${file%.gpg}" + if [[ "$output" == "$file" ]]; then + output="${file}.decrypted" + fi + gpg --decrypt "$file" > "$output" \ + && echo "✅ Decrypted: $output" +} + +# Standard Notes (AppImage in Downloads) +alias standard-notes='/home/$USER/Downloads/standard-notes-3.201.2-linux-x86_64.AppImage' + +alias dailies='~/.claude/context/personal/scripts/daily-reminders.sh' + +# ---- Meshtastic ----- +mesh-nodes() { + for port in /dev/ttyUSB*; do + [ -e "$port" ] || continue + echo "=== $port ===" + timeout 5 meshtastic --port "$port" --info 2>&1 | grep -iE "Owner|Short Name|Role|Hardware" || echo "No Meshtastic device" + done +} + +mesh-send() { + local port="${1:-/dev/ttyUSB0}" + shift + meshtastic --port "$port" --sendtext "$*" +} + +mesh-listen() { + local port="${1:-/dev/ttyUSB0}" + meshtastic --port "$port" --listen +} diff --git a/zsh/.exports b/zsh/.exports index e17cc83..b19f2d9 100644 --- a/zsh/.exports +++ b/zsh/.exports @@ -86,3 +86,8 @@ export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)" # [[ -n "$_xauth" ]] && export XAUTHORITY="$_xauth" # unset _xauth # fi + +# Disable SSH password popups +export SSH_ASKPASS="" +export SSH_ASKPASS_REQUIRE=never +export PATH=$PATH:/opt/zeek/bin diff --git a/zsh/.zshrc b/zsh/.zshrc index 9ef79a9..898fe9c 100644 --- a/zsh/.zshrc +++ b/zsh/.zshrc @@ -9,6 +9,12 @@ fi # Don't export - each shell instance should load fresh ZSHRC_LOADED=1 +# Import X11/Wayland display vars from systemd (for KDE Wayland) +# XAUTHORITY has a randomized filename each session, so pull from systemd +# if [ -z "$DISPLAY" ] || [ -z "$XAUTHORITY" ]; then +# eval $(systemctl --user show-environment | grep -E '^DISPLAY=|^XAUTHORITY=' 2>/dev/null) +# fi + # Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc. # Initialization code that may require console input (password prompts, [y/n] # confirmations, etc.) must go above this block; everything else may go below. @@ -109,10 +115,10 @@ zstyle ':fzf-tab:complete:more:*' fzf-preview 'bat --color=always $realpath 2>/d eval "$(zoxide init zsh)" # Atuin setup - smart history search -eval "$(atuin init zsh)" +eval "$(atuin init zsh --disable-up-arrow)" # Sourcing -source $HOME/.cargo/env +[ -f "$HOME/.cargo/env" ] && source "$HOME/.cargo/env" [ -f ~/.aliases ] && source ~/.aliases [ -f ~/.exports ] && source ~/.exports # source /usr/share/doc/pkgfile/command-not-found.zsh @@ -206,24 +212,94 @@ export PATH="$BUN_INSTALL/bin:$PATH" # Removed redundant .local/bin addition (already in ~/.exports) # SSH Agent for persistent tunnel (Baserow, etc.) -export SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket +# TEMPORARILY DISABLED - Testing if this breaks session during login +# export SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket -# SSH keys - auto-add if agent is running but keys not loaded -# Load main key (check by fingerprint, not email - OPSEC) -ssh-add -l 2>/dev/null | grep -q "Tq/46I0GfFmme7/tyb/BFhj" || { - ssh-add ~/.ssh/id_ed25519 2>/dev/null || echo "🔑 Run: ssh-add ~/.ssh/id_ed25519" -} -# Load additional keys from local config (not committed - contains infra-specific paths) -[[ -f ~/.ssh/autoload ]] && source ~/.ssh/autoload +# Reminder to add SSH key after reboot +# TEMPORARILY DISABLED - Testing if this breaks session during login +# if ! ssh-add -l &>/dev/null; then +# echo "🔑 Reminder: Add your Vultr SSH key for blog automation:" +# echo " ssh-add ~/.ssh/id_ed25519_vultr" +# fi # Deduplicate PATH at the end (after all PATH modifications) export PATH=$(echo "$PATH" | tr ':' '\n' | awk '!seen[$0]++' | paste -sd:) # Make PATH available to GUI applications (Ulauncher, etc.) # This ensures apps launched outside terminal sessions can find bun, custom bins, etc. -systemctl --user import-environment PATH +# TEMPORARILY DISABLED - Testing if this breaks systemd user session during login +# systemctl --user import-environment PATH [ -f ~/.env ] && source ~/.env # Port reference quick access -alias ports='cat ~/.claude/context/infrastructure/ports-reference.md | less' + +# VPN Switcher - alias to private script (not in git) +alias vpn-switch='/home/e/.claude/context/personal/scripts/vpn-switch' + +# GPG-based SSH authentication +export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket) +gpgconf --launch gpg-agent + +# Foundry (Ethereum development toolkit) +export PATH="$HOME/.foundry/bin:$PATH" + +# SSH key status check - run `ssh-keys` to see what's loaded +ssh-keys() { + local -A KEYS=( + [x1]="$HOME/.ssh/id_ed25519_x1" + [forge]="$HOME/.ssh/id_ed25519" + [contabo]="$HOME/.ssh/vps2" + [vps3]="$HOME/.ssh/id_ed25519" + ) + local loaded=$(ssh-add -l 2>/dev/null) + echo "SSH Keys:" + for host in x1 forge contabo vps3; do + local keyfile="${KEYS[$host]}" + local fp=$(ssh-keygen -lf "$keyfile" 2>/dev/null | awk '{print $2}') + if echo "$loaded" | grep -q "$fp" 2>/dev/null; then + echo " $host ✓ loaded" + else + echo " $host ✗ ssh-add $keyfile" + fi + done +} + +# Show missing SSH keys on new shell (quiet if all loaded) +_ssh_key_check() { + local loaded=$(ssh-add -l 2>/dev/null) + local missing=() + local -A KEYS=( + [x1]="$HOME/.ssh/id_ed25519_x1" + [forge/vps3]="$HOME/.ssh/id_ed25519" + [contabo]="$HOME/.ssh/vps2" + ) + for label in x1 "forge/vps3" contabo; do + local keyfile="${KEYS[$label]}" + local fp=$(ssh-keygen -lf "$keyfile" 2>/dev/null | awk '{print $2}') + if ! echo "$loaded" | grep -q "$fp" 2>/dev/null; then + missing+=("$label → ssh-add $keyfile") + fi + done + if (( ${#missing[@]} > 0 )); then + echo "🔑 Missing SSH keys:" + for m in "${missing[@]}"; do + echo " $m" + done + fi +} +_ssh_key_check + +# Post-reboot: remind to refresh security baseline +_reboot_baseline_check() { + local stamp="$HOME/.cache/.last-baseline-boot-id" + local current_boot=$(cat /proc/sys/kernel/random/boot_id 2>/dev/null) + local last_boot=$(cat "$stamp" 2>/dev/null) + if [[ "$current_boot" != "$last_boot" ]]; then + echo "🛡️ New boot detected — security baseline may be stale" + echo " Run: ~/.claude-user/scripts/upgrade-pai.sh --manifest" + mkdir -p "$(dirname "$stamp")" + echo "$current_boot" > "$stamp" + fi +} +_reboot_baseline_check