Linux-To-Go顾名思义就是类似windows-to-go的一个把系统环境带着走的系统。如果是IT打工人,应该想要一个能自由带着走的笔记本吧,更进一步,笔记本可以换成现在性能满足要求的PSSD(Portable SSD)

我会从系统引导到日常使用说明我是如何解决一些实现上的细节,假设有多台电脑,公司,家里。系统也就是Linux在PSSD上,PSSD跟着人走,你人只有一个,所以当前最新的系统时间线也只有一个。系统有快照,并且会跟另一个位置的镜像系统快照同步,你可以通过快照回退到旧的时间线的系统。快照同步可以解决PSSD丢失问题,当然了需要勤快照或者设置成自动快照备份并同步。

系统引导

现今的主板,从EFI启动到Grub之后,然后grub自动通过不同设备,对不同硬件加载对应的内核参数,然后引导进入Gentoo系统。当然你如果所有硬件都一模一样,那么grub就简单了。

    if [ "x$grub_platform" = xefi ]; then
        set gfxpayload=keep
        insmod bli
    fi
    insmod gzio
    insmod part_gpt
    insmod fat
    insmod btrfs
    ### https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.7.0.pdf
    ###
    smbios --type 1 --get-string 4 --set board_vendor
    ### /sys/devices/virtual/dmi/id/product_name
    smbios --type 1 --get-string 5 --set board_name
    ### /sys/devices/virtual/dmi/id/product_sku
    smbios --type 1 --get-string 25 --set board_sku
    ### /sys/devices/virtual/dmi/id/product_family
    smbios --type 1 --get-string 26 --set board_family

    insmod regexp
    regexp --set=rootdisk '(.*),.*' $root
    set root="$rootdisk,gpt2"
    probe --set=rootuuid --fs-uuid $rootdisk,gpt2

    ## get activefs
    for activefs_path in ($root)/activefs-*; do
        set activefs=${activefs_path}
        break
    done
    for activefs_path in ($root)/activefs-*; do
        echo ${activefs_path}
        if test "x$activefs_path" \> "x$activefs" ; then
            set activefs=${activefs_path}
        fi
    done
    echo $activefs

    ## get kernelversion
    for vmlinuz_path in ${activefs}/boot/vmlinuz-*-x86_64; do
        regexp --set=kernelversion '.*/boot/vmlinuz-([0-9\.]+-gentoo.*)-x86_64' ${vmlinuz_path}
        break
    done
    for vmlinuz_path in ${activefs}/boot/vmlinuz-*-x86_64; do
        #echo ${vmlinuz_path}
        regexp --set=kernelversion_path '.*/boot/vmlinuz-([0-9\.]+-gentoo.*)-x86_64' ${vmlinuz_path}
        if test "x$kernelversion_path" \> "x$kernelversion" ; then
            set kernelversion=${kernelversion_path}
        fi
    done
    echo $kernelversion

    if [ "x$board_family" = "xPrecision" ]; then
        echo    ${board_name}
        set     linuxcmd="linux ${activefs}/boot/vmlinuz-${kernelversion}-x86_64 root=UUID=${rootuuid} rootflags=ssd,ssd_spread,noatime,compress=zstd:3,discard=async rw acpi_osi=Linux mitigations=off sysrq_always_enabled=1 systemd.unified_cgroup_hierarchy=1 snd.slots=snd_usb_audio,snd_hda_intel binder.devices=binder,hwbinder,vndbinder randomize_kstack_offset=0 i915.mitigations=off psi=1 net.ifnames=0 systemd.unit=graphical.target zswap.compressor=zstd zswap.zpool=zsmalloc zswap.enabled=0 acpi_mask_gpe=0x42 modules_load=ftdi_sio,orbment_dt orbment_dt.dpi_scale_x=60 orbment_dt.dpi_scale_y=53 orbment_dt.wlr_renderer=2 usb-storage.quirks=152d:0583:f"
        eval    ${linuxcmd}
        initrd  ${activefs}/boot/intel-uc.img ${activefs}/boot/initramfs-${kernelversion}-x86_64.img
    elif [ "x$board_name" = "xMS-7C35" ]; then
        echo    ${board_name}
        set     linuxcmd="linux ${activefs}/boot/vmlinuz-${kernelversion}-x86_64 root=UUID=${rootuuid} rootflags=ssd,ssd_spread,noatime,compress=zstd:3,discard=async rw acpi_osi=Linux mitigations=off sysrq_always_enabled=1 systemd.unified_cgroup_hierarchy=1 binder.devices=binder,hwbinder,vndbinder randomize_kstack_offset=0 net.ifnames=0 initcall_blacklist=acpi_cpufreq_init amd_pstate.shared_mem=1 amd_pstate=active modules_load=nct6775 systemd.unit=graphical.target zswap.compressor=zstd zswap.zpool=zsmalloc zswap.enabled=0 mt7921e.disable_aspm=1 drm.edid_firmware=HDMI-A-2:edid/ehomewei.bin modules_load=nct6775,ftdi_sio,orbment_dt orbment_dt.dpi_scale_x=3 orbment_dt.dpi_scale_y=1 orbment_dt.wlr_renderer=2 usb-storage.quirks=152d:0583:f"
        eval    ${linuxcmd}
        #apparmor=1 security=apparmor lsm=lockdown,yama,apparmor,bpf amd_pstate.shared_mem=1 amd_pstate=passive
        #usb-storage.quirks=152d:0583:u
        initrd  ${activefs}/boot/amd-uc.img ${activefs}/boot/initramfs-${kernelversion}-x86_64.img

