[Internal testing communication] 抛砖分享一下在v25下自动创建ostree快照的脚本
Tofloor
poster avatar
Tent
deepin
2025-09-26 11:54
Author

背景描述

之前我在使用btrfs的时候,参考timeshift的思路,自己写了个自动btrfs快照的脚本。

现在换到v25系统了,把原来的脚本改了下,改成了自动创建ostree快照的脚本。

本次抛砖分享下。

整体思路

1、使用root的定时任务,执行创建快照的脚本。

2、每小时执行一次,如果已有当天的快照,则不再创建新快照,否则创建一个以日期时间命名的快照。

3、创建成功或失败,均弹出一次通知信息,以方便确认是否成功创建快照。

4、创建快照成功后,删除旧快照,只留存3个“自动”快照(不删除手工创建的快照)。

5、把执行信息输出个日志文件,方便创建失败时进行排查。

具体实现

root用户的定时任务:

3 * * * * bash /root/deepin-daily-snapshot.sh > /root/snapshot.log 2>&1

脚本内容(deepin-daily-snapshot.sh):

#!/bin/bash

set -x

unalias -a

GET_CU_DATE="`date +%Y%m%d`"

# 如果已有当天快照,则不再建新快照
if `/usr/sbin/deepin-immutable-ctl snapshot list | grep -q "Auto${GET_CU_DATE}"`; then
    exit 0
fi

SNAPSHOTNAME="Auto`date +%Y%m%d-%H%M%S`"

/usr/sbin/deepin-immutable-ctl snapshot create "${SNAPSHOTNAME}" "SnapShot-For-${GET_CU_DATE}"

if `/usr/sbin/deepin-immutable-ctl snapshot list | grep -q "${SNAPSHOTNAME}"`; then
    for USER_NAME in `who | awk '{print $1}' | uniq`; do
        USER_NAME_STATE=`dbus-send --system --dest=org.freedesktop.login1 --print-reply /org/freedesktop/login1/user/_$(id -u ${USER_NAME}) org.freedesktop.DBus.Properties.Get string:org.freedesktop.login1.User string:State | grep active`
        if [ -n "${USER_NAME_STATE}" ]; then
            sudo -u ${USER_NAME} \
            DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u ${USER_NAME})/bus \
            notify-send -t 0 -i dialog-ok '今日快照创建成功!'
        fi
    done
else
    for USER_NAME in `who | awk '{print $1}' | uniq`; do
        USER_NAME_STATE=`dbus-send --system --dest=org.freedesktop.login1 --print-reply /org/freedesktop/login1/user/_$(id -u ${USER_NAME}) org.freedesktop.DBus.Properties.Get string:org.freedesktop.login1.User string:State | grep active`
        if [ -n "${USER_NAME_STATE}" ]; then
            sudo -u ${USER_NAME} \
            DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u ${USER_NAME})/bus \
            notify-send -t 0 -i dialog-error '今日快照创建失败……'
        fi
    done
    exit 0
fi

# 保留3个自动快照(不自动清理手动创建的快照)
while [ `/usr/sbin/deepin-immutable-ctl snapshot list | grep Auto | wc -l` -gt 3 ]; do
    /usr/sbin/deepin-immutable-ctl snapshot delete `/usr/sbin/deepin-immutable-ctl snapshot list | grep Auto | head -1 | awk '{print $1}'`
done

相关命令

列出快照:

deepin-immutable-ctl snapshot list

创建快照:

sudo deepin-immutable-ctl snapshot create 快照名称 快照描述

回滚快照:

sudo deepin-immutable-ctl snapshot rollback 快照ID

删除快照:

sudo deepin-immutable-ctl snapshot delete 快照ID
Reply Favorite View the author
All Replies
LiuYongzhang
deepin
2025-09-26 12:58
#1

学习了

image.png

Reply View the author
kookboy
deepin
2025-09-26 13:03
#2

有谁在楼主的基础上,创建个UI版本的来呀?给懒得打那些命令的人一个福利。applaud

Reply View the author
LiuYongzhang
deepin
2025-09-26 13:59
#3

把这个脚本喂给Ai 几次后最终是这样

#!/bin/bash

增强版 ostree 快照管理脚本 v1.6

最后更新: 2025-09-26

功能: 自动创建/验证/清理系统快照,支持显示快照列表

===== 配置区域 =====

declare -r LOG_FILE="/var/log/ostree-snapshots.log"

declare -ri KEEP_SNAPSHOTS=3

declare -r LOCK_FILE="/var/run/ostree-snapshot.lock"

declare -r SNAPSHOT_PREFIX="Auto"

===================

初始化严格模式

