breakertt's Blog

一起实现梦想的故事!

Scaleway IPv6 Only VPS 安装 Ubuntu 22.04 Minimal

breakertt's Avatar 2025-06-08 Linux

  1. 1. 🔗 前置阅读和本文动机
  2. 2. 📌 前置准备
    1. 2.1. 💾 分配本地磁盘(3G)
    2. 2.2. 🔑 强烈建议:预先添加 SSH 公钥
    3. 2.3. 重启进入 Rescue 模式
  3. 3. 🛠 制作并上传 DD 镜像
  4. 4. 🧱 DD 写入磁盘镜像
  5. 5. 🧹 修复分区表 & 扩容根分区
    1. 5.1. 1. 检查磁盘和分区
    2. 5.2. 2. 修复分区表
    3. 5.3. 3. 扩容分区到 3G
  6. 6. 🔧 注入 cloud-init 配置(Rescue 模式下操作)
    1. 6.1. 注入配置
    2. 6.2. 退出救援并重启
  7. 7. 🏁 大功告成
  8. 8. One more thing

本教程适用于 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 公钥。

  1. 打开 Scaleway 控制台的添加公钥页面

  2. 点击右上角 “Add SSH key”

  3. 粘贴你的公钥内容并保存

  4. 保存后,该公钥会自动注入 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

命令简要说明

  1. 格式化并挂载磁盘 /dev/vda,作为临时工作目录;
  2. 安装工具 qemu-utils,用于镜像格式转换;
  3. 下载官方 minimal 镜像(QCOW2 格式)并转换成 raw;
  4. 使用 tar + xz 压缩打包,生成 .raw.tar.xz 文件;
  5. 移动压缩文件到 /tmp/,方便后续传输或写入;
  6. 最后用 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. 检查磁盘和分区

partprobe # 重新读取分区表
lsblk

如果存在 /dev/vda1 则说明 dd 成功

2. 修复分区表

parted -l

此时会先提示 /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 公钥!

# 挂载 `/dev/vda1` 系统分区
mkdir /mnt/vda1
mount /dev/vda1 /mnt/vda1

# 创建 seed 目录并写入 cloud-init 配置文件
SEED_DIR="/mnt/vda1/var/lib/cloud/seed/nocloud"

mkdir -p "$SEED_DIR"

# 写入 meta-data
cat > "$SEED_DIR/meta-data" <<EOF
local-hostname: scaleway-vps
EOF

# 写入 user-data
cat > "$SEED_DIR/user-data" <<EOF
#cloud-config
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 包含了下面这些功能

  1. 配置 root 登陆:只允许 root 通过你的 ssh 私钥登陆
  2. 禁用 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 {} \;
本文最后更新于 天前,文中所描述的信息可能已发生改变