这是部分grub.cfg代码,从配置代码里面看出,grub 会自动搜索当前ESP分区所在磁盘的下一个btrfs root分区进行启动,不同硬件会有不同参数,这里我自动搜索了当前ESP所在磁盘的下一个btrfs分区的UUID进行启动,这样可以让这一份grub可以在别的 ESP上,也可以正确启动到对应的系统。

关于 linux的 启动参数。

  • 我添加了 ssd_spread,这个用于让btrfs全局分配可用空间,而不是最小化使用让主控分配,这样可以让廉价SSD速度稍微快一点
  • btrfs开启了zstd:3 压缩
  • discard 异步也开起来,我就不每周周期trim了
  • 会通过board_family来匹配正确的硬件,如果名称匹配就启动到对应的内核参数
  • btrfs 分区是一个 subvolid=5 的主卷,通过查找最新一个 activefs-xxxxx-rw 子卷进行启动,该子卷里面是完整的linux根目录分区,包含home目录,boot目录,内核vmlinuz文件也在其中boot目录里面,所以内核跟子卷在一起,而不是放在ESP分区里面。也就是内核也可以版本控制。子卷里面的activefs-xxxx-rw/boot/vmlinuz 跟 activefs-xxxx-rw/lib/modules/的内核是匹配的不会出现意外启动失败。

ROOT 分区

该btrfs分区 的subvolid=5下面有多份系统镜像, btrfs的subvolid=5不会直接进行挂载,btrfs 会设置 activefs 为 default subvolue,所以不指定subvol的话,默认mount会mount到设置的 default最新activefs

btrfs subvolume list /
ID 283 gen 331310 top level 5 path activefs-202402131805-rw
ID 293 gen 273316 top level 5 path rootfs-202404250135
ID 294 gen 291083 top level 5 path rootfs-202405031342
ID 295 gen 312138 top level 5 path rootfs-202405130327
ID 296 gen 326424 top level 5 path rootfs-202405191812

其中activefs-xxxxxxx-rw 是 最新时间线的可改写的系统,其他 rootfs-xxxxx 均为 Readonly快照,这些子卷可以非常方便的通过工具 在PSSD插入当前电脑的时候,把当缺少的系统快照同步到家里,或者公司的硬盘上做备份,这点相当重要。毕竟PSSD要是丢了什么都没了。

另外,btrfs的 Data,MetaData,System如果不是单份全部改成单份,SSD没必要双份,特别是Data,

btrfs balance start -sconvert=single -mconvert=single -dconvert=single /
btrfs filesystem usage /

PSSD 选择

该u盘ssd一定要选择一个可靠的并且便携的,我使用Chipfancier UME Nano,以前使用 external SSD enclosure 这种M2 硬盘盒子,后来发现带一条线太麻烦,不能方便放到口袋,于是换成u盘形式的ssd。

还有关于PSSD的选择涉及到主控是否支持UASP,linux对 USAP的支持也是需要看硬件的,如果过于老旧的主板可能需要关闭UASP,比如usb-storage.quirks=152d:0583:u 这个内核参数。当前的USB主控桥接器有JMS583,ASM的,RTL的,还有Silicon Motion SM2320这种一体的。一定要选择新的。关于UASP也跟是否支持DISCARD有关,如果用了JMS583,就需要像我一样手动开启 USAP的TRIM支持

/etc/udev/rules.d/10-ssd.rules

ACTION=="add|change", ATTRS{idVendor}=="152d", ATTRS{idProduct}=="0583", SUBSYSTEM=="scsi_disk", ATTR{provisioning_mode}="unmap"
ACTION=="add|change", ATTRS{idVendor}=="152d", ATTRS{idProduct}=="0583", SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", ATTR{queue/rotational}="0"
ACTION=="add|change", ATTRS{idVendor}=="152d", ATTRS{idProduct}=="0583", SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", ATTR{queue/scheduler}="kyber"

再后来,我换掉了chipfancier,换成了三星PSSD t7,因为该盘三星自己的主控,并且各种标准都有实现。比那些只是贴盘的产品好很多。

操作系统 systemd

我只试过systemd的系统,因为有systemd-gpt-auto-generator 可以自动发现我的ESP分区,这样,ls /efi 就自动挂载当前系统的ESP了。

以前我整个btrfs所在分区用luks磁盘加密,现在觉得没必要。

