真就以折腾自己为乐。

其实我从来没想好自己应该使用什么发行版,十多年前最开始使用 Arch Linux 也只是因为觉得大家都在使用 什么 Ubuntu 啊觉得自己可以特例独行一些,并没有深刻的体会到什么叫做 KISS,也没有体会到各种拆包粒度对于使用的影响,也一直只是“会用”而已,并没有太多作为用户之外的使用体验差距。就算是第二个使用的发行版是 openSUSE 也一样,因为某种程度上有类似的基因。

起因

闲的蛋痛。年初企图迁移到 Fedora 失败,仅仅过了5个小时就滚回了 Arch 。可能考虑到了 Debian Sid 的如下特性,打算这次外迁一定要迁移出去,去 Debian。

  • 足够稳定
  • apt 的前端 nala,看上去很帅
  • 周围用 Arch 的很多,向特立独行一点
  • 复习时间之余闲的蛋痛

但是也考虑到一些区别,比如:

  • 包管理器的整合性:debian 系素以混乱的包管理功能著称,dpkg、apt、apt-get、apt-file
  • 软件包获取的难度:这也是为什么我一直非常抗拒使用 deb 系,你可以在软件官网上(比如 zotero),在 github release,在 makedeb ,在 pacstall,在 ppa…你能找出一百个不同的软件包来源,但是你没办法把他们整合(Debian CN repo 许久未变更了)
  • 不知道的一些奇奇怪怪的特性
  • wiki,debian wiki 也就将将是能参考的程度

以上这些问题并没罗列全面,但是我曾经认为自己能够克服这些问题。虽然显然还是太高看自己了。

Arch Linux -> Debian

安装一个新的发行版是容易的,也没什么好说。但是考虑到年初 fedora 的惨痛经历,我打算还是给自己留个后路,方法可以有很多,rsync、或者整体dd、或者打个 tarball 什么的,之前有过尝试用 squashfs 备份系统的经验,而且打包后镜像体积特别小,也是很多发行版安装 CD 的首选方案。那就决定了。

首先备份根分区,需要排除掉所有额外挂载的分区和运行态的玩意儿:

1
sudo mksquashfs / backup-archlinux.squashfs -e /home -e /run -e /proc -e /dev -e /sys -e /tmp -e /var/tmp -e /mnt /boot/efi

直接把镜像扔到了外挂的 SSD 上面,体积也不大,150 G 根分区体积也就 27 G。然后下载了 Debian stable bookworm 的镜像,写盘,分区,安装…不一而足。

装上 Debian 的第一件事就是把 nala 装上,apt 那个简陋的信息展示还是非常难受的,好在 Buster 以后已经默认在 main 里了。

1
sudo apt install nala -y

然后发现自己不在 sudoers 里,得把自己手工添加进去:

1
su - -c "usermod -a -G sudo `whoami`"

注销,重新登陆,sudo 可以正常使用了。

接下来切换到 sid,直接编辑 /etc/apt/sources.list 内容如下:

1
2
deb https://mirrors.cqu.edu.cn/debian sid main contrib non-free non-free-firmware
# deb-src http://mirrors.cqu.edu.cn/debian sid main contrib non-free non-free-firmware

直接执行更新:

1
sudo nala update && sudo nala upgrade -y

重启后切换为了 sid ,plasma 也更新到了 5.27.8。这时候给了我第一个下马威:/sbin/ 没 merge 到 /usr/bin ,问题不大,改个 /etc/profile 加个 PATH 的事。然后安装软件云云,不一而足。

开始给自己找麻烦

从 QQ 开始,这时候我想到一个问题,我在 Arch 下可是用的 AUR 上魔改的 launcher 的启动脚本,要是 deb 每次装完都覆盖掉还是有点难受的。在 Arch 上我习惯于用 pacman hook 处理类似问题,但是 dpkg 好像没有 hook 可用。但是 stackexchange 上的某篇帖子和我的需求是一致的,都是需要更新后然后监听特定文件,并 patch 。那显然回答中的 file trigger 是一个可以使用的东西,那么需求明确了:

  1. 创建一个带触发器的空包
  2. 空包中携带对于需要 patch 部分的脚本或源文件
  3. 安装触发器包后安装原 deb 包触发触发器
  4. patched

