From e59a35de86fddfcf7b25a3b6557ef1834fe1dbc2 Mon Sep 17 00:00:00 2001 From: Hadi Chokr Date: Sun, 5 Apr 2026 11:38:39 +0200 Subject: [PATCH] RootFSv3 --- btrfs-migrator/src/main.rs | 74 ++++++++++++++++++++--- mkosi.extra/live/usr/lib/calamares@subvol | 3 +- mkosi.extra/usr/lib/rootfs-transition | 10 ++- mkosi.finalize.d/40-core.sh.chroot | 14 +++++ mkosi.postinst.chroot | 7 +++ 5 files changed, 98 insertions(+), 10 deletions(-) diff --git a/btrfs-migrator/src/main.rs b/btrfs-migrator/src/main.rs index b8f3bdf..adce054 100644 --- a/btrfs-migrator/src/main.rs +++ b/btrfs-migrator/src/main.rs @@ -122,7 +122,7 @@ fn run(root: &Path) -> Result<(), Box> { println!( "Found {concerning_fstab_entries} concerning fstab entries. This suggests you have a more complicated fstab setup that we cannot auto-migrate. \ - If nothing critically important is managed by fstab you can let the auto-migration run. If you have entries that are required for the system to boot you should manually migrate to @system." +If nothing critically important is managed by fstab you can let the auto-migration run. If you have entries that are required for the system to boot you should manually migrate to @system." ); io::stdout().flush().unwrap(); @@ -170,9 +170,9 @@ fn run(root: &Path) -> Result<(), Box> { defer! { println!("Unmounting overlay for {}", dir); Command::new("umount") - .arg(&compose_dir) - .status() - .expect("Failed to unmount overlay for etc/var"); + .arg(&compose_dir) + .status() + .expect("Failed to unmount overlay for etc/var"); } println!( @@ -237,6 +237,55 @@ fn run(root: &Path) -> Result<(), Box> { return Ok(()); } +fn run_v3(root: &Path) -> Result<(), Box> { + let system_home = root.join("@system/home"); + let system_home_tmp = root.join("@system/home.v3tmp"); + + let _ = Command::new("plymouth") + .arg("display-message") + .arg("--text=Migrating to v3 rootfs. Can take a while.") + .status(); + + println!("Migrating @system/home from subvolume to regular directory with per-user subvolumes"); + + // Stage into a sibling directory so that if we crash mid-way, @system/home is still a subvolume + // and the next boot will retry the migration cleanly. + fs::create_dir(&system_home_tmp)?; + + for entry in fs::read_dir(&system_home)? { + let entry = entry?; + let src = entry.path(); + if !src.is_dir() { + continue; + } + let dst = system_home_tmp.join(entry.file_name()); + println!("Creating user subvolume at {dst:?}"); + CreateSubvolumeOptions::new() + .create(&dst) + .map_err(|e| format!("Failed to create subvolume {dst:?}: {e:?}"))?; + let cp_result = Command::new("cp") + .arg("--recursive") + .arg("--archive") + .arg("--reflink=auto") + .arg(format!("{}/.", src.display())) + .arg(format!("{}/.", dst.display())) + .status() + .expect("Failed to copy user home"); + if !cp_result.success() { + return Err(format!("Failed to copy {src:?} to {dst:?}").into()); + } + } + + DeleteSubvolumeOptions::new() + .delete(&system_home) + .map_err(|e| format!("Failed to delete @system/home subvolume: {e:?}"))?; + + println!("Renaming {system_home_tmp:?} to {system_home:?}"); + fs::rename(&system_home_tmp, &system_home)?; // fatal problem + + return Ok(()); +} + fn main() -> Result<(), Box> { let args: Vec = env::args().collect(); if args.len() < 2 { @@ -245,11 +294,22 @@ fn main() -> Result<(), Box> { return Err("Not enough arguments".into()); } - println!("Migrating to v2 rootfs. This will take a while."); - let root = Path::new(&args[1]); + let system_path = root.join("@system"); - match run(root) { + let (msg, result) = if system_path.exists() { + println!("Migrating to v3 rootfs."); + ( + "Migrating to v3 rootfs. This will take a while.", + run_v3(root), + ) + } else { + println!("Migrating to v2 rootfs. This will take a while."); + ("Migrating to v2 rootfs. This will take a while.", run(root)) + }; + let _ = msg; // used for context above + + match result { Ok(_) => { // Reactivate in case we deactivated it earlier let _ = Command::new("plymouth").arg("show-splash").status(); diff --git a/mkosi.extra/live/usr/lib/calamares@subvol b/mkosi.extra/live/usr/lib/calamares@subvol index 1c144e0..5a50e70 100755 --- a/mkosi.extra/live/usr/lib/calamares@subvol +++ b/mkosi.extra/live/usr/lib/calamares@subvol @@ -90,7 +90,8 @@ btrfs subvolume sync . || true btrfs quota enable --simple . btrfs subvolume create @system btrfs subvolume create @system/etc -mkdir @system/boot @system/proc @system/sys @system/dev @system/run @system/usr +btrfs subvolume delete @system/home || true +mkdir @system/boot @system/proc @system/sys @system/dev @system/run @system/usr @system/home cp /dev/gpt-auto-root kde-linux_$IMAGE_VERSION.erofs # Overmount calamares' mount with the subvol mount diff --git a/mkosi.extra/usr/lib/rootfs-transition b/mkosi.extra/usr/lib/rootfs-transition index 55dd3e0..3be071e 100755 --- a/mkosi.extra/usr/lib/rootfs-transition +++ b/mkosi.extra/usr/lib/rootfs-transition @@ -24,8 +24,14 @@ mount -o rw,subvol=/ /dev/gpt-auto-root /run/kde-linux-rootfs-transition # In a way we could think about moving the mounts into a generator TBH. Then we can do proper condition management on # the systemd side. Question is if they get correctly unmounted automatically. if [ -e /run/kde-linux-rootfs-transition/@system ]; then - cd / - umount --recursive --lazy /run/kde-linux-rootfs-transition + # v2->v3: @system/home is a btrfs subvolume but should be a regular directory. + if btrfs subvolume show /run/kde-linux-rootfs-transition/@system/home >/dev/null 2>&1; then + /usr/lib/btrfs-migrator /run/kde-linux-rootfs-transition + umount --recursive --lazy /run/kde-linux-rootfs-transition + else + cd / + umount --recursive --lazy /run/kde-linux-rootfs-transition + fi exit 0 fi diff --git a/mkosi.finalize.d/40-core.sh.chroot b/mkosi.finalize.d/40-core.sh.chroot index 87cc36f..46966f9 100755 --- a/mkosi.finalize.d/40-core.sh.chroot +++ b/mkosi.finalize.d/40-core.sh.chroot @@ -177,3 +177,17 @@ rm -rf \ rm -rf \ /buildroot \ /.cache \ + +# Custom Plasma Setup +git clone https://invent.kde.org/plasma/plasma-setup.git +cd plasma-setup +git checkout work/hadi/btrfs + +mkdir build && cd build +cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release +make +make install + +cd ../ +cd ../ +rm -rf plasma-setup diff --git a/mkosi.postinst.chroot b/mkosi.postinst.chroot index b9b4b29..df065b6 100755 --- a/mkosi.postinst.chroot +++ b/mkosi.postinst.chroot @@ -127,6 +127,13 @@ sed -i 's%^SHELL=/usr/bin/bash%SHELL=/usr/bin/zsh%' /etc/default/useradd # Put new users in sambashare group sed -i 's%^GROUP=users%GROUP=users\nGROUPS=sambashare%' /etc/default/useradd +# Set BTRFS_SUBVOLUME_HOME for new users +if grep -q '^BTRFS_SUBVOLUME_HOME=' /etc/default/useradd; then + sed -i 's%^BTRFS_SUBVOLUME_HOME=.*%BTRFS_SUBVOLUME_HOME=yes%' /etc/default/useradd +else + echo 'BTRFS_SUBVOLUME_HOME=yes' >> /etc/default/useradd +fi + # Our Plymouth Theme KEEP_THEME="breeze-bgrt"