set -euo pipefail

trap 'log "ERROR: 脚本异常终止"; exit 1' ERR

export PATH="/usr/sbin:/usr/bin:/sbin:/bin"

显示帮助信息

show_help() {

cat << EOF

用法: $(basename "$0") [选项]

选项:

-l, --list 显示所有快照列表

-h, --help 显示此帮助信息

无参数 执行默认操作(创建当日快照并清理旧快照)

功能:

自动管理系统快照,包括创建每日快照、清理旧快照,

并可查看当前系统中的所有快照信息。

EOF

}

日志记录函数

log() {

local log\_entry="[\$(date '+%Y-%m-%d %H:%M:%S')] \$1"

echo "\$log\_entry" | tee -a "\$LOG\_FILE"

}

获取活跃用户列表

get_active_users() {

who | awk '{print \$1}' | sort -u | while read -r user; do

    if loginctl show-user "\$user" | grep -q 'IdleHint=no'; then

        echo "\$user"

    fi

done

}

发送桌面通知

notify_users() {

local status=\$1

local message=\$2


while read -r user; do

    local bus\_path="/run/user/\$(id -u "\$user")/bus"


    [ -S "\$bus\_path" ] || continue


    case "\$status" in

        success)

            sudo -u "\$user" DBUS\_SESSION\_BUS\_ADDRESS="unix:path=\$bus\_path" \\

                notify-send -t 5000 -i dialog-ok "系统快照" "\$message"

            ;;

        failure)

            sudo -u "\$user" DBUS\_SESSION\_BUS\_ADDRESS="unix:path=\$bus\_path" \\

                notify-send -t 10000 -i dialog-error "系统快照" "\$message"

            ;;

        info)

            sudo -u "\$user" DBUS\_SESSION\_BUS\_ADDRESS="unix:path=\$bus\_path" \\

                notify-send -t 3000 -i dialog-information "系统快照" "\$message"

            ;;

    esac

done < <(get\_active\_users)

}

显示快照列表