关于swap,这个通过zram挂载,就不用PSSD的文件系统当swap了,也不用各个地方的硬盘做swap,如果需要的话,也可以通过给SWAP分区打LABEL,然后通过LABEL挂载,并且挂载参数加nofail。 cat /etc/udev/rules.d/10-zram.rules

KERNEL=="zram0", SUBSYSTEM=="block", DRIVER=="", ACTION=="add", ATTR{disksize}=="0", ATTR{disksize}="4096M", RUN+="/sbin/mkswap $env{DEVNAME}"

如果跟我一样是gentoo,那么编译软件的时候记得要用 x86-64 generic 的编译优化参数,不应该是具体的cpu了,防止别的硬件指令集没有而出现问题。 /etc/portage/make.conf

COMMON_FLAGS="-march=x86-64 -mtune=generic -O2 -pipe"

应用软件

不同系统的硬件内存不一样,而且显示器分辨率DPI也不一样,这需要在各个启动程序的启动脚本进行动态调整,比如有cgroup内存限制,需要计算百分比限制。浏览器启动的时候,自动适配当前显示器的DPI大小防止字体大小不一致。

sway也就是 桌面启动的时候有些许判断,不同硬件自定义桌面的一些环境变量

# Environ
export HARDWARE_MODEL=$(</sys/devices/virtual/dmi/id/board_name)
DPI_SCALE_X=$(</sys/module/orbment_dt/parameters/dpi_scale_x)
DPI_SCALE_Y=$(</sys/module/orbment_dt/parameters/dpi_scale_y)
GDK_DPI_SCALE=$(printf %.16f $(echo ${DPI_SCALE_X} / ${DPI_SCALE_Y} | bc -l))
export GDK_DPI_SCALE

case ${HARDWARE_MODEL} in
  "MEG X570 ACE (MS-7C35)")
    export WLR_NO_HARDWARE_CURSORS=1
    ;;
  *)
    true
esac

case $(</sys/module/orbment_dt/parameters/wlr_renderer) in
  "1")
    export WLR_RENDERER=gles2
    ;;
  "2")
    export WLR_RENDERER=vulkan
    #export VK_INSTANCE_LAYERS=VK_LAYER_MESA_overlay
    ;;
  *)
    true
esac

exec /usr/bin/sway $@ 2>~/.local/share/sway/sway.log 1>&2

比如这是启动shell的时候,对nushell 限制内存使用率

mem_size = int(os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') / 1024)
mem_limit_high = int(mem_size * 0.725)
mem_limit_max = int(mem_size * 0.725)

env = os.environ

os.execve("/usr/bin/systemd-run", ["/usr/bin/systemd-run",
                                   "--user",
                                   "--scope",
                                   "-u", "nu-{}.scope".format(uuid.uuid4()),
                                   "-p", "CPUQuota={}%".format((multiprocessing.cpu_count()-1)*100),
                                   "-p", "MemoryHigh={}K".format(mem_limit_high),
                                   "-p", "MemoryMax={}K".format(mem_limit_max),
                                   "-p", "ManagedOOMMemoryPressure=kill",
                                   "nu",
                                   ],
          env
          )

firefox 会计算当前DPI大小并且写入 firefox prefs.ini 启动的时候适配

ls -l /usr/lib64/firefox/browser/defaults/preferences/local-settings.js
lrwxrwxrwx 1 root root 40 Dec 26 17:47 /usr/lib64/firefox/browser/defaults/preferences/local-settings.js -> /run/user/1000/firefox-local-settings.js
def firefox_prefs_dpi(dpi_scale):
    with open("{}/firefox-local-settings.js".format(XDG_RUNTIME_DIR), 'w') as fp:
        fp.write("pref(\"layout.css.devPixelsPerPx\", \"{}\", locked);\n".format(dpi_scale))
        fp.write("pref(\"browser.discovery.enabled\", false, locked);\n")
        fp.write("pref(\"browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons\", false, locked);\n")
        fp.write("pref(\"browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features\", false, locked);\n")
        fp.write("pref(\"browser.sessionstore.interval\", 1800000, locked);\n")
        fp.flush()

if "GDK_DPI_SCALE" in env.keys():
    firefox_prefs_dpi(env["GDK_DPI_SCALE"])
    env.pop("GDK_DPI_SCALE")

如果用了 foot terminal ,也会计算终端的字体大小

fontsize = 12

try:
    settings = Gio.Settings.new("org.gnome.desktop.interface")
    gdk_dpi_scale = settings.get_double("text-scaling-factor")
    fontsize = round(gdk_dpi_scale * fontsize)
except:
    pass

try:
    gdk_dpi_scale = os.environ["GDK_DPI_SCALE"]
    gdk_dpi_scale = float(gdk_dpi_scale)
    fontsize = round(gdk_dpi_scale * fontsize)
except:
    pass

os.execve("/usr/bin/foot", ["/usr/bin/foot",
                            "-f", "monospace:size={}".format(fontsize),
                            "tmux", "new-session", "-A", "-s", "id-{}".format(os.getuid())
                            ],
          os.environ
          )