之前曾经在 Arch 上给 deb 系打过 deb 包,所以创建包过程算是轻车熟路,只不过需要添加 DEBIAN/triggerDEBIAN/postinst , trigger 中 interest <path/to/file> ,然后 postinst 脚本书写如下:

1
2
3
4
5
6
7
8
9
#!/bin/bash
if [ "$1" = "triggered" ]
then
  if [ "$2" = "<path/to/file/triggered>" ]
  then
    # do some patches
  fi
  exit 0
fi

dpkg 在拦截监听器之后在入参上第一个参数是包状态,如果包被其他监听器“感兴趣”,那么第一个参数固定为 triggered ,第二个参数为监听的具体文件。这里需要注意的是,有些包会在后处理部分处理启动器文件等,而我们需要 patch 的部分也是这部分,就需要具体监听其他具体文件。另外所有打包脚本都需要空一行作为 EOF ,否则打包时必报错。

因为特殊需要并没有增长,所以目前仅仅放了一部分 patch 在仓库中。很遗憾没法继续维护下去,以后大概也不会主动去维护。

麻烦开始自己找上门

主要缘由在于我需要一个 Minecraft Launcher。而我常用的是 prismlauncher,但是!没有 deb 包。在 Arch 上没什么好说的,使用的甚至是 -git 版本不说而且是 Qt6 的包。以前打 Qt 包的经历自不必说,经常需要手动打包这样的项目还是非常难受的(难受在 debian 上 qt6 依赖并不全,或者可能拆包太细了我找不到)。前面提到了有 makedeb 这样的平台提供类似 PKGBUILD 的打包脚本并且有托管仓库,而里面,只有 stable 版本。但是毕竟本着能用就行要求不高的原则,先装上用一下。连着 jdk 装好后还姑且启动了几次 MC 1.16.5 试了一下看上去并没有大问题。

接下来开始找 neovim,然后…源的版本是 0.7.2,我的配置文件是 0.9.2 的…不兼容不说,迁移过去也非常麻烦,这些配置文件是长年累月积攒下来的,迁移也是个大工程。那没办法,姑且用官方 binary 包用一下。能用就行。但是 vim…可就太麻烦了,我需要的包括 clipboard 的编译选项 debian 的几个 vim 包都是默认不开的。但是要是使用 vim-nox 的话又不完全支持我在用的插件。那只能先用 vim-common 凑合下了。然后,我 zotero 呢?plasma 5.27.8 不应该存在的 bug (夜览相关的) 怎么还有呢?我 grub-btrfs 呢?我 mmc 呢?我 freetype2 怎么这么多不相关的依赖它的呢?说好的拆包细呢?/usr/game 是什么上古遗留?我 usr-merge呢?有一种,自己的偏见问题主动离开亲妈去找后妈,结果后妈问题更多的即视感。

想着先把那些放一放,咱们先把 Nvidia Optimus Sync 和显卡直出配了,然后查了一下 wiki , 被告知应当使用 nvidia-detect 检测脚本决定用什么驱动包。然后,该脚本检测到我的是 30 系移动端卡之后,自己退出了…好嘛,凭借在 Arch 上的经验,咱们先把开源驱动干掉,blacklist 之,然后装 nvidia-drivers 大概就没什么问题了。然后问题就来了,Debian Wiki 上给的 Optimus Sync 的 X11 配置文件样例,根本就不是做 Optimus Sync 的。那就还是参考一下 Arch Linux Wiki 姑且配一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# /etc/X11/xorg.conf.d/10-nvidia-drm-outputclass.conf

Section "OutputClass"
    Identifier "intel"
    MatchDriver "i915"
    Driver "modesetting"
EndSection

Section "OutputClass"
    Identifier "nvidia"
    MatchDriver "nvidia-drm"
    Driver "nvidia"
    Option "AllowEmptyInitialConfiguration"
    Option "PrimaryGPU" "yes"
    ModulePath "/usr/lib/nvidia/xorg"
    ModulePath "/usr/lib/xorg/modules"
EndSection