list_snapshots() {

echo "==================== 系统快照列表 ===================="

deepin-immutable-ctl snapshot list


# 统计自动快照数量

local snapshot\_list=\$(deepin-immutable-ctl snapshot list | tail -n +2)

local -a auto\_snapshots=()


while IFS= read -r line; do

    local snap\_name=\$(echo "\$line" | awk '{print \$2}')

    if [[ "\$snap\_name" == "\${SNAPSHOT\_PREFIX}-"\* ]]; then

        auto\_snapshots+=("\$line")

    fi

done <<< "\$snapshot\_list"


local total\_count=\$(echo "\$snapshot\_list" | wc -l)

local auto\_count=\${#auto\_snapshots[@]}

local manual\_count=\$((total\_count - auto\_count))


echo -e "\\n统计信息:"

echo "  总快照数: \$total\_count"

echo "  自动创建的快照数: \$auto\_count"

echo "  手动创建的快照数: \$manual\_count"

echo "  配置保留自动快照数: \$KEEP\_SNAPSHOTS"

echo "======================================================"

}

创建当日快照

create_daily_snapshot() {

local date\_stamp=\$(date +%Y%m%d)

local timestamp=\$(date +%Y%m%d-%H%M%S)

local snap\_name="\${SNAPSHOT\_PREFIX}-\${timestamp}"

local snap\_desc="每日快照-\$(date +%F)"


# 检查当日快照是否存在

if deepin-immutable-ctl snapshot list | grep -q "\${SNAPSHOT\_PREFIX}-\${date\_stamp}"; then

    log "INFO: 当日快照已存在,跳过创建"

    return 3  # 特殊返回码表示跳过

fi


# 创建新快照

log "创建快照: \${snap\_name} (\${snap\_desc})"

if ! deepin-immutable-ctl snapshot create "\$snap\_name" "\$snap\_desc"; then

    log "ERROR: 快照创建命令执行失败"

    return 1

fi


# 验证快照是否创建成功

if deepin-immutable-ctl snapshot list | grep -q "\$snap\_name"; then

    log "SUCCESS: 快照创建成功"

    return 0

else

    log "CRITICAL: 快照创建后验证失败"

    return 2

fi

}

清理旧快照

clean_old_snapshots() {

log "开始清理旧快照 (保留最新 \${KEEP\_SNAPSHOTS} 个)"


# 获取快照列表(跳过标题行)

local snapshot\_list=\$(deepin-immutable-ctl snapshot list | tail -n +2)


if [ -z "\$snapshot\_list" ]; then

    log "INFO: 快照列表为空,无需清理"

    return

fi


# 提取自动快照

local -a auto\_snapshots=()

while IFS= read -r line; do

    # 提取快照名称(第二列)

    local snap\_name=\$(echo "\$line" | awk '{print \$2}')


    # 检查是否以 SNAPSHOT\_PREFIX 开头

    if [[ "\$snap\_name" == "\${SNAPSHOT\_PREFIX}-"\* ]]; then

        auto\_snapshots+=("\$line")

    fi

done <<< "\$snapshot\_list"


local total\_count=\${#auto\_snapshots[@]}


if (( total\_count == 0 )); then

    log "INFO: 没有自动快照,无需清理"

    return

fi


# 计算需要删除的数量

local delete\_count=\$((total\_count - KEEP\_SNAPSHOTS))

if (( delete\_count <= 0 )); then

    log "INFO: 无需删除快照 (当前 \${total\_count} 个自动快照)"

    return

fi


log "计划删除 \${delete\_count} 个旧快照 (总共 \${total\_count} 个自动快照)"


# 按时间排序(第四列是日期时间)

mapfile -t sorted\_snapshots < <(printf "%s\\n" "\${auto\_snapshots[@]}" | sort -k4)


# 删除最早创建的快照

for (( i=0; i        local snap\_line="\${sorted\_snapshots[\$i]}"

    local snap\_id=\$(echo "\$snap\_line" | awk '{print \$1}')

    local snap\_name=\$(echo "\$snap\_line" | awk '{print \$2}')


    log "删除旧快照: \${snap\_name} (ID: \${snap\_id})"

    if deepin-immutable-ctl snapshot delete "\$snap\_id"; then

        log "SUCCESS: 成功删除快照"

    else

        log "WARNING: 删除快照失败"

    fi

done


log "清理完成: 删除了 \${delete\_count} 个旧快照"

}

主控制流程

main() {

# 处理命令行参数

if [[ \$# -gt 0 ]]; then

    case "\$1" in

        -l|--list)

            list\_snapshots

            exit 0

            ;;

        -h|--help)

            show\_help

            exit 0

            ;;

        \*)

            echo "错误: 未知选项 '\$1'"

            show\_help

            exit 1

            ;;

    esac

fi


log "=== 启动快照任务 ==="


# 检查文件锁

if [[ -e "\$LOCK\_FILE" ]]; then

    lock\_pid=\$(cat "\$LOCK\_FILE" 2>/dev/null)

    if ps -p "\$lock\_pid" &>/dev/null; then

        log "WARNING: 上一个任务仍在运行 (PID \$lock\_pid),跳过本次执行"

        exit 0

    else

        log "清理过期的锁文件"

        rm -f "\$LOCK\_FILE"

    fi

fi


# 获取锁

echo \$\$ > "\$LOCK\_FILE"

trap 'rm -f "\$LOCK\_FILE"; log "清理锁文件"' EXIT


# 快照创建状态

local create\_status=0

create\_daily\_snapshot || create\_status=\$?


# 清理旧快照(无论创建是否成功)

clean\_old\_snapshots


# 通知用户

case \$create\_status in

    0) 

        notify\_users "success" "系统快照创建成功" 

        log "=== 快照任务完成 ==="

        exit 0

        ;;

    1) 

        notify\_users "failure" "快照创建命令执行失败" 

        log "ERROR: === 快照任务失败 ==="

        exit 1

        ;;

    2) 

        notify\_users "failure" "快照创建后验证失败" 

        log "ERROR: === 快照任务失败 ==="

        exit 1

        ;;

    3) 

        notify\_users "info" "当日快照已存在,已跳过创建" 

        log "=== 快照任务完成 (已跳过创建) ==="

        exit 0

        ;;

    \*) 

        notify\_users "failure" "快照任务出现未知错误 (状态码: \$create\_status)" 

        log "ERROR: === 快照任务出现未知错误 ==="

        exit 1

        ;;

esac

}

启动主程序

main "$@"

Reply View the author
Tent
deepin
2025-09-26 14:21
#4
LiuYongzhang

把这个脚本喂给Ai 几次后最终是这样

#!/bin/bash

增强版 ostree 快照管理脚本 v1.6

最后更新: 2025-09-26

功能: 自动创建/验证/清理系统快照,支持显示快照列表

===== 配置区域 =====

declare -r LOG_FILE="/var/log/ostree-snapshots.log"

declare -ri KEEP_SNAPSHOTS=3

declare -r LOCK_FILE="/var/run/ostree-snapshot.lock"

declare -r SNAPSHOT_PREFIX="Auto"

===================

初始化严格模式

set -euo pipefail

trap 'log "ERROR: 脚本异常终止"; exit 1' ERR

