前言

昨天晚上看着手机突发奇想,准备尝试搞搞让手机作为扩展屏扩展一波。看到这篇文章想必就知道我改图形界面配置搞崩了!P-28.jpg

第一次就是加了这两行:

8113e45b6f34555cf6b0dbc86625dfb5.jpg 怕是给我主屏幕搞到外面那个虚拟屏幕了……因为这个锤子文件我记着是通过 nvidia-xconfig 创建出来的,遂给它直接删了,然后重启就好了~

这时候 bro 还觉得自己贼牛逼,在朋友表示 “老实点得了” 之后,又去尝试了一次其他方式(指同一个文件换了一部分配置去修改)。

然后,然后就没有然后了……删除 xorg.conf 都没得救了……并且最痛苦的是,越是自动修复,越是炸!!!P-21.jpg

4d3453bb11592ee9189120a615f57294.jpg

心路历程展示:

Pasted image 20241201145519.png

好在十几天前用 diskGenious 分盘的时候有“顺便”为 Ubuntu 系统盘做备份,重要的数据也不在那个盘里~!!P-29.png

坏消息是,我脑子抽了,没有把一些数据一类的文件从坏掉的 Ubuntu 盘里面拿出来,直接执行了恢复……P-3.jpg

为了避免二次出现这种锤头事情,本喵决定探索一下系统的增量备份方案~ P-12.jpg

需求

我是 ext4 的盘做 Ubuntu 的系统盘,数据考虑到使用场景放在 NTFS 类型的盘中。所以我需要一个备份方案能够将我的 ext4 盘中的数据保存进 NTFS 盘中。

Deja Dup

基于“Deja Dup”的增量备份

没办法在运行系统的时候备份系统盘

这个工具是系统自带的,反正 win 搜索 “备份”,就有结果了。不过问题在于,没办法为我现在正在用的磁盘创建映像勒! Pasted image 20241201151426.png

指向原始笔记的链接

Timeshift

基于“Timeshift”的增量备份

无法将快照保存到 NTFS 文件系统中

“Timeshift” 的话,使用起来很方便,基本就是傻瓜式操作。打开向导,然后……然后对着看、对这选就对了。

RSYNC 快照

  • 快照是通过使用rsync创建系统文件的副本,以及从以前的快照中硬链接未更改的文件来创建的。
  • 在创建首个快照时,所有文件都会被复制后续的快照将在此基础上增加,未更改的文件会硬链接到以前的快照。
  • 快照可以保存到任一个格式化为Linux文件系统的磁盘。请将快照保存到非系统或外部磁盘,这样即使系统磁盘损坏或重新格式化,也可恢复系统。
  • 您可以排除文件和目录以节省磁盘空间。

它能够快速选择需要保存计划、需要保存的用户、筛选需要保存的文件。

Pasted image 20241201151844.png Pasted image 20241201151657.png Pasted image 20241201151756.png

很可惜,它的快照只能够保存在 Linux文件系统的磁盘(sad!!)。

指向原始笔记的链接

Duplicity

基于“Duplicity”的增量备份

上古老工具,链式增量备份,没办法删除前面的增量记录

当google 第一页发现2013年的文章的时候,我就知道自己被 GPT 骗了(但还是去尝试了一番,了解到了 GNG加密工具)

Duplicity 是一个支持增量备份的工具,可以将备份存储到 NTFS,同时压缩和加密数据。

备份步骤

  1. 安装 Duplicity
sudo apt install duplicity
  1. 执行增量备份
sudo duplicity backup --allow-source-mismatch \
  --exclude /dev \
  --exclude /proc \
  --exclude /sys \
  --exclude /tmp \
  --exclude /run \
  --exclude /mnt \
  --exclude /media \
  --exclude /lost+found \
  --no-encryption \
  / \
  "file://<目标位置>"

