本教程适用于 Scaleway 提供的 仅支持 IPv6 的 VPS,目标是通过 DD 写入方式安装 Ubuntu 22.04 最小系统,并关闭 cloud-init 的自动初始化。
🔗 前置阅读和本文动机
以下是参考文章,建议提前阅读了解 VPS 开通和磁盘配置方法:
不过这些文章中介绍的 Debian 12 DD 安装方法目前大多已经不可用,因为 Debian 12的
cloudimg 镜像超过了 Scaleway 默认的 3G 本地磁盘大小,写入会失败,我的目标是在最
小的尺寸下装下一个尽量现代的系统,多 1G 感觉也是有点难受;而 Debian 11又较旧。因
此我选择了探索如何在Scaleway 上安装 Ubuntu 22.04 Minimal。
📌 前置准备
在这里我假设你已经起好一台 Scaleway Stardust VPS,并关机删除了自带的 10GB 硬盘。
💾 分配本地磁盘(3G)
创建 VPS 时请务必选择本地存储(Local disk),容量建议 3GB,参考上面的文章,不要忘记设置 Boot volume。
🔑 强烈建议:预先添加 SSH 公钥
原因:Scaleway Rescue 模式默认仅提供 SSH 登录,但有时不会展示 root 密码,或者用户名不是 root,容易导致连接困难。最稳妥的方式是提前配置 SSH 公钥。
打开 Scaleway 控制台的添加公钥页面
点击右上角 “Add SSH key”
粘贴你的公钥内容并保存
保存后,该公钥会自动注入 Rescue 模式
重启进入 Rescue 模式
关机,在 Advanced settings -> Boot Mode 里面设置 Use rescue image
,可以顺便设置
Boot volume 为我们新分配的 3GB 硬盘如果你前面没做的话。
设置完成之后开机。
🛠 制作并上传 DD 镜像
✅ 重要提示:必须先将 VPS 切换到 Rescue 模式再进行 DD 操作!
因为我们已经添加了 ssh key,所以救援模式应该也可以直接登陆,ssh 登陆之后运行一下命令创建可以用来 dd 的 ubuntu 22.04 minimal 镜像
运行下面的命令,推荐在 tmux
或者 screen
里面运行
mkfs.ext4 -F /dev/vda mkdir /mnt/vda mount /dev/vda /mnt/vda
apt update && apt install -y qemu-utils
cd /mnt/vda/ wget https://cloud-images.ubuntu.com/minimal/releases/jammy/release/ubuntu-22.04-minimal-cloudimg-amd64.img qemu-img convert -O raw ubuntu-22.04-minimal-cloudimg-amd64.img ubuntu-22.04-minimal.raw tar -cf - ubuntu-22.04-minimal.raw | xz -3 -T1 -v > ubuntu-22.04-minimal.raw.tar.xz mv ubuntu-22.04-minimal.raw.tar.xz /tmp/
umount /dev/vda dd if=/dev/zero of=/dev/vda bs=1M status=progress && sync
|
命令简要说明
- 格式化并挂载磁盘
/dev/vda
,作为临时工作目录;
- 安装工具
qemu-utils
,用于镜像格式转换;
- 下载官方 minimal 镜像(QCOW2 格式)并转换成 raw;
- 使用 tar + xz 压缩打包,生成
.raw.tar.xz
文件;
- 移动压缩文件到
/tmp/
,方便后续传输或写入;
- 最后用
dd
清空磁盘,准备好做 dd 写入。
🧱 DD 写入磁盘镜像
tar -xOvf /tmp/ubuntu-22.04-minimal.raw.tar.xz | dd of=/dev/vda bs=4M status=progress conv=fsync
|
🧹 修复分区表 & 扩容根分区
写入后默认的 root 分区可能不占满磁盘,需要手动修复并扩容。
1. 检查磁盘和分区
如果存在 /dev/vda1
则说明 dd 成功
2. 修复分区表
此时会先提示 /dev/sda
警告,输入 Ignore
忽略,然后应该可以如下内容:
Warning: Not all of the space available to /dev/vda appears to be used...
|
输入 Fix
进行修复分区表
3. 扩容分区到 3G
e2fsck -f /dev/vda1 resize2fs /dev/vda1 parted -s /dev/vda resizepart 1 100%
|
此时运行 fdisk -l /dev/vda
应该会有如下输出
Disk /dev/vda: 2.79 GiB, 3000000000 bytes, 5859375 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: gpt Disk identifier: 8F39EFB4-6CF0-4E98-A9E8-64C65D622F84
Device Start End Sectors Size Type /dev/vda1 227328 5859341 5632014 2.7G Linux filesystem /dev/vda14 2048 10239 8192 4M BIOS boot /dev/vda15 10240 227327 217088 106M EFI System
Partition table entries are not in disk order.
|
🔧 注入 cloud-init 配置(Rescue 模式下操作)
注入配置
运行下面的命令
✅ 重要提示:把命令里面的 YOUR_SSH_PUBKEY
替换成你自己的 ssh 公钥!
mkdir /mnt/vda1 mount /dev/vda1 /mnt/vda1
SEED_DIR="/mnt/vda1/var/lib/cloud/seed/nocloud"
mkdir -p "$SEED_DIR"
cat > "$SEED_DIR/meta-data" <<EOF local-hostname: scaleway-vps EOF
cat > "$SEED_DIR/user-data" <<EOF
final_message: "Cloud-init completed. Will not run again."
ssh_pwauth: true
packages: - openssh-server
users: - name: root ssh_authorized_keys: - <YOUR_SSH_PUBKEY>
runcmd: - sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config - sed -i 's/^#\?PermitRootLogin .*/PermitRootLogin yes/' /etc/ssh/sshd_config - systemctl restart ssh || systemctl restart sshd - touch /etc/cloud/cloud-init.disabled EOF
echo "✅ meta-data 和 user-data 写入完成。"
|
DD 写入的系统中未包含默认 cloud-init 配置,我们需要通过 Rescue 模式挂载系统分区,并注入 cloud-init 的 meta-data 与 user-data,其中 user-data 包含了下面这些功能
- 配置 root 登陆:只允许 root 通过你的 ssh 私钥登陆
- 禁用 cloud-init 后续运行:创建
.disabled
文件,cloud-init 只运行一次后永久禁用。
退出救援并重启
在 Scaleway 页面关闭 VPS,Boot mode 切回 Use local boot
,然后开机,可以打开
console 看看初始化情况,cloud-init 大概需要一分钟左右。
🏁 大功告成
VPS 切回正常启动后,系统将运行一次 cloud-init 并配置 root 的公钥。到此为止,你已
经成功通过安装并初始化了一个干净的 Ubuntu 22.04 Minimal 系统!
One more thing
又捣鼓了个生成 1G ubuntu disk image,uefi only,大概还有 200M 可以用,基本不太能用,推推荐分配 2G。之后打算看看 minimal 那里提供的 rootfs 和 squashfs,或者直接 bootstrap。
下面的脚本需要空间宽裕的电脑上运行,推荐直接进 sudo su
然后 cd /tmp/
然后运行。
运行结束之后 scp
上传到在救援模式下的 Scaleway STARDUST,然后按照上面的流程走就好了。
#!/bin/bash set -e
# === 参数 === IMG_URL="https://cloud-images.ubuntu.com/minimal/releases/jammy/release/ubuntu-22.04-minimal-cloudimg-amd64.img" IMG_ORIG="ubuntu-22.04-minimal-cloudimg-amd64.img" RAW_ORIG="ubuntu-22.04-minimal-cloudimg-amd64.raw" RAW_NEW="ubuntu-22.04-minimal-cloudimg-amd64-efionly-0.95g.raw" RAW_XZ="${RAW_NEW}.tar.xz"
# === 准备工具 === echo "📦 Installing required tools..." sudo apt-get update sudo apt-get install -y qemu-utils parted dosfstools e2fsprogs gdisk
# === 下载 cloudimg === echo "🌀 Downloading cloudimg..." wget -c "$IMG_URL" -O "$IMG_ORIG"
# === 转换为 raw === echo "🔄 Converting to raw..." qemu-img convert -O raw "$IMG_ORIG" "$RAW_ORIG"
# === 绑定原始镜像 === echo "🔁 Mapping original image..." LOOP1=$(sudo losetup --show -Pf "$RAW_ORIG") echo "LOOP1: $LOOP1"
# === 创建新镜像(目标分区号为 15, 1) === echo "🧱 Creating new 950M image..." qemu-img create -f raw "$RAW_NEW" 950M
# 使用 sgdisk 设置分区结构(先计算实际扇区) SECTOR_SIZE=512 EFI_START=2048 EFI_SIZE_MB=16 EFI_END=$((EFI_START + (EFI_SIZE_MB * 1024 * 1024 / 512) - 1)) ROOT_START=$((EFI_END + 1)) #EFI_START=10240 #EFI_END=227327 #ROOT_START=227328 DISK_SIZE=$(stat --format "%s" "$RAW_NEW") TOTAL_SECTORS=$((DISK_SIZE / SECTOR_SIZE)) ROOT_END=$((TOTAL_SECTORS - 34))
sgdisk -o "$RAW_NEW" sgdisk --new=15:${EFI_START}:${EFI_END} --typecode=15:ef00 "$RAW_NEW" sgdisk --new=1:${ROOT_START}:${ROOT_END} --typecode=1:8300 "$RAW_NEW"
# 设置标志位 parted "$RAW_NEW" --script set 15 boot on parted "$RAW_NEW" --script set 15 esp on
# 映射新镜像 loop LOOP2=$(sudo losetup --show -Pf "$RAW_NEW") echo "LOOP2: $LOOP2"
# === 提取原始 UUID 和 PARTUUID === echo "🔍 Extracting original UUIDs..." UUID_ROOT=$(sudo blkid -s UUID -o value "${LOOP1}p1") UUID_EFI=$(sudo blkid -s UUID -o value "${LOOP1}p15" | tr -d '-' | cut -c1-8) echo "Root UUID: $UUID_ROOT" echo "EFI UUID: $UUID_EFI"
echo "🔍 Extracting original PARTUUIDs..." PARTUUID_EFI=$(sudo sgdisk -i=15 "$LOOP1" | grep 'Partition unique GUID' | awk '{print $4}') PARTUUID_ROOT=$(sudo sgdisk -i=1 "$LOOP1" | grep 'Partition unique GUID' | awk '{print $4}') echo "EFI PARTUUID: $PARTUUID_EFI" echo "ROOT PARTUUID: $PARTUUID_ROOT"
# === 格式化新分区 === echo "🧼 Formatting new partitions..." sudo mkfs.vfat -i "$UUID_EFI" -n UEFI "${LOOP2}p15" sudo mkfs.ext4 "${LOOP2}p1" sudo tune2fs -U "$UUID_ROOT" "${LOOP2}p1" sudo e2label "${LOOP2}p1" cloudimg-rootfs
# === 设置 GPT 分区 GUID === echo "🧾 Applying PARTUUIDs to new GPT..." sudo sgdisk --partition-guid=15:$PARTUUID_EFI "$LOOP2" sudo sgdisk --partition-guid=1:$PARTUUID_ROOT "$LOOP2"
# delete loops then remount loops #sleep 3 #sudo losetup -d "$LOOP1" #sudo losetup -d "$LOOP2" #sleep 3 #LOOP1=$(sudo losetup --show -Pf "$RAW_ORIG") #echo "LOOP1: $LOOP1" #LOOP2=$(sudo losetup --show -Pf "$RAW_NEW") #echo "LOOP2: $LOOP2"
# 打印 parted, blkid, sgdisk 信息 echo "🔎 Partition table:" parted "$LOOP1" print parted "$LOOP2" print
echo "🔎 lsblk info:" blkid ${LOOP1}p1 blkid ${LOOP1}p15 blkid ${LOOP2}p1 blkid ${LOOP2}p15
echo "🔎 GPT info (sgdisk):" sgdisk -i=1 $LOOP1 sgdisk -i=15 $LOOP1 sgdisk -i=1 $LOOP2 sgdisk -i=15 $LOOP2
# === 挂载 old/new 镜像分区 === echo "📦 Mounting partitions..."
echo "📁 Copying rootfs..." sudo mkdir -p /mnt/cloud-root /mnt/new-root sudo mount "${LOOP1}p1" /mnt/cloud-root sudo mount "${LOOP2}p1" /mnt/new-root sudo cp -a /mnt/cloud-root/* /mnt/new-root/
rm -rf /mnt/new-root/usr/src/* rm -rf /mnt/new-root/usr/include/* rm -rf /mnt/new-root/usr/games rm -rf /mnt/new-root/usr/share/man/* rm -rf /mnt/new-root/usr/share/doc/* rm -rf /mnt/new-root/usr/share/info/*
echo "📁 Copying EFI partition..." sudo mkdir -p /mnt/cloud-efi /mnt/new-root/boot/efi sudo mount "${LOOP1}p15" /mnt/cloud-efi sudo mount "${LOOP2}p15" /mnt/new-root/boot/efi sudo cp -a /mnt/cloud-efi/* /mnt/new-root/boot/efi/
# === 卸载所有挂载点与 loop === echo "🧹 Cleaning up..." sudo umount /mnt/new-root/boot/efi /mnt/cloud-efi sudo umount /mnt/cloud-root /mnt/new-root sudo losetup -d "$LOOP1" sudo losetup -d "$LOOP2"
# === 压缩为 tar.xz 镜像 === echo "📦 Compressing final image..." tar -cf - "$RAW_NEW" | xz -3 -T1 -v > "$RAW_XZ"
echo "✅ Done: $RAW_XZ is ready for dd!"
|
进系统之后的后续清理,谨慎
#!/bin/bash set -eux
apt-get purge -y perl tcl expect cloud-init snapd unattended-upgrades ubuntu-release-upgrader-core popularity-contest apt-get autoremove --purge -y apt-get clean
rm -rf /usr/share/{doc,man,info,locale} rm -rf /usr/include /usr/src /usr/lib/x86_64-linux-gnu/perl rm -f /usr/lib/x86_64-linux-gnu/libicu*.so* rm -f /usr/lib/x86_64-linux-gnu/libbfd-* /usr/lib/x86_64-linux-gnu/libopcodes-* rm -rf /usr/lib/x86_64-linux-gnu/ldscripts rm -f /usr/lib/x86_64-linux-gnu/libtcl*.so*
rm -rf /var/lib/apt/lists/* rm -rf /var/cache/* find /var/log -type f -exec truncate -s 0 {} \;
|