但是,Debian 的 ModulePath 位置并不一致,需要找一下。不过问题也不大,总归dpkg -L 也能找到。但是问题就来了,nvidia-fbc 并没有随 nvidia-drivers 一起安装,nvenc 也并没法启用。但是总归是拆包的问题装上去就没问题了,大概。

更加麻烦起来了

总体来说,虽然我的需求奇奇怪怪但是 Debian 大体上能满足,我也不多说什么。直到和群友聊起来,Minecraft 的 In-Game 打开文件夹没法打开。然后我试了一下,确实打不开。虽然资源包什么的我日常很少动,但是总归打不开还是很麻烦,明明我在 Arch 上都是行为正常的…. 打印了一下日志,发现如下问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
java.security.PrivilegedActionException: null
  at java.security.AccessController.doPrivileged(AccessController.java:573) ~[?:?]
  at net.minecraft.class_156$class_158.method_669(class_156.java:313) ~[client-intermediary.jar:?]
  at net.minecraft.class_156$class_158.method_673(class_156.java:324) ~[client-intermediary.jar:?]
  at net.minecraft.class_5375.method_29670(class_5375.java:102) ~[client-intermediary.jar:?]
  at net.minecraft.class_4185.method_25306(class_4185.java:94) ~[client-intermediary.jar:?]
  at net.minecraft.class_4264.method_25348(class_4264.java:56) ~[client-intermediary.jar:?]
  at net.minecraft.class_339.method_25402(class_339.java:189) ~[client-intermediary.jar:?]
  at net.minecraft.class_4069.method_25402(class_4069.java:38) ~[client-intermediary.jar:?]
  at net.minecraft.class_312.method_1611(class_312.java:98) ~[client-intermediary.jar:?]
  at net.minecraft.class_437.method_25412(class_437.java:409) ~[client-intermediary.jar:?]
  at net.minecraft.class_312.method_1601(class_312.java:98) ~[client-intermediary.jar:?]
  at net.minecraft.class_312.method_22686(class_312.java:169) ~[client-intermediary.jar:?]
  at net.minecraft.class_1255.execute(class_1255.java:102) ~[client-intermediary.jar:?]
  at net.minecraft.class_312.redirect$foc001$viafabricplus$redirectSync(class_312.java:3538) ~[client-intermediary.jar:?]
  at net.minecraft.class_312.method_22684(class_312.java:169) ~[client-intermediary.jar:?]
  at org.lwjgl.glfw.GLFWMouseButtonCallbackI.callback(GLFWMouseButtonCallbackI.java:43) ~[lwjgl-glfw-3.3.1.jar:?]
  at org.lwjgl.system.JNI.invokeV(Native Method) ~[lwjgl-3.3.1.jar:?]
  at org.lwjgl.glfw.GLFW.glfwWaitEventsTimeout(GLFW.java:3474) ~[lwjgl-glfw-3.3.1.jar:?]
  at com.mojang.blaze3d.systems.RenderSystem.limitDisplayFPS(RenderSystem.java:237) ~[client-intermediary.jar:?]
  at net.minecraft.class_310.method_1523(class_310.java:1244) ~[client-intermediary.jar:?]
  at net.minecraft.class_310.method_1514(class_310.java:802) ~[client-intermediary.jar:?]
  at net.minecraft.client.main.Main.main(Main.java:250) ~[minecraft-1.20.1-client.jar:?]
  at net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:468) ~[fabric-loader-0.14.21.jar:?]
  at net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74) ~[fabric-loader-0.14.21.jar:?]
  at net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23) ~[fabric-loader-0.14.21.jar:?]
  at org.prismlauncher.launcher.impl.StandardLauncher.launch(StandardLauncher.java:88) ~[NewLaunch.jar:?]
  at org.prismlauncher.EntryPoint.listen(EntryPoint.java:126) ~[NewLaunch.jar:?]
  at org.prismlauncher.EntryPoint.main(EntryPoint.java:71) ~[NewLaunch.jar:?]
Caused by: java.io.IOException: Cannot run program "xdg-open": error=13, 权限不够
  at java.lang.ProcessBuilder.start(ProcessBuilder.java:1143) ~[?:?]
  at java.lang.ProcessBuilder.start(ProcessBuilder.java:1073) ~[?:?]
  at java.lang.Runtime.exec(Runtime.java:594) ~[?:?]
  at java.lang.Runtime.exec(Runtime.java:453) ~[?:?]
  at net.minecraft.class_156$class_158.method_671(class_156.java:313) ~[client-intermediary.jar:?]
  at java.security.AccessController.doPrivileged(AccessController.java:569) ~[?:?]
  ... 27 more