排除目录的解释

  1. /dev:这个目录包含了设备文件,是 Linux 系统用来与硬件交互的接口。设备文件并不是常规文件,它们代表硬件设备(如磁盘、终端、内存等)。因此,/dev 目录不需要备份。

  2. /proc:这个目录是一个虚拟文件系统,包含了关于系统和进程的实时信息(例如 CPU、内存、设备状态等)。这些文件是动态生成的,并不是常规存储的文件,因此无需备份。

  3. /sys/sys 也是一个虚拟文件系统,提供了有关内核、设备驱动程序和硬件的实时信息。它不包含常规数据,因此也不需要备份。

  4. /tmp:这个目录用于存放临时文件,在系统运行时生成,系统重启后通常会被清空。因为这些文件不持久化,所以也不需要备份。

  5. /run:这个目录包含了运行时数据(如正在运行的服务的 PID 文件、套接字等),这些数据在系统重启时会丢失,因此不需要备份。

  6. /mnt:通常这个目录用于挂载外部存储设备(如硬盘、U 盘等)。duplicity 默认排除它,因为这些存储设备上的数据通常不需要备份,或者你可能希望将其视为单独的备份目标。

  7. /media:这个目录用于挂载可移动存储设备(如 USB 驱动器、光盘等)。和 /mnt 一样,通常你不需要备份这些外部存储设备中的内容。

  8. /lost+found:这个目录通常存在于文件系统的根目录下,用于存放被系统错误或文件系统损坏时恢复的文件。这个目录的内容也是动态生成的,通常不需要备份。

GPG加密

Duplicity 内置 GPG加密 ,首先使用它来生成新的密钥对:

gpg --full-generate-key

然后跟着提示一路往下就对了~下面是我的过程(隐去敏感信息):

gpg (GnuPG) 2.4.4; Copyright (C) 2024 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
 
请选择您要使用的密钥类型:
   (1) RSA RSA 
   (2) DSA Elgamal 
   (3) DSA(仅用于签名)
   (4) RSA(仅用于签名)
   (9) ECC(签名和加密) *默认*
  (10) ECC(仅用于签名)
  (14)卡中现有密钥 
您的选择是? 
请选择您想要使用的椭圆曲线:
   (1) Curve 25519 *默认*
   (4) NIST P-384
   (6) Brainpool P-256
您的选择是? 
请设定这个密钥的有效期限。
         0 = 密钥永不过期
      <n>  = 密钥在 n 天后过期
      <n>w = 密钥在 n 周后过期
      <n>m = 密钥在 n 月后过期
      <n>y = 密钥在 n 年后过期
密钥的有效期限是?(0) 
密钥永远不会过期
这些内容正确吗? (y/N) y
GnuPG 需要构建用户标识以辨认您的密钥。
 
真实姓名: name
电子邮件地址:mail 
注释: 
您选定了此用户标识:
    “name <mail>
 
