前言

众所周知,给云服务器安装操作系统可以使用服务商提供的预装镜像,也可以使用服务商额外的挂载 ISO 镜像功能安装。对于一些没有提供我们想要的操作系统镜像,又不提供挂载镜像功能的服务商,比如 QQ云、阿里云的轻量服务器等,可以使用 vps2arch 等第三方脚本(或手动操作,原理一致)覆盖原有的系统安装新的操作系统。但是这种方法有个致命的缺点,就是无法对原有的文件系统进行修改,无法重新分区。这种情况下,哪怕只是想从系统盘空闲空间中划分一块建立SWAP分区也会十分困难,因为预装镜像中常见的ext4文件系统不支持在线 Resize ,必须先 umount 才能进行操作(xfsbtrfs支持在线扩容的特性)。

在之前给 VPS 安装 Archlinux 的时候,我尝试过一个可以脱离系统盘工作的内存系统脚本menhera.sh结果以失败告终。最近,由于在研究给 VPS 安装 NixOS ,还尝试了 Root on tmpfs 的玩法,必须对系统盘进行重新分区,我不得不重拾这个脚本继续探索。

脚本分析

首先我们阅读这个脚本的源码,可以发现这个脚本做的工作其实很简单,大致分为以下几个部分:

  • 下载rootfs镜像

  • 部署镜像

  • 复制系统环境

  • 安装额外的所需软件

  • 转移系统

menhera脚本使用的镜像是 Debian 构建的 rootfs ,格式是squashfs,包含完整的系统内核,是一个可以独立工作的 rootfs 。但是它缺少很多安装系统会用到的工具,而且体积很大,包含完整的内核——只能使用旧系统的内核,不需要自备内核,所以这个镜像并不是很适合用来构建安装系统的内存环境。之前的尝试中,我使用过 ArchISO 的 airootfs 镜像来替代 Debian 的镜像,但是效果不尽人意。虽然 airootfs 中会包含更多的工具,但是同是包含内核的 rootfs ,这个镜像的体积也非常的大(600MB+)。不过经过我的测试,Archlinux 的bootstrap在这个环境中也可以工作,它不包含内核,包含绝大多数需要的工具,而且体积不大(100MB+),很适合用于内存环境的构建。

部署镜像指在 tmpfs 中挂载这个镜像,需要使用到 tmpfs 和镜像对应文件系统需要的工具。

需要复制的系统环境主要包括含ROOT密码的/etc/passwd/etc/shadow文件,以及包含系统 DNS 的/etc/resolv.conf文件,可以直接在内存环境中使用旧系统的密码和 DNS ,省去自行配置的过程。

安装额外的软件指给内存环境安装需要但不包含的软件,比如 SSH 服务器程序。

这一切都做好之后就可以将系统根目录转移到内存中去,进入内存环境。

开始工作

由于清理占用系统盘的进程需要进行多次,这种情况下无法运行脚本,所以这里我们需要手动执行命令,并且不能使用 SSH 连接服务器,必须使用服务商提供的 VNC 连接或串口终端(Azure)。为了确保内存环境中的软件能正常工作,需要原有系统的内核版本尽可能的高,这里推荐新装服务器直接使用 Debian 11 的系统。准备好后就可以开始了。

下载镜像

首先下载archlinux-bootstrap镜像。

latest版本的镜像名称是与当下最新的版本号有关,在自己操作的时候版本号可能并不一致,操作时请自行获取最新的镜像下载地址。

国内公网可以使用阿里云、腾讯云等公共云服务的镜像:

1
curl https://mirrors.aliyun.com/archlinux/iso/latest/archlinux-bootstrap-XXXX.XX.XX-x86_64.tar.gz -O

教育网建议使用教育网源的镜像,比如清华源:

1
curl https://mirrors.tuna.tsinghua.edu.cn/archlinux/iso/latest/archlinux-bootstrap-XXXX.XX.XX-x86_64.tar.gz -O

海外主机可以使用海外源:

1
curl https://mirror.rackspace.com/archlinux/iso/latest/archlinux-bootstrap-XXXX.XX.XX-x86_64.tar.gz -O

部署内存环境

下载好的镜像我们需要部署到内存中。

首先开辟一块内存盘用于存储环境,这里我们将这个环境挂载到/tmp/menhera下:

1
2
mkdir /tmp/menhera
mount -t tmpfs -o size=100% tmpfs /tmp/menhera

解压镜像到这个内存盘中:

1
tar zxf archlinux-bootstrap-2022.03.01-x86_64.tar.gz -C /tmp/menhera

由于原版镜像外层套有一层名为root.x86_64的文件夹,需要将 root 从这个文件夹中移动到内存盘的根下,否则后面的pivot_root命令无法执行。

这里不能使用常规的mv指令,需要额外指定参数:

1
2
mv /tmp/menhera/root.x86_64/* -t /tmp/menhera
rmdir /tmp/menhera/root.x86_64

复制系统必需文件

直接从系统根目录复制到内存系统的根目录下:

1
2
3
cp -r /etc/resolv.conf /tmp/menhera/etc
cp -r /etc/passwd /tmp/menhera/etc
cp -r /etc/shadow /tmp/menhera/etc

安装需要的额外软件包

这里需要chroot到内存系统中安装需要的软件包,比如btrfs-progsvim,都不包含在bootstrap中。

首先 bind 系统目录,使这个内存系统可以运转:

1
2
3
4
mount -t proc /proc /tmp/menhera/proc
mount --make-rslave --rbind /sys /tmp/menhera/sys
mount --make-rslave --rbind /dev /tmp/menhera/dev
mount --make-rslave --rbind /run /tmp/menhera/run

然后设置软件源:

1
echo 'Server = http://mirror.rackspace.com/archlinux/$repo/os/$arch' > /tmp/menhera/etc/pacman.d/mirrorlist

记得替换为适合自己的软件源。

初始化pacman-keyring并更新软件仓库:

1
2
3
chroot /tmp/menhera pacman-key --init
chroot /tmp/menhera pacman-key --populate archlinux
chroot /tmp/menhera pacman -Sy --noconfirm

也可以安装软件包的同时更新软件仓库:

1
2
chroot /tmp/menhera pacman -Sy vim --noconfirm
chroot /tmp/menhera pacman -S btrfs-progs --noconfirm

注意如果是小内存(如1G,更小内存的机型尚未测试)的服务器在这一步可能会因为内存不足更新失败,可以一个一个安装软件包。如果不幸中断了,可以删除锁后重新执行指令。

1
2
rm /tmp/menhera/var/lib/pacman/db.lck
chroot /tmp/menhera pacman -S btrfs-progs --noconfirm

过于庞大的软件包(如 Nix )建议进入内存环境后再安装。

转移根目录

在这之前请把额外需要做的工作全部做完。如果需要使用额外的内核模块(如btrfs),请提前加载:

1
modprobe btrfs

为转移后的旧系统建立挂载点:

1
mkdir /tmp/menhera/mnt/old

开始转移根目录:

1
2
mount --make-rprivate /
pivot_root /tmp/menhera /tmp/menhera/mnt/old

将旧系统的系统目录改绑到当前系统下:

1
2
3
4
5
mount --move /mnt/old/dev /dev
mount --move /mnt/old/run /run
mount --move /mnt/old/sys /sys
mount --move /mnt/old/proc /proc
mount -t tmpfs -o size=100% tmpfs /tmp

初步解除旧系统的占用:

1
2
3
4
swapoff -a # 卸载旧系统的SWAP分区
systemctl daemon-reload # 重载 systemd 服务
systemctl daemon-reexec # 重新执行 systemd 服务
fuser -kvm /mnt/old -15 # 停止所有旧系统的进程

请按顺序执行。

执行完后,系统会进入到内存环境的登陆界面,输入root和旧系统root用户的密码即可登陆到内存环境中。

解除系统分区的占用

这部分的细节我其实也并不是很理解,希望有大佬来补充。

之前的清理其实并不彻底,此时 umount 系统分区会提示 busy 。

适合我的服务器的步骤如下,依次执行以下指令:

1
2
3
4
5
6
fuser -kvm /mnt/old -15 # 再次清理旧系统的进程,此时会提示无法清除的进程
umount /dev/vda1 # 这时应该可以 umount 系统分区了,但是重新分区会提示 busy
systemctl daemon-reload # 再次重载 systemd 服务
systemctl daemon-reexec # 再次重新执行 systemd 服务
fuser -kvm /mnt/old -15 # 清理残留进程
fuser -kvm /mnt/old -15 # 再次清理残留进程,这次清理完后应该会跳回登陆界面

到这里应该就可以顺利操作系统分区了。

如果内存不足,进入内存环境后建议立刻创建 SWAP 分区并挂载。

安装 NixOS 的一点提示

如果要安装 NixOS 系统,必须在内存环境中安装 Nix 。

第一个问题就是空间不足。 Nix 会在根目录创建/nix目录存放store path,由于根目录挂载在内存中,也会占用内存空间,但并不会使用挂载的 SWAP 空间。所以建议提前建立分区挂载到/nix。推荐使用btrfs文件系统,建立一个临时的 subvolume ,使用完后直接删除 subvolume 即可。

第二个问题是使用官方脚本会出现不明原因的无法创建用户的错误。可以通过安装 Archlinux 系统源中的 Nix 让 pacman 创建用户实现曲线救国的效果。当然这里也可以直接使用 Archlinux 源中的 Nix ,但是有一点局限:

使用 Archlinux 官方源里的 Nix 安装的包,不能直接在系统的 Shell 里调用,需要用nix-shell -p <软件名>的方式创建一个包含该软件的 Nix Shell 在里面使用,比较麻烦。

如果使用源中的 Nix ,安装系统时会出现文件不存在的问题,所以只能使用官方脚本安装。

如果使用官方脚本安装 Nix ,可以先安装源中的 Nix ,卸载后再运行官方脚本。

分步脚本使用方法

我把一些可以自动执行的指令整理成脚本的形式,放在了我的 Github 仓库中,可以自动化执行部分步骤。

首先clone整个仓库,国内主机请注意网络环境:

1
git clone https://github.com/alca7raz/menhera-archlinux

里面包含如下分步脚本:

  • 01-download-image.sh 第一步

  • 02-deploy-ram-sys-and-copy-config.sh 第二步

  • 03-config-ram-sys.sh 第三步和第四步的初始化密钥阶段

也可以直接执行00-init-ram-sys.sh脚本,自动执行以上步骤。脚本执行完毕后需要自己给内存环境安装软件包,具体步骤参考上文

安装完软件包后可以执行10-switch-root.sh,进入内存系统。之后的步骤参考第六步

参考资料

menhera.sh

Github 仓库 - menhera-archlinux