export PATH="/usr/sbin:/usr/bin:/sbin:/bin"

显示帮助信息

show_help() {

cat << EOF

用法: $(basename "$0") [选项]

选项:

-l, --list 显示所有快照列表

-h, --help 显示此帮助信息

无参数 执行默认操作(创建当日快照并清理旧快照)

功能:

自动管理系统快照,包括创建每日快照、清理旧快照,

并可查看当前系统中的所有快照信息。

EOF

}

日志记录函数

log() {

local log\_entry="[\$(date '+%Y-%m-%d %H:%M:%S')] \$1"

echo "\$log\_entry" | tee -a "\$LOG\_FILE"

}

获取活跃用户列表

get_active_users() {

who | awk '{print \$1}' | sort -u | while read -r user; do

    if loginctl show-user "\$user" | grep -q 'IdleHint=no'; then

        echo "\$user"

    fi

done

}

发送桌面通知

notify_users() {

local status=\$1

local message=\$2


while read -r user; do

    local bus\_path="/run/user/\$(id -u "\$user")/bus"


    [ -S "\$bus\_path" ] || continue


    case "\$status" in

        success)

            sudo -u "\$user" DBUS\_SESSION\_BUS\_ADDRESS="unix:path=\$bus\_path" \\

                notify-send -t 5000 -i dialog-ok "系统快照" "\$message"

            ;;

        failure)

            sudo -u "\$user" DBUS\_SESSION\_BUS\_ADDRESS="unix:path=\$bus\_path" \\

                notify-send -t 10000 -i dialog-error "系统快照" "\$message"

            ;;

        info)

            sudo -u "\$user" DBUS\_SESSION\_BUS\_ADDRESS="unix:path=\$bus\_path" \\

                notify-send -t 3000 -i dialog-information "系统快照" "\$message"

            ;;

    esac

done < <(get\_active\_users)

}

显示快照列表

