Mac(x86派)的废物再利用

 技术  网络  frp  btrfs  备份 󰈭 9192字

手头有一个退下来的mbp, 是老版本因而搭载的还是x86的CPU, 同时由于电源被我通过拓展坞充了次电而弄坏过, 因而一旦断电会光速关机.. 加上mbp真的是用到后面(大概4年)越用越卡, 让人怀疑macos是不是搞了什么鬼或怎么样.. 总之这台机子不太能当作主力机了, 退下来后也没怎么用过, 装了个windows但是用起来也是卡卡的, 前阵子拿来实验室企图当个服务器用, ntr上了个archlinux后但是也不知道拿来做什么, 正巧最近发现主力机的ssd/文件系统可能有问题, 因而考虑拿mbp来当一个x86派使用, 做做备份或者其他一些什么事情.

网络访问与流量转发

首先需要解决的是网络访问问题, 至少需要在校园网内的10网段下自由通信.

动态dns工具还是选用的timothymiller/cloudflare-ddns, 并且加上moreality的补丁, 就可以将局域网的IP提交到dns解析中去了.

 1diff --git a/cloudflare-ddns.py b/cloudflare-ddns.py
 2index d6a974a..e6892b4 100755
 3--- a/cloudflare-ddns.py
 4+++ b/cloudflare-ddns.py
 5@@ -49,6 +49,13 @@ def deleteEntries(type):
 6                 "DELETE", option)
 7             print("🗑️ Deleted stale record " + identifier)
 8
 9+import socket
10+def get_inner_ip():
11+    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
12+    s.connect(('8.8.8.8', 80))
13+    ip = s.getsockname()[0]
14+    s.close()
15+    return ip
16
17 def getIPs():
18     a = None
19@@ -58,10 +65,11 @@ def getIPs():
20     global purgeUnknownRecords
21     if ipv4_enabled:
22         try:
23-            a = requests.get(
24-                "https://1.1.1.1/cdn-cgi/trace").text.split("\n")
25-            a.pop()
26-            a = dict(s.split("=") for s in a)["ip"]
27+            a = get_inner_ip()
28+            # a = requests.get(
29+            #     "https://1.1.1.1/cdn-cgi/trace").text.split("\n")
30+            # a.pop()
31+            # a = dict(s.split("=") for s in a)["ip"]
32         except Exception:
33             global shown_ipv4_warning
34             if not shown_ipv4_warning:

这样一来在校园网内部(10网段部分)就可以直接通过域名对其进行访问.

由于工位区域的有线网络是无限量免费的, 因而还期望在校园网内的任何区域, 通过工位的mac派做一个流量中转, 从而实现流量自由, 不过貌似尝试了一下不太行. 我们实验室的组网结构大概如下:

实验室组网结构

实验室貌似只有一个10网段的ip接入, 大的交换机又将192的网段分发给若干个小交换机, 一个小交换机就提供一间实验室的网络访问; 而至少在我们这间实验室中, 脚下的有线口实际上是由分交换机接出来lan口, 并不是一个独立的10网段ip.. 这就导致白嫖有一些困难, 只有在工位的有线接入的IP是无限流量, 而我的mac派貌似无法在这样的组网体系下获得一个10网段的ip, 而使用NAT的话我必须又没有主交换机的操作权限, 只有我们实验室分交换机的操作权限..

frp转发

目前的解决办法是基于frp的端口转发, 将私网(192网段)的服务器的22和7890端口全部映射出来, 而目标机器就是我的主力机. 主力机此时只需要连接上10网段即可充当frp server.

这样, 为了使用Mac派做流量中转只需要把代理服务器端口设置为本机的frp开的口子, 即可将流量全部走内网先发到mac派, 再从mac派白嫖实验室的流量发到任意外网了.

本机的脚本比较简单, 主脚本入口为frps.sh:

1# frps.sh
2#!/bin/zsh
3
4set -e
5
6$(dirname $0)/upload_ip/main.sh
7frps -c /data/rqdmap/Applications/frp/frps.ini

frp server端只需要配置一个端口即可:

1# frps.ini
2[common]
3bind_port = 7000

