diff --git a/dot_config/zsh/config/aliases.zsh b/dot_config/zsh/config/aliases.zsh index e0dd659..96f31f8 100644 --- a/dot_config/zsh/config/aliases.zsh +++ b/dot_config/zsh/config/aliases.zsh @@ -133,14 +133,6 @@ alias ns='npm start' alias nt='npm test' # ═══ Утилиты ═══════════════════════════════════════════════ -alias reload='source ~/.config/zsh/.zshrc' -alias path='echo $PATH | tr ":" "\n"' -alias h='history' -alias c='clear' -alias x='exit' -alias rsend='sudo rsync -avhP --stats' -alias wakemac='ssh vps "wakeonlan -i 192.168.1.255 FC:34:97:E1:08:E9"' - alias zshrc='${EDITOR} ~/.config/zsh/.zshrc' alias reload='source ~/.config/zsh/.zshrc' alias path='echo $PATH | tr ":" "\n"' @@ -150,6 +142,10 @@ alias x='exit' alias rsend='sudo rsync -avhP --stats' alias wakemac='ssh vps "wakeonlan -i 192.168.1.255 FC:34:97:E1:08:E9"' +# ═══ Пользовательские скрипты ══════════════════════════════ +alias bm='backup-menu' +alias sp='ssh-pick' + # Использовать bat вместо cat, если установлен if command -v bat &>/dev/null; then alias cat='bat' diff --git a/dot_config/zsh/config/functions.zsh b/dot_config/zsh/config/functions.zsh index ee34275..25e7a65 100644 --- a/dot_config/zsh/config/functions.zsh +++ b/dot_config/zsh/config/functions.zsh @@ -266,6 +266,53 @@ sshp-restart() { } +# _backup_reminder - проверить давность последнего бэкапа (вызывается из .zshrc) +_backup_reminder() { + local state_file="${XDG_STATE_HOME:-$HOME/.local/state}/backup-last-run" + local threshold=$(( 2 * 24 * 3600 )) # 2 дня в секундах + local now + now=$(date +%s) + + if [[ ! -f "$state_file" ]]; then + print -P "%F{yellow}⚠ Бэкап не запускался. Запусти: backup-menu%f" + return + fi + + local last + last=$(cat "$state_file") + local diff=$(( now - last )) + + if (( diff > threshold )); then + local days=$(( diff / 86400 )) + print -P "%F{yellow}⚠ Последний бэкап: ${days} дн. назад. Запусти: backup-menu%f" + fi +} + +# proj - перейти в проект через fzf +proj() { + local base="${PROJECTS_DIR:-$HOME/Documents/Projects}" + if [[ ! -d "$base" ]]; then + echo "Projects dir not found: $base" + return 1 + fi + + local target + if command -v fzf &>/dev/null; then + target=$(find "$base" -maxdepth 2 -mindepth 1 -type d \ + \( -name .git -o -name node_modules -o -name .venv \) -prune \ + -o -type d -print \ + | sed "s|$base/||" \ + | fzf --prompt "proj > " --height=40% --reverse) || return 0 + elif [[ -n "$1" ]]; then + target="$1" + else + ls "$base" + return 0 + fi + + cd "$base/$target" +} + activate() { local venv_dir="" if [[ -d ".venv" ]]; then diff --git a/dot_config/zsh/dot_zshrc b/dot_config/zsh/dot_zshrc index 5fa0aff..c7e9448 100644 --- a/dot_config/zsh/dot_zshrc +++ b/dot_config/zsh/dot_zshrc @@ -34,3 +34,6 @@ source "${ZSH_CONFIG_DIR}/prompt.zsh" # ═══ Fastfetch ══════════════════════════════════════════════ [[ -z "$TMUX" && "$TERM_PROGRAM" == "kitty" ]] && fastfetch + +# ═══ Напоминания ════════════════════════════════════════════ +(( ${+functions[_backup_reminder]} )) && _backup_reminder diff --git a/dot_local/bin/backup-menu b/dot_local/bin/backup-menu new file mode 100755 index 0000000..639b540 --- /dev/null +++ b/dot_local/bin/backup-menu @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# backup-menu — interactive resticprofile menu + +set -euo pipefail + +PROFILES=(dotfiles projects vaults files pictures) +STATE_FILE="${XDG_STATE_HOME:-$HOME/.local/state}/backup-last-run" + +_profile_label() { + local profile=$1 + local last + last=$(resticprofile -n "$profile" snapshots --json 2>/dev/null \ + | python3 -c " +import sys, json +try: + snaps = json.load(sys.stdin) + if snaps: + t = snaps[-1]['time'][:16].replace('T', ' ') + print(f'last: {t}') + else: + print('no snapshots') +except: + print('?') +" 2>/dev/null || echo "?") + printf "%-16s %s" "$profile" "$last" +} + +_run_backup() { + local profile=$1 + echo "▶ resticprofile -n $profile backup" + resticprofile -n "$profile" backup + mkdir -p "$(dirname "$STATE_FILE")" + date +%s > "$STATE_FILE" +} + +_run_forget() { + local profile=$1 + echo "▶ resticprofile -n $profile forget" + resticprofile -n "$profile" forget +} + +_show_snapshots() { + local profile=$1 + resticprofile -n "$profile" snapshots 2>&1 | less +} + +main() { + if ! command -v fzf &>/dev/null; then + echo "fzf not found" + exit 1 + fi + + # Build profile list with last run info + local lines=() + for p in "${PROFILES[@]}"; do + lines+=("$(_profile_label "$p")") + done + + local selected + selected=$(printf '%s\n' "${lines[@]}" | fzf \ + --prompt "backup > " \ + --header "Enter — запустить бэкап | Ctrl-F — forget | Ctrl-S — снапшоты" \ + --bind "ctrl-f:become(echo '__forget__'$(echo {} | awk '{print $1}'))" \ + --bind "ctrl-s:become(echo '__snaps__'$(echo {} | awk '{print $1}'))" \ + --no-sort) || exit 0 + + local profile + profile=$(echo "$selected" | awk '{print $1}') + + case "$selected" in + __forget__*) _run_forget "${selected#__forget__}" ;; + __snaps__*) _show_snapshots "${selected#__snaps__}" ;; + *) _run_backup "$profile" ;; + esac +} + +main "$@" diff --git a/dot_local/bin/ssh-pick b/dot_local/bin/ssh-pick new file mode 100755 index 0000000..33d4fce --- /dev/null +++ b/dot_local/bin/ssh-pick @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# ssh-pick — выбрать SSH-хост через fzf + +set -euo pipefail + +SSH_CONFIG_DIR="${HOME}/.ssh/config.d" +SSH_CONFIG="${HOME}/.ssh/config" + +_list_hosts() { + local configs=() + + if [[ -d "$SSH_CONFIG_DIR" ]]; then + configs=("$SSH_CONFIG_DIR"/*.conf) + fi + [[ -f "$SSH_CONFIG" ]] && configs+=("$SSH_CONFIG") + + for f in "${configs[@]}"; do + [[ -f "$f" ]] || continue + awk '/^Host [^*]/{ + sub(/^Host /, "") + n = split($0, hosts, " ") + for (i = 1; i <= n; i++) print hosts[i] + }' "$f" + done | sort -u +} + +main() { + if ! command -v fzf &>/dev/null; then + echo "fzf not found" + exit 1 + fi + + local hosts + hosts=$(_list_hosts) + + if [[ -z "$hosts" ]]; then + echo "No SSH hosts found" + exit 1 + fi + + local selected + selected=$(echo "$hosts" | fzf \ + --prompt "ssh > " \ + --header "Enter — подключиться | Ctrl-C — выйти" \ + --height=40% \ + --reverse) || exit 0 + + echo "▶ ssh $selected" + exec ssh "$selected" +} + +main "$@"