list_snapshots() {

echo "==================== 系统快照列表 ===================="

deepin-immutable-ctl snapshot list


# 统计自动快照数量

local snapshot\_list=\$(deepin-immutable-ctl snapshot list | tail -n +2)

local -a auto\_snapshots=()


while IFS= read -r line; do

    local snap\_name=\$(echo "\$line" | awk '{print \$2}')

    if [[ "\$snap\_name" == "\${SNAPSHOT\_PREFIX}-"\* ]]; then

        auto\_snapshots+=("\$line")

    fi

done <<< "\$snapshot\_list"


local total\_count=\$(echo "\$snapshot\_list" | wc -l)

local auto\_count=\${#auto\_snapshots[@]}

local manual\_count=\$((total\_count - auto\_count))


echo -e "\\n统计信息:"

echo "  总快照数: \$total\_count"

echo "  自动创建的快照数: \$auto\_count"

echo "  手动创建的快照数: \$manual\_count"

echo "  配置保留自动快照数: \$KEEP\_SNAPSHOTS"

echo "======================================================"

}

创建当日快照

create_daily_snapshot() {

local date\_stamp=\$(date +%Y%m%d)

local timestamp=\$(date +%Y%m%d-%H%M%S)

local snap\_name="\${SNAPSHOT\_PREFIX}-\${timestamp}"

local snap\_desc="每日快照-\$(date +%F)"


# 检查当日快照是否存在

if deepin-immutable-ctl snapshot list | grep -q "\${SNAPSHOT\_PREFIX}-\${date\_stamp}"; then

    log "INFO: 当日快照已存在,跳过创建"

    return 3  # 特殊返回码表示跳过

fi


# 创建新快照

log "创建快照: \${snap\_name} (\${snap\_desc})"

if ! deepin-immutable-ctl snapshot create "\$snap\_name" "\$snap\_desc"; then

    log "ERROR: 快照创建命令执行失败"

    return 1

fi


# 验证快照是否创建成功

if deepin-immutable-ctl snapshot list | grep -q "\$snap\_name"; then

    log "SUCCESS: 快照创建成功"

    return 0

else

    log "CRITICAL: 快照创建后验证失败"

    return 2

fi

}

清理旧快照

clean_old_snapshots() {

log "开始清理旧快照 (保留最新 \${KEEP\_SNAPSHOTS} 个)"


# 获取快照列表(跳过标题行)

local snapshot\_list=\$(deepin-immutable-ctl snapshot list | tail -n +2)


if [ -z "\$snapshot\_list" ]; then

    log "INFO: 快照列表为空,无需清理"

    return

fi


# 提取自动快照

local -a auto\_snapshots=()

while IFS= read -r line; do

    # 提取快照名称(第二列)

    local snap\_name=\$(echo "\$line" | awk '{print \$2}')


    # 检查是否以 SNAPSHOT\_PREFIX 开头

    if [[ "\$snap\_name" == "\${SNAPSHOT\_PREFIX}-"\* ]]; then

        auto\_snapshots+=("\$line")

    fi

done <<< "\$snapshot\_list"


local total\_count=\${#auto\_snapshots[@]}


if (( total\_count == 0 )); then

    log "INFO: 没有自动快照,无需清理"

    return

fi


# 计算需要删除的数量

local delete\_count=\$((total\_count - KEEP\_SNAPSHOTS))

if (( delete\_count <= 0 )); then

    log "INFO: 无需删除快照 (当前 \${total\_count} 个自动快照)"

    return

fi


log "计划删除 \${delete\_count} 个旧快照 (总共 \${total\_count} 个自动快照)"


# 按时间排序(第四列是日期时间)

mapfile -t sorted\_snapshots < <(printf "%s\\n" "\${auto\_snapshots[@]}" | sort -k4)


# 删除最早创建的快照

for (( i=0; i        local snap\_line="\${sorted\_snapshots[\$i]}"

    local snap\_id=\$(echo "\$snap\_line" | awk '{print \$1}')

    local snap\_name=\$(echo "\$snap\_line" | awk '{print \$2}')


    log "删除旧快照: \${snap\_name} (ID: \${snap\_id})"

    if deepin-immutable-ctl snapshot delete "\$snap\_id"; then

        log "SUCCESS: 成功删除快照"

    else

        log "WARNING: 删除快照失败"

    fi

done


log "清理完成: 删除了 \${delete\_count} 个旧快照"

}

主控制流程

main() {

# 处理命令行参数

if [[ \$# -gt 0 ]]; then

    case "\$1" in

        -l|--list)

            list\_snapshots

            exit 0

            ;;

        -h|--help)

            show\_help

            exit 0

            ;;

        \*)

            echo "错误: 未知选项 '\$1'"

            show\_help

            exit 1

            ;;

    esac

fi


log "=== 启动快照任务 ==="


# 检查文件锁

if [[ -e "\$LOCK\_FILE" ]]; then

    lock\_pid=\$(cat "\$LOCK\_FILE" 2>/dev/null)

    if ps -p "\$lock\_pid" &>/dev/null; then

        log "WARNING: 上一个任务仍在运行 (PID \$lock\_pid),跳过本次执行"

        exit 0

    else

        log "清理过期的锁文件"

        rm -f "\$LOCK\_FILE"

    fi

fi


# 获取锁

echo \$\$ > "\$LOCK\_FILE"

trap 'rm -f "\$LOCK\_FILE"; log "清理锁文件"' EXIT


# 快照创建状态

local create\_status=0

create\_daily\_snapshot || create\_status=\$?


# 清理旧快照(无论创建是否成功)

clean\_old\_snapshots


# 通知用户

case \$create\_status in

    0) 

        notify\_users "success" "系统快照创建成功" 

        log "=== 快照任务完成 ==="

        exit 0

        ;;

    1) 

        notify\_users "failure" "快照创建命令执行失败" 

        log "ERROR: === 快照任务失败 ==="

        exit 1

        ;;

    2) 

        notify\_users "failure" "快照创建后验证失败" 

        log "ERROR: === 快照任务失败 ==="

        exit 1

        ;;

    3) 

        notify\_users "info" "当日快照已存在,已跳过创建" 

        log "=== 快照任务完成 (已跳过创建) ==="

        exit 0

        ;;

    \*) 

        notify\_users "failure" "快照任务出现未知错误 (状态码: \$create\_status)" 

        log "ERROR: === 快照任务出现未知错误 ==="

        exit 1

        ;;

esac

}

启动主程序

main "$@"

之前我还搞过一个蓝牙连手机,断连后自动锁屏的脚本,当时也是发给ai让优化来着,结果规范是规范了,但也变得非常复杂了 。

就本次这个脚本来看,确实给优化复杂了。joy

Reply View the author
晚秋(lateautumn)
Moderator
2025-09-26 15:26
#5

学习了,谢谢分享。

Reply View the author
叶落无语
deepin
2025-09-26 16:05
#6

学习了,谢谢分享。

Reply View the author
LiuYongzhang
deepin
2025-09-26 16:06
#7

image.png

Reply View the author
Tent
deepin
2025-09-26 17:08
#8
LiuYongzhang

image.png

这个dialog-ok和dialog-error挺好,我觉得我可以照这个改一下

Reply View the author
zccrs
deepin
2025-09-26 18:10
#9

666,太棒了

Reply View the author