更改姓名(N)、注释(C)、电子邮件地址(E)或确定(O)/退出(Q)? O
我们需要生成大量的随机字节。在质数生成期间做些其他操作(敲打键盘
、移动鼠标、读写硬盘之类的)将会是一个不错的主意;这会让随机数
发生器有更好的机会获得足够的熵。
我们需要生成大量的随机字节。在质数生成期间做些其他操作(敲打键盘
、移动鼠标、读写硬盘之类的)将会是一个不错的主意;这会让随机数
发生器有更好的机会获得足够的熵。
gpg: 目录‘/home/your_user/.gnupg/openpgp-revocs.d’已创建
gpg: 吊销证书已被存储为‘/home/your_user/.gnupg/openpgp-revocs.d/**.rev’
公钥和私钥已经生成并被签名。

输入 gpg --list-secret-keys --keyid-format LONG 查看密钥ID,输出大概是下面这样的:

/home/your_user/.gnupg/secring.gpg
------------------------------
sec   4096R/<YOUR_KEY_ID> 2019-12-12 [expires: 2021-12-12]
uid                          Your Name <you@example.com>
ssb   4096R/<YOUR_SUBKEY_ID> 2019-12-12

`

执行加密备份

上次系统搞崩溃用户数据其实是没有任何影响的,所以我的备份策略是:系统每天备份、用户数据每周备份。

sudo PASSPHRASE=$(sudo cat ~/.gnupg/passphrase.txt) duplicity backup --gpg-options "--keyring /home/your_user/.gnupg/pubring.kbx" --skip-if-no-change /etc file:///path/to/etc
 
sudo PASSPHRASE=$(sudo cat ~/.gnupg/passphrase.txt) duplicity backup --gpg-options "--keyring /home/your_user/.gnupg/pubring.kbx" --skip-if-no-change /bin file:///path/to/bin
 
sudo PASSPHRASE=$(sudo cat ~/.gnupg/passphrase.txt) duplicity backup --gpg-options "--keyring /home/your_user/.gnupg/pubring.kbx" --skip-if-no-change /lib file:///path/to/lib
 
sudo PASSPHRASE=$(sudo cat ~/.gnupg/passphrase.txt) duplicity backup --gpg-options "--keyring /home/your_user/.gnupg/pubring.kbx" --skip-if-no-change /usr file:///path/to/usr
 
sudo PASSPHRASE=$(sudo cat ~/.gnupg/passphrase.txt) duplicity backup --gpg-options "--keyring /home/your_user/.gnupg/pubring.kbx" --skip-if-no-change /var file:///path/to/var

运行的时候依然需要每次输入 GPG 密码短语 。为了能够结合自动化备份功能,我们将密码短语存储在某一个文件中,然后让 duplicity 使用 --gpg-options 选项来指定密码文件。

duplicity backup --gpg-options "--passphrase-file /path/to/passphrase_file" <source> <destination>

恢复步骤

  1. 恢复特定时间点的数据

    duplicity restore --time <> "file://<备份位置>" <要恢复的目>

    比如:

    duplicity restore --time 2024-11-20T00:00:00 "file://<备份位置>" /

不行了不行了,tmd这个太复杂了……实在是接受不了这个增量保存记录没办法删除的鬼畜玩意儿……他这个是,后面的记录是基于前面的记录做修改的,那么问题来了,假设我不需要3次以前的增量记录了呢?我还删除不了,貌似只能恢复、重新备份?拿不是太麻烦了……(而且资料也不多)

指向原始笔记的链接

rsync

rsync 备份内容有问题,至少我备份的那几个文件夹直接恢复是无效的 【 详见下一篇文章

这玩意儿就是前面 timeshift 用的工具……被前面误导,导致直到最后才尝试去看它,发现可以存到 NTFS文件系统

基于“rsync”的增量备份

简介

rsync 是一个功能强大的文件同步工具,支持高效的数据传输。它能够:

  • 进行全量或增量备份。
  • 支持增量备份,这意味着只有自上次备份以来发生变化的文件才会被传输。
  • 提供 --link-dest 选项,支持硬链接(hard link),让你能高效地存储多个备份版本。

增量备份的关键:

  • rsync 通过硬链接--link-dest)避免重复存储没有变化的文件,显著节省存储空间。

增量备份是在前一次备份的基础上去做的

全量备份

rsync 全量备份:

sudo rsync -a --progress --delete /etc /bin /lib /usr /sbin /to/your/path/backup/system_backup/
  • -a归档模式
    • 递归复制目录:会复制整个目录,包括其中的子目录和文件。
    • 保留文件属性:保持源文件的权限、时间戳、符号链接、用户和组信息等。
    • 保留设备文件(对于特殊设备文件和管道等)。
    • 保留符号链接:如果源是符号链接,rsync 会保留链接本身,而不是复制链接指向的内容。
  • --progress :显示文件传输的进度信息(每个文件的传输进度、已复制的数据量、传输速度等)。
  • --delete :删除目标目录中那些源目录中不存在的文件。常用于保持源和目标目录的完全同步。

脚本实现全量备份和增量备份

我们编写了一个 Bash 脚本来自动化全量和增量备份的过程,这里我仅保留最后3次的记录。

核心步骤如下:

#!/bin/bash
 
# 定义备份目录
BACKUP_DIR="/to/your/path/backup"
BACKUP_TYPE_DIR="$BACKUP_DIR/system_incremental"
 
# 定义系统文件备份目录
SYSTEM_DIRECTORIES="/etc /bin /lib /usr /sbin"
 
# 确保备份目录存在
mkdir -p $BACKUP_TYPE_DIR
 
# 获取当前日期(格式:YYYYMMDD)
CURRENT_DATE=$(date +%Y%m%d)
 
# 获取当天之前的所有备份(按日期分组)
PREVIOUS_BACKUPS=$(ls -d $BACKUP_TYPE_DIR/* | grep -v "$CURRENT_DATE" | sort)
 
# 对于今天之前的每一天,只保留最新的备份
if [ ! -z "$PREVIOUS_BACKUPS" ]; then
    echo "Cleaning up previous backups..."
    # 获取每一天的最新备份
    DAYS=$(echo "$PREVIOUS_BACKUPS" | awk -F- '{print $1"-"$2"-"$3}' | sort | uniq)
    for day in $DAYS; do
        # 获取该天的所有备份
        DAY_BACKUPS=$(echo "$PREVIOUS_BACKUPS" | grep "$day")
        # 获取当天最新的备份(保留最新的一个)
        KEEP_BACKUP=$(echo "$DAY_BACKUPS" | tail -n 1)
        # 删除当天其他的备份
        echo "$DAY_BACKUPS" | grep -v "$KEEP_BACKUP" | xargs sudo rm -rf
    done
fi
 
# 获取最新的备份目录
LATEST_BACKUP=$(ls -d $BACKUP_TYPE_DIR/* | sort | tail -n 1)
 
# 如果没有任何备份,做全量备份
if [ -z "$LATEST_BACKUP" ]; then
    NEW_BACKUP="$BACKUP_TYPE_DIR/full-$CURRENT_DATE-$(date +%H%M%S)"
    sudo rsync -a --progress $SYSTEM_DIRECTORIES $NEW_BACKUP
else
    # 做增量备份
    NEW_BACKUP="$BACKUP_TYPE_DIR/inc-$CURRENT_DATE-$(date +%H%M%S)"
    sudo rsync -a --progress --link-dest=$LATEST_BACKUP $SYSTEM_DIRECTORIES $NEW_BACKUP
fi
 
# 删除多余的旧备份(保留最近3天的每一天的最后一次备份)
cd $BACKUP_TYPE_DIR
ls -d * | sort | uniq -w 8 | head -n -3 | xargs sudo rm -rf

这里还有一个备份用户数据的, --exclude 是排除了 *.log*.cache 这种文件减少错误:

#!/bin/bash
 
# 定义备份目录
BACKUP_DIR="/to/your/pathT7_Touch/backup"
- BACKUP_TYPE_DIR="$BACKUP_DIR/system_incremental"
+ BACKUP_TYPE_DIR="$BACKUP_DIR/user_incremental"
 
# 定义需要备份的目录(假设是 /home 和 /var)
FILES_TO_BACKUP="/home /var"
 
# 确保备份目录存在
mkdir -p $BACKUP_TYPE_DIR
 
# 获取当前日期(格式:YYYYMMDD)
CURRENT_DATE=$(date +%Y%m%d)
 
# 获取当天之前的所有备份(按日期分组)
PREVIOUS_BACKUPS=$(ls -d $BACKUP_TYPE_DIR/* | grep -v "$CURRENT_DATE" | sort)
 
# 对于今天之前的每一天,只保留最新的备份
if [ ! -z "$PREVIOUS_BACKUPS" ]; then
    echo "Cleaning up previous backups..."
    # 获取每一天的最新备份
    DAYS=$(echo "$PREVIOUS_BACKUPS" | awk -F- '{print $1"-"$2"-"$3}' | sort | uniq)
    for day in $DAYS; do
        # 获取该天的所有备份
        DAY_BACKUPS=$(echo "$PREVIOUS_BACKUPS" | grep "$day")
        # 获取当天最新的备份(保留最新的一个)
        KEEP_BACKUP=$(echo "$DAY_BACKUPS" | tail -n 1)
        # 删除当天其他的备份
        echo "$DAY_BACKUPS" | grep -v "$KEEP_BACKUP" | xargs sudo rm -rf
    done
fi
 
# 获取最新的备份目录
LATEST_BACKUP=$(ls -d $BACKUP_TYPE_DIR/* | sort | tail -n 1)
 
# 如果没有任何备份,执行全量备份
if [ -z "$LATEST_BACKUP" ]; then
    NEW_BACKUP="$BACKUP_TYPE_DIR/full-$CURRENT_DATE-$(date +%H%M%S)"
-    sudo rsync -a --progress $SYSTEM_DIRECTORIES $NEW_BACKUP
+    sudo rsync -a --progress --exclude="*.log" --exclude="*.cache" $FILES_TO_BACKUP $NEW_BACKUP
else
    # 做增量备份
    NEW_BACKUP="$BACKUP_TYPE_DIR/inc-$CURRENT_DATE-$(date +%H%M%S)"
-    sudo rsync -a --progress --link-dest=$LATEST_BACKUP $SYSTEM_DIRECTORIES $NEW_BACKUP
+    sudo rsync -a --progress --link-dest=$LATEST_BACKUP --exclude="*.log" --exclude="*.cache" $FILES_TO_BACKUP $NEW_BACKUP
fi
 
# 删除多余的旧备份(保留最近3天的每一天的最后一次备份)
cd $BACKUP_TYPE_DIR
ls -d * | sort | uniq -w 8 | head -n -3 | xargs sudo rm -rf
 

Bug

备份的内容太多了!咱也不知道是哪个的问题,所幸就不管它了~

设置服务随着开机且目标硬盘接入启动

这部分主要是为我的系统备份准备的

创建服务

/etc/systemd/system/ 目录下创建一个 service 。比如:

sudo nano /etc/systemd/system/system_backup_after_mount.service

我这里是把脚本放到了 /usr/local/bin/ 下,所以可以sudo全局调用。

[Unit]
Description=Run system_backup after mount
After=multi-user.target
RequiresMountsFor=/to/your/mount_path

[Service]
ExecStart=incremental_system_backup.sh
Type=oneshot
RemainAfterExit=false

[Install]
WantedBy=multi-user.target

After=multi-user.target

  • 作用: After 指定此服务在某个目标或服务之后启动。它只是设置启动顺序,并不意味着服务本身是依赖于目标。
  • 示例: After=multi-user.target 表示此服务会在系统达到 multi-user.target 状态之后启动。multi-user.target 表示系统已进入多用户模式,用户可以登录并使用系统。
  • 解释: 这意味着,只有在系统的多用户模式(即系统完成了基本的网络配置、登录等任务)启动之后,当前服务才会启动。

RequiresMountsFor=/to/your/mount_path

  • 作用: RequiresMountsFor=systemd 提供的一个特殊配置项,用来明确指定某个挂载点的依赖关系。它确保服务的执行依赖于指定路径的挂载。
  • 示例: RequiresMountsFor=/to/your/mount_path 表示当前服务依赖于 /to/your/mount_path 路径的挂载。如果该路径没有被挂载,systemd 会阻止服务的启动,并在该挂载点完成挂载后再启动该服务。
  • 解释: 这意味着,如果指定的挂载点(如 /to/your/mount_path)没有被挂载,systemd 会等待直到该挂载点挂载完成,才能继续启动服务。这样可以防止服务在必要的挂载点未准备好时启动,避免出现错误。

设置服务自启

sudo systemctl enable system_backup_after_mount.service

设置服务定时执行

这部分是为我的用户数据做的,主要用到的是 定时器服务

创建服务

/etc/systemd/system/ 目录下创建一个 service 。比如:

sudo nano /etc/systemd/system/user_backup.service
# /etc/systemd/system/user_backup.service

[Unit]
Description=Run user_backup after mount
After=multi-user.target
RequiresMountsFor=/to/your/mount_path

[Service]
ExecStart=incremental_user_backup.sh
Type=oneshot
RemainAfterExit=false

[Install]
WantedBy=multi-user.target

创建定时器

/etc/systemd/system/ 目录下创建一个 timer 。比如:

sudo nano /etc/systemd/system/user_backup.timer
# /etc/systemd/system/user_backup.timer

[Unit]
Description=Run user_backup weekly

[Timer]
OnCalendar=weekly
Persistent=true
Unit=user_backup.service

[Install]
WantedBy=timers.target

OnCalendar=weekly

  • 作用: 该字段定义了任务的调度时间。
  • 示例: OnCalendar=weekly 表示每周运行一次任务。
  • 多种格式的示例:
    • OnCalendar=daily:每天运行一次。
    • OnCalendar=Mon *-*-* 12:00:00:每周一中午12点运行。
    • OnCalendar=*-01-01 00:00:00:每年1月1日00:00运行。
    • OnCalendar=2018-12-25 10:00:00:在指定的日期和时间(2018年12月25日10:00)运行。

Persistent=true

  • 作用: 如果系统在预定时间未能运行该定时器(例如,系统在指定时间关闭或暂停),该字段指示 systemd 是否在下次系统启动时执行该定时器任务。
  • 示例: Persistent=true 表示即使系统在预定的时间关闭,定时器也会在系统启动时运行,确保任务不遗漏。
  • 如果为 false 或缺省: 系统不会执行错过的任务,只会在指定的下次时间运行。

Unit=user_backup.service

  • 作用: 这个字段指定该定时器触发的服务单元。它指示 systemd 在定时器触发时运行哪个具体的服务。
  • 示例: Unit=user_backup.service 表示定时器触发时会运行名为 user_backup.service 的服务单元。
  • 常见用途: 可以指定任何有效的服务文件,如 backup.servicecleanup.service 等。

WantedBy=timers.target

  • 作用: 该字段定义了该定时器启用时所属的目标。目标是 systemd 中的一种单位类型,用来组织和管理多个服务或定时器。
  • 示例: WantedBy=timers.target 表示该定时器属于 timers.target,即系统定时器目标,这通常用于启用所有定时器相关的功能。
  • 其他目标:
    • WantedBy=multi-user.target:意味着服务将在 multi-user 模式下运行。
    • WantedBy=default.target:将服务设置为默认启动。

设置定时器自动启动

sudo systemctl enable user_backup.timer

服务不需要自启,它会被定时器叫醒的~

指向原始笔记的链接

结论

结论

  • 如果只是个人日常备份到 linux文件系统的话,可以直接使用 Timeshift 省时省力~
  • 如果是愿意折腾,期望更精细控制备份的话,可以直接尝试 rsync
  • 不建议去尝试 Duplicity