手头有一个退下来的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表中PREROUTING
与OUTPUT
链的规则, 而为什么是这两条链以及是否可以删去其中一条等内部原理我没有深究, 先这样知其然地用起来, 自动化的脚本如下:
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的权限:Text1%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
其实对于我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的信心:
bash1# 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参数可以获取:bash1$ 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, 比如在本机下可以看到所有的快照的父亲都是其备份的数据子卷; 而在远端则可以看到所有的快照父亲都是其增量备份的父亲:Text1rqdmap 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
, 因而对这两个分区的迁移比较简单, 以家目录为例:bash1cd /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一次对应的分区到某个目录下就行.bash1sudo mount -o subvol=/ -U 933f5983-05de-4b6f-bd75-34540ae5284e /real_root/
-
接着在/real_root里操作, 创建快照, 清理旧的文件;
-
完成后现在的顶级子卷/下可能仅仅存在一个分子卷文件夹(比如命名为
@
), 这个小的子卷文件夹才包含了真正的FHS结构; 这种文件结构显然不能直接挂载为根目录给Linux使用, ArchWiki提供了两种解决办法: 基于子卷id+Grub内核选项指定挂载某个目录下的子卷作为根目录(未尝试), 另一种直接通过设置btrfs分区的默认挂载子卷:
bash1cd / 2sudo btrfs subvolume set-default @
- 这样当挂载该分区时, 对应的子卷就会自动挂载上来; 不过缺点是此时无法再直接访问到根目录所在子卷真实的父亲子卷了, 解决方案还是利用mount直接挂载, 通过调整
subvol
参数即可指定挂载哪个子卷.
bash1# 挂载顶级子卷 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