在服务器上需要多做一些工作, 包括实现轮询等功能:

 1#!/bin/zsh
 2function reset() {
 3	main=$(curl -s https://rqdmap.top/ip)
 4	pkill -x frpc
 5	pid=''
 6}
 7
 8reset
 9echo $main
10while true
11do
12	ping -c 1 -W 3 $main > /dev/null
13	res=$?
14	if [ $res -eq 0 ]; then
15		if [ -z $pid ]; then
16			export FRP_SERVER_ADDR=$main
17			frpc -c /root/startup/frp/frpc.ini &
18			pid=$!
19		else
20			kill -0 $pid >/dev/null 2>&1
21			res=$?
22			if [ $res -ne 0 ]; then
23				reset
24			fi
25		fi
26	else
27		reset
28	fi
29	sleep 5
30done
  • 不过不断的curl尽管不会对服务器造成巨大的工作负载, 但是会大大增加nginx服务器的日志 :( 目前还是注释掉了这一行, 转而直接从文件中读ip, 这个ip文件又由cronie定时任务每天只从远程服务器下载一次. 虽然我认为学校的dhcp给我的10网段ip不会变化, 而且这种一天下载一次的自动任务就及时性来说还是了差了些, 不过聊胜于无, 或许之后在奇奇怪怪的场景下可能会救命

frp client的配置为:

 1[common]
 2server_addr = {{ .Envs.FRP_SERVER_ADDR }}
 3server_port = 7000
 4
 5[ssh]
 6type = tcp
 7local_ip = 127.0.0.1
 8local_port = 22
 9remote_port = 60022
10
11[clash_proxy]
12type = tcp
13local_port = 7890
14remote_port = 60078

这样子做的优点是完全地不依靠于NAT或其他设备要求, 自己就是一台(内网中的)公网设备, 不过缺点也比较多:

  • 使用起来不够便利, 归根结底私网的服务器没有公网地址, 因而其完全依赖主力机启动后才拥有一个能够使用的10网段的IP地址, 这也导致了在一些未经配置的环境上(比如windows)无法开箱即用的白嫖流量; 此外也不能像一个简单的ss节点一样在手机等clash支持的设备上直接使用, 总的来说局限性感觉比较大.

轮询脚本粗糙且十分不鲁棒.. 不过凑活用着先..

另一个有关frp与btrfs备份的问题是.. 修改后续自动化备份脚本中的ssh指令为: ssh -p 60022 root@localhost, 传了一半就会中断掉! 提示对端关闭了连接.. 我尝试将ssh会话数量调整到1000, 但是也无济无事, 而且直接用纯局域网传输就十分的没有问题, 一旦走了frp就会出现该问题.. 检查Mac派的日志也没有看到奇怪的东西, frp服务也没有崩溃重新启动.. 这是一个遗留的问题, 还没有想好从哪方面DEBUG, 比较怀疑是纯frp的问题, 不过目前已经没有心思再去折腾这套东西了, 留待以后去解决吧.

为了安全, 应当设置iptables的规则, 禁止除本机外的其他局域网ip访问自己的60078端口, 设置完成后, 使用其他设备尝试nmap应显示阻塞:

1nmap -p 60078 10.192.177.19
2Starting Nmap 7.94 ( https://nmap.org ) at 2023-09-20 18:51 CST
3Nmap scan report for 10.192.177.19
4Host is up (0.010s latency).
5
6PORT      STATE    SERVICE
760078/tcp filtered unknown
8
9Nmap done: 1 IP address (1 host up) scanned in 11.18 seconds

自动化端口选择

使用主力机时, 如何动态优先选择frp路线而同时能够保留本机clash代理做fallback呢? 每次都手动切换未免太过于复杂了, 这里考虑借用iptables的端口转发功能, 将多个可能的代理入口整合到同一个端口上去.

有关iptables的转发参考这篇SO问答How to do local port forwarding with iptables - Stack Overflow即可, 需要同时设置nat表中PREROUTINGOUTPUT链的规则, 而为什么是这两条链以及是否可以删去其中一条等内部原理我没有深究, 先这样知其然地用起来, 自动化的脚本如下:

 1#!/bin/zsh
 2#!/bin/zsh
 3
 4merge_ports=(7890 9090)
 5
 6local_ports=(8901 9091)
 7remote_ports=(60078 60090)
 8
 9systemctl restart iptables.service
10
11local_port=${local_ports[1]}
12remote_port=${remote_ports[1]}
13
14# 先把缺省的本地clash代理装载进路由表
15for ((i=1; i<=${#local_ports[@]}; i++)); do
16	local_port=${local_ports[i]}
17	merge_port=${merge_ports[i]}
18
19	iptables -t nat -A PREROUTING   -s localhost -p tcp --dport $merge_port -j REDIRECT --to-ports $local_port > /dev/null
20	iptables -t nat -A OUTPUT       -s localhost -p tcp --dport $merge_port -j REDIRECT --to-ports $local_port > /dev/null
21done
22
23echo $(date) use port: $local_port
24
25
26ok=0
27for i in {1..6}; do
28	echo "connecting to frpc $i times.."
29    lsof -i:$remote_port > /dev/null
30    if [ $? -eq 0 ]; then
31		ok=1
32		echo "$(date) check port: $remote_port (Success)"
33		break
34    fi
35    sleep 10
36done
37
38
39if [ $ok -eq 1 ]; then
40	echo "frpc is running, switch to remote port."
41
42	for ((i=1; i<=${#local_ports[@]}; i++)); do
43		local_port=${local_ports[i]}
44		remote_port=${remote_ports[i]}
45		merge_port=${merge_ports[i]}
46		iptables -t nat -D PREROUTING	-s localhost -p tcp --dport $merge_port -j REDIRECT --to-ports $local_port  > /dev/null
47		iptables -t nat -D OUTPUT		-s localhost -p tcp --dport $merge_port -j REDIRECT --to-ports $local_port  > /dev/null
48		iptables -t nat -A PREROUTING   -s localhost -p tcp --dport $merge_port -j REDIRECT --to-ports $remote_port > /dev/null
49		iptables -t nat -A OUTPUT       -s localhost -p tcp --dport $merge_port -j REDIRECT --to-ports $remote_port > /dev/null
50	done
51fi
  • 由于之前的代理端口设置的都是clash的默认7890, 我不想再去改那些各种各样的不同应用的参数了, 因而这里考虑的是直接改clash的实际端口为8901, 再使用iptables将两个实际代理端口整合进入7890, 这样上层的代理使用者就可以无感迁移..

  • 自动化脚本与frp相互配合, frpc中设置的是5s轮询一次并尝试建立连接, 因为这里认为每隔10s查询一次一共查6次即可不会遗失可能的frpc连接..

  • iptables的sudo权限问题: 一般的用户无法直接不加sudo的执行iptables, 但我的bspwm启动脚本是以一般用户的权限执行的, 这就有些麻烦, 目前的解决方案是在/etc/sudoers中单独为这个文件设置nopasswd的权限:

    1%wheel ALL=(ALL:ALL) NOPASSWD: /data/rqdmap/Applications/frp/auto_proxy.sh

    这样的话使用sudo执行该脚本就不会要求输入root密码而可直接运行了!

定期备份

由于Mac派本身的磁盘十分的小, 因而特意采购了一个rc20外接作为数据盘.

主力机数据备份

有这个需求是因为有时候系统会莫名的卡死, 此时任何指令都无法执行, 无论是终端, 还是预先定义好的rofi的reboot等功能都无法使用, 也无法切换不同的tty, 智能强制重启… 而且十分的无征兆, 个人怀疑是磁盘的问题..

而且journalctl也不能找到任何的记录, 除了强制重启外没有任何解决办法..

比较担忧是磁盘出现了问题, 或者是btrfs出现了问题… 而我目前的系统完全没有做过任何的备份, 如果因为一些fs的bug导致数据丢失那么后果是不能承受的.. 因而火速买了块新的硬盘, 并且准备利用上btrfs的快照等功能进行文件的周期性备份.

准备外部设备

首先给新磁盘分区, 分区怎么弄还没仔细考量, 但是是作为数据盘所以感觉问题不大, 先直接划个一半, 分512G的分区出来.

 1$ fdisk -l
 2Disk /dev/nvme0n1: 233.76 GiB, 251000193024 bytes, 61279344 sectors
 3Disk model: APPLE SSD AP0256J
 4Units: sectors of 1 * 4096 = 4096 bytes
 5Sector size (logical/physical): 4096 bytes / 4096 bytes
 6I/O size (minimum/optimal): 4096 bytes / 4096 bytes
 7Disklabel type: gpt
 8Disk identifier: 453B6E73-B06A-4362-9A8E-4116BD952046
 9
10Device            Start      End  Sectors   Size Type
11/dev/nvme0n1p1        6    76805    76800   300M EFI System
12/dev/nvme0n1p2    76806 12272367 12195562  46.5G Apple APFS
13/dev/nvme0n1p3 12272384 61246463 48974080 186.8G Linux filesystem
14/dev/nvme0n1p4 61246571 61279338    32768   128M Apple boot
15
16
17Disk /dev/nvme0n2: 8 KiB, 8192 bytes, 2 sectors
18Disk model: APPLE SSD AP0256J
19Units: sectors of 1 * 4096 = 4096 bytes
20Sector size (logical/physical): 4096 bytes / 4096 bytes
21I/O size (minimum/optimal): 4096 bytes / 4096 bytes
22
23
24Disk /dev/sda: 931.51 GiB, 1000204886016 bytes, 1953525168 sectors
25Disk model: USB3.0
26Units: sectors of 1 * 512 = 512 bytes
27Sector size (logical/physical): 512 bytes / 4096 bytes
28I/O size (minimum/optimal): 4096 bytes / 4096 bytes
29Disklabel type: gpt
30Disk identifier: DDEBE655-F032-4473-A730-7495F176D967
31
32Device     Start        End    Sectors  Size Type
33/dev/sda1   2048 1073743871 1073741824  512G Linux filesystem

再安装一份btrfs工具包pacman -S btrfs-progs, 将该分区变成btrfs: mkfs.btrfs -L BACK_DATA /dev/sda1, 加一行fstab, 使其开机自动挂载到/mnt上:

1LABEL=BACK_DATA         /mnt        btrfs       defaults,compress=zstd

这儿其实挂载到mnt上不好, 最好是挂载到像/data这种地方, 把mnt留给可能的其余的外接设备; 不过我的mac就一个可用的雷电口了, 除非使用拓展坞不然也只可能挂载这一个外接设备:)

简单用dd测一下速度:

1[ 21:08:02 ]   sync; dd if=/dev/zero of=/root/back/tmpfile bs=1M count=10240; sync
210240+0 records in
310240+0 records out
410737418240 bytes (11 GB, 10 GiB) copied, 7.62182 s, 1.4 GB/s
5
6[ 21:09:22 ]   sync; dd of=/dev/zero if=/root/back/tmpfile bs=1M count=10240; sync
710240+0 records in
810240+0 records out
910737418240 bytes (11 GB, 10 GiB) copied, 6.75932 s, 1.6 GB/s

感觉上是是被数据线限制了速度, 硬盘盒本身是c口的, mac也是c口的, 但此时手头正好没有c2c的线, 只能用一个绿联的拓展坞+usb口先连上去..

安装compsize包, 用于计算压缩比: compsize -x /.

数据迁移至子卷

主力机的分区在设计上共有3个btrfs分区:

 1sudo compsize -x /
 2[sudo] password for rqdmap:
 3Processed 636907 files, 461590 regular extents (478929 refs), 369706 inline.
 4Type       Perc     Disk Usage   Uncompressed Referenced
 5TOTAL       73%       35G          48G          48G
 6none       100%       29G          29G          28G
 7zstd        32%      6.2G          19G          20G
 8
 9sudo compsize -x /data
10Processed 272085 files, 252570 regular extents (272331 refs), 191028 inline.
11Type       Perc     Disk Usage   Uncompressed Referenced
12TOTAL       82%       72G          87G          89G
13none       100%       64G          64G          64G
14zstd        34%      7.9G          23G          25G
15
16sudo compsize -x /home
17Processed 612873 files, 477469 regular extents (484544 refs), 329417 inline.
18Type       Perc     Disk Usage   Uncompressed Referenced
19TOTAL       64%       21G          33G          33G
20none       100%       16G          16G          15G
21zstd        31%      5.5G          17G          17G
22prealloc   100%      4.9M         4.9M          28M
btrfs的透明压缩功能?

其实对于我2T的黑盘来说, 完全足够我的Linux系统使用任意大的空间. 但是看着磁盘空间的性能被尽可能的压缩与利用, 能让人感受到系统被维护的紧凑与简洁, 身心愉悦!

  • 在更早的Linux使用阶段我并没有将/data单独的区分出来, 全部放在/home下以追求更好的移植性(切换不同的根目录并挂载家目录即可近乎无感地进入不同的发行版等等), 不过这样是坏的. 因为许多软件会随地在我的家目录里直接拉屎, 我目前维护的家目录下仅有十来个常用的文件夹, 而包含隐藏文件统计则多达近100个! 同时arch的包管理器明确说了其不管理这种由应用生成的配置文件, 这就导致这一坨里面我完全不知道哪些是必要的, 哪些是可选的, 以及哪些是完全的垃圾.. 为此我单独拆分出来了data分区, 用来存放一些我认为是必要的数据, 而剩下的一些文件即使在后续迁移整理的时候丢失也不会特别痛苦.
 1$ ls -l
 2drwxr-xr-x 1 rqdmap rqdmap    56 Sep  6 10:06 Downloads
 3drwxr-xr-x 1 rqdmap rqdmap   154 Sep  5 21:11 dotfiles
 4drwxr-xr-x 1 rqdmap rqdmap     0 Sep  5 20:54 tmp
 5drwxr-xr-x 1 rqdmap rqdmap   168 Sep  5 18:56 Zotero
 6drwxr-xr-x 1 rqdmap rqdmap     0 Aug 31 15:31 Desktop
 7drwxr-xr-x 1 rqdmap rqdmap   136 Jul 28 02:10 Games
 8lrwxrwxrwx 1 rqdmap rqdmap    18 Jul 23 20:35 Codes -> /data/rqdmap/Codes
 9drwxr-xr-x 1 rqdmap rqdmap     8 Jul 23 18:08 resources
10lrwxrwxrwx 1 rqdmap rqdmap    19 Apr 17 19:01 Videos -> /data/rqdmap/Videos
11lrwxrwxrwx 1 rqdmap rqdmap    22 Apr 16 02:04 hugo-blog -> /data/rqdmap/hugo-blog
12drwxr-xr-x 1 rqdmap rqdmap 10392 Dec  7  2022 CloudMusic
13lrwxrwxrwx 1 rqdmap rqdmap    22 Sep 28  2022 Resources -> /data/rqdmap/Resources
14lrwxrwxrwx 1 rqdmap rqdmap    26 Sep 28  2022 Applications -> /data/rqdmap/Applications/
15lrwxrwxrwx 1 rqdmap rqdmap    21 Sep 28  2022 Pictures -> /data/rqdmap/Pictures
16lrwxrwxrwx 1 rqdmap rqdmap    23 Sep 28  2022 Templates -> /data/rqdmap/Templates/
17lrwxrwxrwx 1 rqdmap rqdmap    22 Sep 28  2022 Documents -> /data/rqdmap/Documents
  • 大部分是以软链接的形式存在, 这些软链接大部分是一些文档, 图片等资源, 如果做数据迁移这些肯定是必要的.

  • 而对于一些系统的配置文件而言, 我本身也维护了一个dotfiles仓库, 里面备份了我常用/必要软件的所有配置. 因为迁移时直接拉下来部署即可还原个七七八八.

    • 其实最重要的就是alacritty+zsh+nvim+bspwm+sxhkd+rofi+joshuto这一套东西, 其余到处拉屎的东西删掉也不心疼:/

btrfs的快照其实是一种特殊的子卷, 由于btrfs的子卷是一个比较新奇的概念, 因而在实际动手实操前专门也去看了些相关的东西.

此时才意识到我的btrfs在部署以来就一直没有启用子卷功能, 而是直接把默认的顶级子卷拿来使用. 而事实上子卷的设计十分适合做快照与备份, 通过快照生成一个新的子卷后, 如果希望实现数据恢复, 只需要将该子卷设为该btrfs文件系统分区的默认挂载子卷, 再次启动系统后即可恢复到旧的状态上, 此时再将旧的被污染的子卷删除就万事大吉了!

有关子卷操作的指令整理如下:

1btrfs subvolume list /			# 查看/下的所有子卷
2btrfs filesystem usage /		# 查看btrfs文件系统的使用情况
3
4btrfs subvolume snapshot [-r] SRC DST	# 快照创建, -r创建只读快照
5mv					# 像文件夹一样移动快照
6btrfs subvolume delete			# 但不能像文件夹一样rm快照
7
8# 快照的传递, -p 允许增量备份
9btrfs send [-p <Parent Snapshot>] SRC | btrfs receive DST

这里多看一眼send和receive指令, 通常的操作是将本地磁盘的快照直接发送到外部存储设备上, send将子卷信息发送至stdout, 而receive做相应的接受; 那么-p参数如何起作用呢? 外部设备如何才能做到增量备份呢? 如果本机和外部设备均有一份A的快照, 我在本机基于A又衍生出了A’, 基于A’衍生出了A’’, 我如果直接将对A’做增量备份的A’‘发送到外部会怎么样呢?

  • 做一个简单的实验以确保自己对这套fs的信心:

    1# btrfs send -p @-new @-new-new | btrfs receive /mnt/archlinux
    2At subvol @-new-new
    3At snapshot @-new-new
    4ERROR: snapshot: cannot find parent subvolume f6eb518f-20d3-5d42-b5f0-aa349a62cd0a

    这种基于不存在的父亲做增量备份是无法发送出去的! 可以看到每一个子卷其实是有一个uuid号的, 每个子卷的uuid通过btrfs subvolume list -u /home的u参数可以获取:

    1$ btrfs subvolume list /home -u
    2ID 358 gen 62924 top level 5 uuid ffc92909-f024-5a44-950e-d9b9fe493d19 path @
    3ID 359 gen 62979 top level 5 uuid f6eb518f-20d3-5d42-b5f0-aa349a62cd0a path @-new
    4ID 360 gen 62985 top level 5 uuid f86536d5-8af2-7a45-be1d-e9f30263d42a path @-new-new

    也就是说在跨btrfs时其实是根据uuid号来确定其父亲是否存在的, 那么如果出现了误操作的错误/遭到恶意的攻击, 是否有可能使得本不存在的父亲变得存在? 也许btrfs内部还有一些检验机制, 但总的来说, 在个人谨慎的使用下, 哪怕没有进一步的校验, 仅仅是随机生成的uuid也已经能让人有许多的信心了.

还有一个有关btrfs的增量快照功能.. 当我按照A->B->C->D->E的顺序依次发送了一串增量快照后, 是否一定需要按照顺序只能删除一个前缀(如A,B,C), 能否删除任意的快照(如仅删除C)而保持剩余的快照工作正常?

  • 根据这篇帖子btrfs incremental backup - do I understand correctly how it works ? : btrfs所说的, 在CoW系统上任意的快照都是一个完整的, 独立的Copy, 那么这就意味可以任意操作系统中的快照而不会影响到其他快照. 而增量快照仅仅是为了节省一些数据传输的带宽罢了, 当使用send指令时宿主机会检查父快照和当前快照的指针树的结构差异, 仅将这些差异所在的实际物理块发送出去以节省带宽; 而一旦完成了快照的发送与接收(当然成功的接收的前提还是目标机和宿主机都有同一份父快照), 后续在目标机上操作这些快照就跟正常的一样了, 可以任意删除任意的快照而不受到影响.

  • 如何理解btrfs sub list / -qu不匹配的问题呢? -q参数会额外显示子卷的父亲uuid, 比如在本机下可以看到所有的快照的父亲都是其备份的数据子卷; 而在远端则可以看到所有的快照父亲都是其增量备份的父亲:

     1rqdmap in ~ took 1s
     2[ 15:36:45 ] ✗  sudo btrfs sub list / -qu
     3ID 382 gen 41838 top level 5 parent_uuid -                                    uuid 00670c66-1bc2-7345-9fb7-0876bf4cd45c path @
     4ID 386 gen 41591 top level 5 parent_uuid 00670c66-1bc2-7345-9fb7-0876bf4cd45c uuid af6e94b4-ee99-db40-bfa3-08a7e029b0e1 path snapshots/real_root_2023-09-13_12:32:11
     5ID 387 gen 41612 top level 5 parent_uuid 00670c66-1bc2-7345-9fb7-0876bf4cd45c uuid b325431c-c7a0-2b45-a685-b048bab99f0c path snapshots/real_root_2023-09-13_12:45:42
     6ID 388 gen 41628 top level 5 parent_uuid 00670c66-1bc2-7345-9fb7-0876bf4cd45c uuid 0af173a2-e2d7-4a41-8da8-a584c76636d1 path snapshots/real_root_2023-09-13_12:55:03
     7
     8root in 🌐 ArchMacBook in /mnt
     9[ 15:38:58 ]   btrfs subvolum list /mnt -uq
    10ID 264 gen 307 top level 5 parent_uuid 2103c456-596d-8749-a422-6173215ef1fa uuid bf71d5aa-7777-f94c-ba1e-ccb930bdb382 path archlinux/real_root_2023-09-13_12:32:11
    11ID 265 gen 310 top level 5 parent_uuid 6500561e-270c-0643-a6af-15e8a922ea1a uuid f4cab7d8-c765-d747-b7a2-61d9b5f249eb path archlinux/data_2023-09-13_12:36:19
    12ID 266 gen 313 top level 5 parent_uuid 8c93f642-6940-484b-abf9-dec2517e97fc uuid fa8a305e-2523-5641-a9ca-43f67fc18f8f path archlinux/home_2023-09-13_12:38:34
    13ID 267 gen 319 top level 5 parent_uuid bf71d5aa-7777-f94c-ba1e-ccb930bdb382 uuid 9268f5a2-1d13-ed47-a432-d97df14eef05 path archlinux/real_root_2023-09-13_12:45:42
    14ID 268 gen 322 top level 5 parent_uuid f4cab7d8-c765-d747-b7a2-61d9b5f249eb uuid fe2703f8-6d47-f246-ae78-acea7a31d986 path archlinux/data_2023-09-13_12:45:42
    15ID 270 gen 328 top level 5 parent_uuid 9268f5a2-1d13-ed47-a432-d97df14eef05 uuid f49f3849-8ac2-e342-a89d-206cfcb8ccc8 path archlinux/real_root_2023-09-13_12:55:03
    16ID 271 gen 324 top level 5 parent_uuid fe2703f8-6d47-f246-ae78-acea7a31d986 uuid ef5ae3f4-ec0d-e247-8010-d9e5f07a1eda path archlinux/data_2023-09-13_12:55:10
    17ID 272 gen 328 top level 5 parent_uuid a1869d25-84c1-4746-81cb-90b4ee608dfe uuid 21024e3c-ba1d-784e-a373-24477cca7a31 path archlinux/home_2023-09-13_12:55:12

    为了做测试我特意把home的中间的一次备份删除了, 这样确实看上去两次home快照之间无法连起来了, 但是还是能正常使用的; 毕竟就算能连起来, 我的root和data的根快照也有一个父亲, 这个父亲已经被我删掉了, 也不影响他们的使用. (大概)

    因而总的来说我认为, 这个parent_uuid不会实际影响任何快照使用; 可以继续放心大胆使用!

最后一个需要处理的问题是, 由于目前所有的数据均在顶级子卷下, 那么如果仍然在该子卷下创建新的快照备份该子卷的内容, 就会出现一些不够优雅的自循环问题:

  • 当备份第n次时, 由于前面n-1次的备份文件夹均存在于该子卷的某个目录下, 因而第n次备份时会把这些n-1个空文件夹(好在btrfs保证快照的数据并不会循环拷贝)都再次拷贝一份! 这太不好了

解决方案要么是开辟一块独立的区域专门用来中转/存储快照, 但是听起来就十分的愚蠢; 不如直接对数据做一些迁移, 将所有的顶级子卷下的数据迁移到新创建的子卷中, 这样就可以直接在同一个分区下存放快照数据了:

  • 对于/data/home来说, 由于其下还有一个子文件夹叫rqdmap, 因而对这两个分区的迁移比较简单, 以家目录为例:

     1cd /home
     2sudo btrfs subvolume snapshot /home/ /home/rqdmap-new
     3
     4# 删除rqdmap-new/下的一些无用的文件夹, 如'RECYCLE\ BIN'等
     5
     6sudo mv ./rqdmap-new/rqdmap/*	./rqdmap-new
     7sudo mv ./rqdmap-new/rqdmap/.*	./rqdmap-new
     8sudo rmdir ./rqdmap-new/rqdmap		# 确保所有的文件都被转移
     9sudo rm -rf ./rqdmap
    10sudo mv rqdmap-new rqdmap
  • 对于/分区来说, 情况更加麻烦一些, 由于存在若干运行时文件夹(/run, /proc等), 一种办法是用外接优盘再连个archlinux上来, 然后对主力机的分区进行操作, 或者直接再mount一次对应的分区到某个目录下就行.

    1sudo mount -o subvol=/ -U 933f5983-05de-4b6f-bd75-34540ae5284e /real_root/
    • 接着在/real_root里操作, 创建快照, 清理旧的文件;

    • 完成后现在的顶级子卷/下可能仅仅存在一个分子卷文件夹(比如命名为@), 这个小的子卷文件夹才包含了真正的FHS结构; 这种文件结构显然不能直接挂载为根目录给Linux使用, ArchWiki提供了两种解决办法: 基于子卷id+Grub内核选项指定挂载某个目录下的子卷作为根目录(未尝试), 另一种直接通过设置btrfs分区的默认挂载子卷:

    1cd /
    2sudo btrfs subvolume set-default @
    • 这样当挂载该分区时, 对应的子卷就会自动挂载上来; 不过缺点是此时无法再直接访问到根目录所在子卷真实的父亲子卷了, 解决方案还是利用mount直接挂载, 通过调整subvol参数即可指定挂载哪个子卷.
    1# 挂载顶级子卷
    2sudo mount -o subvol=/ -U 933f5983-05de-4b6f-bd75-34540ae5284e /real_root/
    3# 挂载实际的根目录子卷
    4sudo mount -o subvol=/@ -U 933f5983-05de-4b6f-bd75-34540ae5284e /real_root/

这样, 我们就可以直接在三个分区的顶级子卷下创建一个专门用来备份的快照文件夹, 而不会出现尴尬的自循环问题了!

csum问题处理

备份/data的时候忽然出现了问题:

1At subvol /data/snapshots/data_2023-09-11_17:47:57
2At subvol data_2023-09-11_17:47:57
3ERROR: send ioctl failed with -5: Input/output error
4ERROR: unexpected EOF in stream

检查日志文件, 貌似是btrfs检查到校验和不匹配的问题..

重新再尝试了一次也不行, 直接上优盘, 彻底离线去做收发也还是不行, 看来可能是本身出了问题,

btrfs本身提供了一些检查工具:

  • 运行btrfs check, 没有任何问题; 而且不推荐用它来--repair, 该指令还很不稳定, 可能存在很多问题

  • 运行btrfs scrub start /home, 使用btrfs scrub status /home来查看, 居然确实有不少的问题:

 1# sudo btrfs scrub status /data
 2UUID:             490a3540-730e-43b1-9e2d-05abbc310673
 3Scrub started:    Mon Sep 11 21:25:07 2023
 4Status:           finished
 5Duration:         0:00:32
 6Total to scrub:   72.39GiB
 7Rate:             2.26GiB/s
 8Error summary:    read=48 csum=16
 9  Corrected:      22
10  Uncorrectable:  42
11  Unverified:     0
12
13# sudo btrfs scrub status /home
14UUID:             a0c17fc1-3a85-49e0-94c7-46f2e056d5b2
15Scrub started:    Mon Sep 11 21:25:47 2023
16Status:           finished
17Duration:         0:00:16
18Total to scrub:   22.70GiB
19Rate:             1.42GiB/s
20Error summary:    read=96
21  Corrected:      37
22  Uncorrectable:  59
23  Unverified:     0
24
25# sudo btrfs scrub status /
26UUID:             933f5983-05de-4b6f-bd75-34540ae5284e
27Scrub started:    Mon Sep 11 21:36:00 2023
28Status:           finished
29Duration:         0:00:20
30Total to scrub:   27.66GiB
31Rate:             1.38GiB/s
32Error summary:    read=16
33  Corrected:      12
34  Uncorrectable:  4
35  Unverified:     0

data盘发现有16个不可修复的校验和问题… 这里可能就是无法传递快照的元凶

网上有说可以检查一下内存情况的, 就参考了Stress testing - ArchWiki提供的MemTest86, 使用grub-mkconfig生成一下配置文件, 把对应的入口放进去:

 1### BEGIN /etc/grub.d/60_memtest86+-efi ###
 2if [ "${grub_platform}" == "efi" ]; then
 3    menuentry "Memory Tester (memtest86+)" --class memtest86 --class gnu --class tool {
 4        if loadfont unicode ; then
 5            set gfxmode=1024x768,800x600,auto
 6            set gfxpayload=800x600,1024x768
 7            terminal_output gfxterm
 8        fi
 9        search --fs-uuid --no-floppy --set=root  48A2-F7D3
10        linux /memtest86+/memtest.efi 
11    }
12fi
13### END /etc/grub.d/60_memtest86+-efi ###

简单跑了一组是PASS的..

由于跑memtest特别慢, 而且感觉我的内存在哀嚎, 一组后就停止了, 考虑寻找别的方法.

参考backup - Finding files with BTRFS Uncorrectable Errors - Super User提供的方法, 先跑一组scrub任务, 随后可以直接看内核日志, 其中就记录哪些校验和不正确的文件!

所有的校验和问题均发生在idea文件夹下, 而这个工具我已经吃灰了不知道多久了, 直接整个删除; 重启后就可以看到修复完成了, 没有csum的问题了!

 1# sudo btrfs scrub status /data
 2UUID:             490a3540-730e-43b1-9e2d-05abbc310673
 3Scrub started:    Mon Sep 11 22:31:26 2023
 4Status:           finished
 5Duration:         0:00:32
 6Total to scrub:   70.72GiB
 7Rate:             2.21GiB/s
 8Error summary:    read=48
 9  Corrected:      22
10  Uncorrectable:  26
11  Unverified:     

自动化备份脚本

 1#!/bin/zsh
 2set -e
 3
 4targets=(
 5	"/real_root/@"
 6	"/data/rqdmap"
 7	"/home/rqdmap"
 8)
 9
10dst="/mnt/archlinux/"
11
12ssh_cmd="ssh -i /home/rqdmap/.ssh/id_ed25519 root@mac.rqdmap.top"
13# ssh_cmd="ssh -i /home/rqdmap/.ssh/id_ed25519 -p 60022 root@localhost"
14
15
16function error() {
17	echo -e "\033[31m[ERROR] $1\033[0m"
18	exit 1
19}
20
21function warn() {
22	echo -e "\033[33m[Warn] $1\033[0m"
23}
24
25function ok() {
26	echo -e "\033[32m[INFO] $1\033[0m"
27}
28
29if [ "$(id -u)" != "0" ]; then
30	error "Must be root"
31fi
32
33function get_latest_snap() {
34	if [ $# -ne 1 ]; then
35		error "get_latest_snap: wrong number of arguments"
36	fi
37	eax=$(ls $1 | sort -r | head -n 1)
38}
39
40# snap SRC DST
41function snap() {
42	if [ $# -ne 2 ]; then
43		error "snap: wrong number of arguments"
44	fi
45	snap_name=$(basename $(dirname $target))_$(date +%Y-%m-%d_%H:%M:%S)
46
47	cmd="btrfs subvolume snapshot -r $1 $2/$snap_name"
48	ok $cmd
49	eval $cmd
50}
51
52# inc_send_recv par cur
53function inc_send_recv() {
54	if [ $# -ne 2 ]; then
55		error "inc_send_recv: wrong number of arguments"
56	fi
57	# cmd="btrfs send -p $1 $2 | btrfs receive $dst"
58	cmd="btrfs send -p $1 $2 | $ssh_cmd 'btrfs receive $dst'"
59	ok $cmd
60	eval $cmd
61}
62
63
64for target in $targets; do
65	snap_path=$(dirname $target)/snapshots
66
67	echo $snap_path $target
68
69	if [ -d $snap_path ]; then
70		get_latest_snap $snap_path; par_snap=$eax
71
72		snap $target $snap_path
73
74		get_latest_snap $snap_path; cur_snap=$eax
75
76		inc_send_recv $snap_path/$par_snap $snap_path/$cur_snap
77
78	else
79		error "no snapshots in $snap_path"
80	fi
81done

本机测试时可以使用L54, 外接到mac派上后可以使用L55走网络进行传输; 脚本执行过程如下:

结果看上去还不错:

这样后续只要开一个定时任务, 不断地执行该脚本, 将数据主动推到mac派上, 就可以实现定期备份了; 而当备份快照过多时, 直接删除不想要的快照即可.

这里的自动任务具体还没有实现, 因为考虑到本机不是服务器, 不可能全天开机, 设置的定时任务如果过晚/过早那么可能还在睡觉电脑没开机.. 而如果在中间的话可能当时我正在写代码, 一些当天的工作不能被全部备份起来. 理想的情况是做一个关机hook.. 但是这是否好..(终将变成Windows的模样(x))以及如何实现我还在考虑.. 因而暂时先搁置, 目前可以通过手动快照的方式苟且使用..

服务器备份

古老的备份方式是十分朴素的每次都是全量备份的方式… 并且由于没有利用上mac派因而是手动备份, 想起来一次就备份一次..

1#!/bin/zsh
2
3ssh root@rqdmap.top "tar -zcvf docker.tar.gz docker;"
4folder=$(date +%s)
5mkdir $folder
6scp root@rqdmap.top:docker.tar.gz $folder/

有一种原始人类的美, 我的服务器docker数据虽说不大, 但是也有100M不到点的大小, 次次都全量备份不仅对空间利用不友好, 而且主要是很不优雅.

目前考虑使用rsync从远端拉下来后, 直接使用btrfs创建增量快照, rsync最小化流量传输, 而btrfs快照最小化磁盘存储空间!

添加crontab, 每隔3天在早上6点做一次备份:

10 6 */3 * * /mnt/server/rqdmap.top/snapshot

直接运行一次, 效果还行:

不过由于之前都是不关docker服务地做备份, roccoshi说需要关, 但我不关貌似也没有发现太大的问题, 但三天备份一次我认为就算关了重启一次也没关系, 因而修改一下指令添加docker服务的关闭与重启, 最终的自动化脚本为:

 1# /mnt/server/rqdmap.top/snapshot
 2#!/bin/zsh
 3set -e
 4
 5target="/mnt/server/rqdmap.top/@"
 6
 7function error() {
 8	echo -e "\033[31m[ERROR] $1\033[0m"
 9	exit 1
10}
11
12function warn() {
13	echo -e "\033[33m[Warn] $1\033[0m"
14}
15
16function ok() {
17	echo -e "\033[32m[INFO] $1\033[0m"
18}
19
20if [ "$(id -u)" != "0" ]; then
21	error "Must be root"
22fi
23
24function get_latest_snap() {
25	if [ $# -ne 1 ]; then
26		error "get_latest_snap: wrong number of arguments"
27	fi
28	eax=$(ls $1 | sort -r | head -n 1)
29}
30
31# snap SRC DST
32function snap() {
33	if [ $# -ne 2 ]; then
34		error "snap: wrong number of arguments"
35	fi
36	snap_name=$(basename $(dirname $target))_$(date +%Y-%m-%d_%H:%M:%S)
37
38	cmd="btrfs subvolume snapshot -r $1 $2/$snap_name"
39	ok $cmd
40	eval $cmd
41}
42
43
44snap_path=$(dirname $target)/snapshots
45
46echo $snap_path $target
47
48
49if [ -d $snap_path ]; then
50	cmd="ssh root@rqdmap.top 'cd /root/docker; docker-compose stop;'; rsync -azhv --delete --info=progress2 root@rqdmap.top:/root/docker/ /mnt/server/rqdmap.top/@; ssh root@rqdmap.top 'cd /root/docker; docker-compose restart;';"
51	ok $cmd
52	eval $cmd
53
54	snap $target $snap_path
55else
56	error "no snapshots in $snap_path"
57fi
嗨! 这里是 rqdmap 的个人博客, 我正关注 GNU/Linux 桌面系统, Linux 内核, 后端开发, Python, Rust 以及一切有趣的计算机技术! 希望我的内容能对你有所帮助~
如果你遇到了任何问题, 包括但不限于: 博客内容说明不清楚或错误; 样式版面混乱等问题, 请通过邮箱 rqdmap@gmail.com 联系我!
修改记录:
  • 2023-12-04 01:00:47更新frp端口聚合代码
  • 2023-09-20 21:34:53基于iptables动态整合多个代理端口(本机+frp)
  • 2023-09-15 16:08:31为Mac派添加frp穿透
  • 2023-09-13 17:20:33Mac(x86派)的废物再利用
Mac(x86派)的废物再利用