add a helper for the btrfs migration

notably this allows us to recursively delete and snapshot things
This commit is contained in:
Harald Sitter
2025-05-12 14:36:55 +02:00
parent fc8c58c9e2
commit 5f1c5b62f6
9 changed files with 827 additions and 19 deletions

1
.gitignore vendored
View File

@@ -15,6 +15,7 @@
/upload-vacuum/upload-vacuum
/upload-vacuum/vendor/
/btrfs-cleanup/target/
/btrfs-migrator/target/
mkosi.local.conf

607
btrfs-migrator/Cargo.lock generated Normal file
View File

@@ -0,0 +1,607 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "bindgen"
version = "0.69.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"lazy_static",
"lazycell",
"log",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
"which",
]
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "btrfs-migrator"
version = "0.1.0"
dependencies = [
"dialoguer",
"fstab",
"libbtrfsutil",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "console"
version = "0.15.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"unicode-width",
"windows-sys",
]
[[package]]
name = "dialoguer"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
dependencies = [
"console",
"shell-words",
"tempfile",
"thiserror",
"zeroize",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "errno"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fstab"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c23024238716ec12ab45f4618673c4e175c16c43efbda9e1ed92189088897820"
dependencies = [
"log",
]
[[package]]
name = "getrandom"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi",
]
[[package]]
name = "glob"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "home"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
"windows-sys",
]
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libbtrfsutil"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee4f2ad4e073cbfb6cafa9087df2ec9c8d72229dbb30a8aa143895cb81855b81"
dependencies = [
"libbtrfsutil-sys",
"libc",
"uuid",
]
[[package]]
name = "libbtrfsutil-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46241d507aeb587e1f0b240d8e1b6dd045ee0ef5f56c413cf135ee1a8dac6db2"
dependencies = [
"bindgen",
"pkg-config",
]
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libloading"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c"
dependencies = [
"cfg-if",
"windows-targets 0.53.0",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys",
]
[[package]]
name = "rustix"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.9.4",
"windows-sys",
]
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix 1.0.7",
"windows-sys",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "uuid"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "which"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
dependencies = [
"either",
"home",
"once_cell",
"rustix 0.38.44",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b"
dependencies = [
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"

View File

@@ -0,0 +1,2 @@
SPDX-License-Identifier: CC0-1.0
SPDX-FileCopyrightText: none

16
btrfs-migrator/Cargo.toml Normal file
View File

@@ -0,0 +1,16 @@
# SPDX-License-Identifier: BSD-2-Clause
# SPDX-FileCopyrightText: None
[package]
name = "btrfs-migrator"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "_kde-linux-btrfs-migrator"
path = "src/main.rs"
[dependencies]
dialoguer = "0.11.0"
fstab = "0.4.0"
libbtrfsutil = "0.7.1"

175
btrfs-migrator/src/main.rs Normal file
View File

@@ -0,0 +1,175 @@
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
// SPDX-FileCopyrightText: 2025 Harald Sitter <sitter@kde.org>
use std::{env, error::Error, fs::{self}, io::{self, Write}, path::Path, process::Command, vec};
use libbtrfsutil::{CreateSnapshotOptions, CreateSubvolumeOptions, DeleteSubvolumeOptions};
use fstab::FsTab;
use dialoguer::{self, Confirm};
fn run(root: &Path, usr: &Path) -> Result<(), Box<dyn Error>> {
env::set_current_dir(root)?;
let system_path = root.join("@system");
if system_path.exists() {
println!("@system exists already. Skipping migration.");
return Ok(());
}
let import_path = root.join("@system.import");
if import_path.exists() {
println!("@system.import exists. Deleting it.");
match DeleteSubvolumeOptions::new().recursive(true).delete(&import_path) {
Ok(_) => println!("Deleted subvolume: {import_path:?}"),
Err(error) => println!("Problem deleting subvolume {import_path:?}: {error:?}")
}
}
CreateSubvolumeOptions::new()
.create(&import_path)
.map_err(|error| format!("Problem creating subvolume {import_path:?}: {error:?}"))?;
env::set_current_dir(&import_path)?;
println!("Current directory: {:?}", env::current_dir()?);
// May or may not exist. Don't trip over it!
let fstab = FsTab::new(&root.join("@etc-overlay/upper/fstab"));
let mut concerning_fstab_entries = 0;
for entry in fstab.get_entries().unwrap_or_default() {
if entry.vfs_type != "swap" {
concerning_fstab_entries += 1;
}
}
if concerning_fstab_entries > 0 {
let _ = Command::new("plymouth")
.arg("deactivate")
.status();
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.");
// TODO: add wiki link to migration instructions
io::stdout().flush().unwrap();
let migrate = Confirm::new()
.with_prompt("Do you want to continue with auto-migration?")
.interact()
.unwrap();
if !migrate {
Command::new("systemctl")
.arg("reboot")
.status()
.expect("failed to execute systemctl reboot");
return Err("Concerning fstab entries found".into());
}
}
let mut subvolumes_to_backup = vec![];
for dir in ["etc", "var"] {
let lower_path = usr.join("share/factory").join(dir);
println!("Copying {lower_path:?} to {}", import_path.join(dir).display());
let lower_result = Command::new("cp")
.arg("--recursive")
.arg("--archive")
.arg("--reflink=auto")
.arg(&lower_path)
.arg(dir)
.status()
.expect("failed to execute cp for lower dir");
if !lower_result.success() {
println!("Failed to copy lower dir {lower_path:?} to {dir:?}");
return Err("Failed to copy lower dir".into());
}
let dir_path = root.join(format!("@{dir}-overlay/upper"));
println!("Copying {dir_path:?} to {}", import_path.join(dir).display());
let upper_result = Command::new("cp")
.arg("--recursive")
.arg("--archive")
.arg("--reflink=auto")
.arg(&dir_path)
.arg(dir)
.status().expect("Failed to copy upper dir");
if !upper_result.success() {
println!("Failed to copy upper dir {dir_path:?} to {dir:?}");
return Err("Failed to copy upper dir".into());
}
subvolumes_to_backup.push(format!("@{dir}-overlay"));
}
let subvol_targets = [("@home", "home"), ("@root", "root"), ("@snap", "snap"), ("@containers", "var/lib/containers"), ("@docker", "var/lib/docker")];
for (subvol, target) in subvol_targets {
println!("Snapshotting {} to {}", root.join(subvol).display(), target);
let target_path = Path::new(target);
// Inside var the target_path may already exist if they predate the subvolumes. Originally contianers and docker were not subvolumes.
// Make sure to throw the data away before trying to snapshot, otherwise the snapshot will fail.
if target_path.exists() {
println!("Removing pre-existing directory {target_path:?}");
fs::remove_dir_all(target_path)?;
}
match target_path.parent() {
Some(dir) => {
if dir != Path::new("") && !dir.exists() { // bit crap but parent of a relative path is the empty path.
println!("create_dir {dir:?}");
fs::create_dir(dir)?;
}
},
None => {
println!("No parent directory for {target_path:?}");
},
}
CreateSnapshotOptions::new()
.recursive(true)
.create(root.join(subvol), Path::new(target))?;
subvolumes_to_backup.push(subvol.to_string());
}
println!("Renaming {import_path:?} to {system_path:?}");
fs::rename(import_path, system_path)?; // fatal problem
for subvol in subvolumes_to_backup {
let from = root.join(&subvol);
let to = root.join(subvol + ".backup");
println!("Archiving {from:?} to {to:?}");
let _ = fs::rename(from, to); // not a fatal problem
}
return Ok(());
}
fn main() -> Result<(), Box<dyn Error>> {
let args: Vec<String> = env::args().collect();
if args.len() < 3 {
println!("Usage: {} system_mount usr_mount", args[0]);
println!("Migrates a legacy subvol (pre-May-2025) to v2 rootfs");
return Err("Not enough arguments".into());
}
println!("Migrating to v2 rootfs. This will take a while.");
let root = Path::new(&args[1]);
let usr = Path::new(&args[2]);
match run(root, usr) {
Ok(_) => {
// Reactivate in case we deactivated it earlier
let _ = Command::new("plymouth").arg("reactivate").status();
Ok(())
}
Err(e) => {
// Quit plymouth if there was a fatal problem so the user can see the output
Command::new("plymouth")
.arg("quit")
.status()
.expect("failed to execute plymouth reactivate");
Err(e)
}
}
}

View File

@@ -74,6 +74,9 @@ echo "Server = https://archive.archlinux.org/repos/${BUILD_DATE}/\$repo/os/\$arc
# Make sure permissions are sound
./permission-fix.sh
cargo build --release --manifest-path btrfs-migrator/Cargo.toml
cp -v btrfs-migrator/target/release/_kde-linux-btrfs-migrator mkosi.extra/usr/bin/
mkosi \
--environment="CI_COMMIT_SHORT_SHA=${CI_COMMIT_SHORT_SHA:-unknownSHA}" \
--environment="CI_COMMIT_SHA=${CI_COMMIT_SHA:-unknownSHA}" \

View File

@@ -4,6 +4,8 @@
set -eux
. /etc/os-release
if [ -b /dev/gpt-auto-root ]; then
echo "gpt-auto-root exists"
else
@@ -19,6 +21,11 @@ fi
mkdir /run/kde-linux-rootfs-transition
mount -o rw,subvol=/ /dev/gpt-auto-root /run/kde-linux-rootfs-transition
# NOTE: this is required to be here!
# It's a bit awkward but the system drive is not mounted yet, so we can only peek into it through our mount.
# This is effectively the no-op condition that should be in the unit but can't be because we need the mount.
# 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
echo "@system exists"
cd /
@@ -26,24 +33,13 @@ if [ -e /run/kde-linux-rootfs-transition/@system ]; then
exit 0
fi
cd /run/kde-linux-rootfs-transition
btrfs --verbose subvolume create /run/kde-linux-rootfs-transition/@system
# We need access to our usr so we can import the /usr/share/factory/var.
# Previously this was an overlay so we don't really know which files were used from the lower dir.
mkdir /run/kde-linux-rootfs-transition-usr
mount -o ro,X-mount.subdir=usr "/run/kde-linux-rootfs-transition/kde-linux_$VERSION_ID.erofs" /run/kde-linux-rootfs-transition-usr
# overlays
cp --recursive --archive --reflink=always /run/kde-linux-rootfs-transition/@etc-overlay/upper @system/etc || true
cp --recursive --archive --reflink=always /run/kde-linux-rootfs-transition/@var-overlay/upper @system/var || true
# subvolumes. these are technically difficult because they may contain subvolumes. Not really a good way to deal with that
# outside writing a custom tool, so we just pretend they don't contain subvolumes yet.
cp --recursive --archive --reflink=always /run/kde-linux-rootfs-transition/@home @system/home || true
cp --recursive --archive --reflink=always /run/kde-linux-rootfs-transition/@root @system/root || true
cp --recursive --archive --reflink=always /run/kde-linux-rootfs-transition/@snap @system/snap || true
## We do not transition @containers nor @docker because of the subvolume complexity. With how few users we have they can
## just transition these manually if they need them.
# TODO turn all our subvolumes into readonly snapshots for backup purposes
# TODO inform user of the change somehow
_kde-linux-btrfs-migrator /run/kde-linux-rootfs-transition /run/kde-linux-rootfs-transition-usr
cd /
umount --recursive --lazy /run/kde-linux-rootfs-transition-usr
umount --recursive --lazy /run/kde-linux-rootfs-transition

View File

@@ -7,6 +7,7 @@ build() {
/usr/lib/systemd/systemd-volatile-root \
/usr/bin/_kde-linux-overlay \
/usr/bin/_kde-linux-rootfs-transition \
/usr/bin/_kde-linux-btrfs-migrator \
/usr/lib/systemd/system-generators/kde-linux-live-generator \
/usr/lib/systemd/system-generators/kde-linux-mount-generator \
/usr/lib/systemd/systemd-bootchart \

View File

@@ -32,15 +32,22 @@ if [ "$SYSTEMD_IN_INITRD" = "1" ]; then
# Generated by $(basename "$0")
[Unit]
Description=Root Partition Transition
DefaultDependencies=no
Before=initrd-root-fs.target
Requires=systemd-fsck-root.service
After=systemd-fsck-root.service
Before=sysroot.mount
After=blockdev@dev-gpt\x2dauto\x2droot.target
# Let vconsole-setup do its setup before starting user interaction:
After=systemd-vconsole-setup.service
[Service]
ExecStart=/usr/bin/_kde-linux-rootfs-transition
Type=oneshot
StandardOutput=tty
StandardInput=tty
StandardError=tty
RemainAfterExit=yes
EOF
mkdir "$late_dir/sysroot.mount.requires/" || true
@@ -76,7 +83,7 @@ After=imports.target
After=blockdev@dev-gpt\x2dauto\x2droot.target
[Mount]
What=/sysroot/system/kde-linux_${BUILD_ID}.erofs
What=/sysroot/system/kde-linux_${VERSION_ID}.erofs
Where=/sysusr/usr
Options=ro,X-mount.subdir=usr
EOF
@@ -111,7 +118,7 @@ Before=systemd-sysext.service
After=var.mount
[Mount]
What=/system/@kde-linux_debug_${BUILD_ID}
What=/system/@kde-linux_debug_${VERSION_ID}
Where=/var/lib/extensions/debug
Type=none
Options=bind,nodev,x-gdu.hide,x-gvfs-hide