脚本文件:remmin备份还原snap.sh

问题

remmina 的 session 数据要备份到备份根的 remmin备份 目录,每次备份是一个年月日时间戳子目录,下次手动操作要能照着做,不依赖环境 PATH。

解决:snap.sh(推荐)

脚本位置:备份根 remmin备份/snap.sh,在本目录下 ./snap.sh 执行。2026-10-22 快照、列表、恢复三条路已实测全通。快照结构:<YYYYMMDD_HHMMSS>/remmina-data/ + <YYYYMMDD_HHMMSS>/remmina-config/。

场景一:备份(live → 时间戳子目录)

./snap.sh         # 生成 YYYYMMDD_HHMMSS/ 快照,打完报会话文件数
./snap.sh list    # 列出现有快照(含每个快照的会话数)

脚本自带校验:remmina-config/remmina.pref 缺失则报错,不留半截快照。

场景二:恢复(快照 → live 运行时数据)

./snap.sh 20261022_124257      # 裸快照名、相对路径、全路径三种写法都认

恢复前脚本自动给现状打 pre-restore-<时间> 快照做回滚面;remmina 正在运行时会先问一句,确认才退出它再覆盖;非快照目录会被拒绝。恢复后重开 remmina 生效。

备份对象

会话数据~/.local/share/remmina/*.remmina(本次 13 个,每个连接一个文件)
首选项+密钥~/.config/remmina/remmina.pref,含 secret= 一行,是密码解密密钥,必须同批留

ssh 会话引用的私钥是外部文件,不在 remmina 目录里,需另行确认有备份。~/.local/share/remmina 下的 .backup-* 是 remmina 自带自动备份,随目录一起拷走即可。

手动兜底(snap.sh 不可用时)

TS=$(date +%Y%m%d_%H%M%S)
DST=".../remmin备份/$TS"
mkdir -p "$DST" \
  && cp -a ~/.local/share/remmina "$DST/remmina-data" \
  && cp -a ~/.config/remmina      "$DST/remmina-config"
# 恢复:先关 remmina(恢复前建议先跑一遍上面给现状打快照),再拷回去
SRC=".../remmin备份/20261022_115640"
cp -a "$SRC/remmina-data/."   ~/.local/share/remmina/ \
  && cp -a "$SRC/remmina-config/." ~/.config/remmina/

NixOS 下终端 PATH 极简,若报未找到命令,把 date、cp、ls、mkdir 换成 /run/current-system/sw/bin/ 下的绝对路径,用法不变。完整手册见备份根 readme.md(第一部分 snap.sh 命令手册,第二部分原始手动操作手册)。

实测记录(2026-10-22)

首备 20261022_115640(手动命令,13 个 .remmina + remmina.pref,已核对);snap.sh 快照 20261022_124257(13 个);从它恢复一次,自动生成 pre-restore-20261022_124303,live 仍 13 个,无损往返。台账:hermes-nixos-workspace docs/mobaxterm-to-remmina.md §8。

/s500data/软件数据备份/Nixos备份/remmin备份/snap.sh

#!/usr/bin/env bash
# snap.sh —— remmina 会话快照 / 恢复(免安装,在本目录下 ./snap.sh 执行)
#
#   ./snap.sh                        从 live 数据生成一个 YYYYMMDD_HHMMSS 快照子目录
#   ./snap.sh <快照名>               用该快照的数据覆盖回 live(快照名 / 相对路径 / 全路径都认)
#   ./snap.sh list                   列出现有快照
#
# 快照结构:<TS>/remmina-data/ + <TS>/remmina-config/
#   remmina-data   ≙ ~/.local/share/remmina/*.remmina(会话)
#   remmina-config ≙ ~/.config/remmina/remmina.pref(含 secret= 密码解密密钥,必须同批留)
set -euo pipefail
export PATH="/run/current-system/sw/bin:/usr/bin:/bin"

ROOT="$(cd "$(dirname "$0")" && pwd)"
DATA_SRC="$HOME/.local/share/remmina"
CONF_SRC="$HOME/.config/remmina"

die() { echo "ERR: $*" >&2; exit 1; }

do_backup() {
    local tag="${1:-$(date +%Y%m%d_%H%M%S)}"
    local dst="$ROOT/$tag"
    [ -e "$dst" ] && die "已存在:$dst"
    [ -d "$DATA_SRC" ] || die "live 会话目录不存在:$DATA_SRC"
    [ -d "$CONF_SRC" ] || die "live 配置目录不存在:$CONF_SRC"
    mkdir -p "$dst"
    cp -a "$DATA_SRC" "$dst/remmina-data"
    cp -a "$CONF_SRC" "$dst/remmina-config"
    local n; n=$(ls "$dst/remmina-data" | wc -l)
    [ -f "$dst/remmina-config/remmina.pref" ] || die "快照缺 remmina.pref,异常"
    echo "OK 快照:$dst(会话文件 $n 个)"
}

resolve_snap() {
    local arg="$1" cand=""
    if [[ "$arg" == /* ]]; then cand="$arg"                    # 全路径
    elif [[ "$arg" == */* ]]; then cand="$ROOT/$arg"; [ -d "$cand" ] || cand="$(pwd)/$arg"
    else cand="$ROOT/$arg"; fi                                # 裸快照名
    [ -d "$cand" ] || die "快照目录不存在:$arg(解析为 $cand)"
    [ -d "$cand/remmina-data" ] && [ -d "$cand/remmina-config" ] \
        || die "不是有效快照(缺 remmina-data/remmina-config):$cand"
    echo "$cand"
}

do_restore() {
    local src; src="$(resolve_snap "$1")"
    if pgrep -x remmina >/dev/null 2>&1; then
        echo "WARN: remmina 正在运行,恢复前需退出,否则内存数据会盖掉恢复结果。"
        read -r -p "退出 remmina 并继续?[y/N] " ans
        [[ "$ans" == [yY] ]] || die "已取消"
        pkill -x remmina
        for _ in $(seq 1 20); do pgrep -x remmina >/dev/null 2>&1 || break; sleep 0.5; done
        pgrep -x remmina >/dev/null 2>&1 && die "remmina 未能退出,手动关掉再试"
    fi
    # 先给现状打快照,回滚面
    local tag="pre-restore-$(date +%Y%m%d_%H%M%S)"
    echo "先给现状打快照:$tag"
    do_backup "$tag"
    cp -a "$src/remmina-data/."   "$DATA_SRC/"
    cp -a "$src/remmina-config/." "$CONF_SRC/"
    local n; n=$(ls "$DATA_SRC"/*.remmina 2>/dev/null | wc -l)
    echo "OK 已从 $src 恢复(当前会话 $n 个)。重开 remmina 生效;回滚用:./snap.sh $tag"
}

do_list() {
    for d in "$ROOT"/*/; do
        [ -d "$d" ] || continue
        local b; b="$(basename "$d")"
        [[ "$b" == .* ]] && continue
        if [ -d "$d/remmina-data" ]; then
            echo "$b  (会话 $(ls "$d/remmina-data" | wc -l) 个)"
        else
            echo "$b  (非快照目录)"
        fi
    done
}

case "${1:-}" in
    "")         do_backup ;;
    list|ls)    do_list ;;
    -h|--help|help)
        sed -n '2,11p' "$0" ;;
    *)          do_restore "$1" ;;
esac