Caused by: java.io.IOException: error=13, 权限不够
  at java.lang.ProcessImpl.forkAndExec(Native Method) ~[?:?]
  at java.lang.ProcessImpl.<init>(ProcessImpl.java:314) ~[?:?]
  at java.lang.ProcessImpl.start(ProcessImpl.java:244) ~[?:?]
  at java.lang.ProcessBuilder.start(ProcessBuilder.java:1110) ~[?:?]
  at java.lang.ProcessBuilder.start(ProcessBuilder.java:1073) ~[?:?]
  at java.lang.Runtime.exec(Runtime.java:594) ~[?:?]
  at java.lang.Runtime.exec(Runtime.java:453) ~[?:?]
  at net.minecraft.class_156$class_158.method_671(class_156.java:313) ~[client-intermediary.jar:?]
  at java.security.AccessController.doPrivileged(AccessController.java:569) ~[?:?]
  ... 27 more

我写 java ,我也用 process builder 调用过,但问题是这问题是怎么会权限不够的。查了一下 java 的进程,归属于我的账户下,而且 jdk 在家目录,按理讲权限是足够的,而且 xdg-open 本身不是 /sbin 下的脚本。于是决定写个 Demo 验证下问题。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# OpenURL.java
import java.io.IOException;

public class OpenURL {
    public static void main(String[] args) {
        String url = "file:/home/ddqi/";
        try {
            ProcessBuilder processBuilder = new ProcessBuilder("xdg-open", url);
            Process process = processBuilder.start();
            int exitCode = process.waitFor();
            if (exitCode == 0) {
                System.out.println("成功打开URL: " + url);
            } else {
                System.err.println("无法打开URL: " + url);
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试MA

1
2
3
# MANIFEST.MF
Manifest-Version: 1.0
Main-Class: OpenURL

然后用 jdk17 编译打包一下:

1
2
3
javac OpenURL.java

jar -cfm OpenURL.jar MANIFEST.MF OpenURL.class

运行:

1
java -jar OpenURL.jar

很明显的一个问题,报错和 MC 的报错一致。说明可能不是代码的问题。思路转变一下,CentOS 上我被 SELinux 恶心过,但是还有个东西是其他发行版上用的,Apparmor,也是内核级别的,我打自己的内核时候都是去掉支持的。那么没办法,先至少关掉 Apparmor 试一下,添加内核参数,重启,然后 aa-status 一下,确认关闭。重新运行一次该 jar 文件,得到成功响应但是却无法拉起 dolphin 的问题。那估计只能是 jdk 少东西了或者依赖什么少了…

于是开始重复:改 Apparmor 规则,跑 demo 验证。但是连最开始没出现问题的 jdk8 在 demo 中行为也不正常。按理来讲 Apparmor 启用时候至少 jdk8 行为在 In-Game 中是正常的,java17 的可能是撞了 java 自己的安全策略。但是这样的话又解释不清同一套 jdk 为什么在 Arch 上就行为正常。(这里定义正常行为为未开启任何策略下,java process builder 能够掉用其他 os 的套件拉起进程)

Debian -> Arch LInux

正常情况下发现问题,分析问题,解决问题对于正常人来说是一个正常的流程。但是这是一个 不正常的问题,我的怒气值已经满了,以及我最低的要求都满足不了我要你干甚。惹不起老子躲不起吗,走走走打道回府。

安装回去的流程更简单,本身 squashfs 就是根分区的最小完整克隆。释放回去就完事了。步骤还是轻车熟路:下 live CD,写盘,重启。

直接格调这个折磨我心态到爆炸的 Debian :

1
2
# -f 强制重做文件系统
mkfs.btrfs -f /dev/nvme1n1p2

然后把外接 SSD 挂上来看看原来的 fstab 怎么写:

1
2
3
4
5
6
7
mkdir -p {test,test1}

mount  /dev/sda1 ./test

mount  ./test/backup-archlinux.squashfs ./test1

cat ./test1/etc/fstab

分区布局是这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# /dev/nvme1n1p2
UUID=be72b201-80a6-4224-ba13-4bb6e0414577       /               btrfs           rw,relatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=256,subvol=/@      0 0

# /dev/nvme1n1p2
UUID=be72b201-80a6-4224-ba13-4bb6e0414577       /tmp            btrfs           rw,relatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=257,subvol=/@tmp   0 0

# /dev/nvme1n1p2
UUID=be72b201-80a6-4224-ba13-4bb6e0414577       /usr/local      btrfs           rw,relatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=258,subvol=/@usr_local     0 0

# /dev/nvme1n1p2
UUID=be72b201-80a6-4224-ba13-4bb6e0414577       /var/log        btrfs           rw,relatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=259,subvol=/@var_log       0 0

# /dev/nvme0n1p1 LABEL=SYSTEM
UUID=BE42-0216          /boot/efi       vfat            rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro    0 2

# /dev/nvme1n1p3
UUID=97ed42a1-e595-4009-81f0-470f9d183109       /home           ext4            rw,relatime     0 2

# /dev/nvme1n1p2
UUID=be72b201-80a6-4224-ba13-4bb6e0414577       /.snapshots     btrfs           rw,relatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=260,subvol=/@snapshots     0 0

那子卷布局还是按照原来的做:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 先把根挂上来,开启压缩(必须和打包前的分区inode保持一致)
mount -o compress=zstd /dev/nvme1n1p2 /mnt
# 根分区
btrfs subvolume create /mnt/@
# /tmp
btrfs subvolume create /mnt/@tmp
# /var/log
btrfs subvolume create /mnt/@var_log
# /usr/local
btrfs subvolume create /mnt/@usr_local
# /.snapshots
btrfs subvolume create /mnt/@snapshots

# 卸载根分区
umount /mnt

接着直接挂分区:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 根分区和子卷
mount -o compress=zstd,subvol=@ /dev/nvme1n1p2 /mnt
# 创建挂载点
mkdir -p /mnt/{home.usr/local,proc.run,sys,media,dev,var/log,tmp,.snapshots,boot/efi}
# 挂载其他子卷
mount -o compress=zstd,subvol=@tmp /dev/nvme1n1p2 /mnt/tmp
mount -o compress=zstd,subvol=@var_log /dev/nvme1n1p2 /mnt/var/log
mount -o compress=zstd,subvol=@usr_local /dev/nvme1n1p2 /mnt/usr/local
mount -o compress=zstd,subvol=@snapshots /dev/nvme1n1p2 /mnt/.snapshots
# efi 和 home
mount /dev/nvme0n1p1 /mnt/boot/efi
mount /dev/nvme1n1p3 /mnt/home

释放镜像:

1
2
3
4
# 取消挂载镜像
umount ./test1
# 释放镜像
unsquashfs -f -d /mnt test/backup-archlinux.squashfs

完成后基本就没什么问题了,但是需要重新装一下 grub:

1
2
3
4
5
6
7
8
# chroot 到系统
arch-chroot /mnt
# 删除旧的 grub 引导
rm -rf /boot/efi/EFI/Debian
# 装载引导器
grub-install --target=x86_64-efi --efi-directory=/boot/efi/ --recheck
# 生成配置
grub-mkconfig -o /boot/grub/grub.cfg

由于根分区 UUID 变了,因为 fstab 还是得改一下,直接退出 chroot 环境,执行:

1
genfstab -U /mnt >> /mnt/etc/fstab

重启一下发现又回到和蔼可亲的 Arch Linux 了。

总结

先排除自己的菜的原因。我可能菜,但是桌面系统用的如此憋屈感觉应该不全是我的问题…每次想从 Arch 叛逃,但是又逃回来,每次都是走之前觉得 Arch 这不好那不好骗自己能有办法安心在新发行版下熟悉,但是用着用着却又想起之前的各个好处。尤其是有 aur 这样的整合的,统一的平台这件事。省去了很多麻烦,也方便统一管理。

但是话又说回来,现在的自己竟然这么习惯于逃避了…果然是因为害怕的事情变多了的缘故么。