From aba1761d815c3c675c319f3d3ca032ef2411e362 Mon Sep 17 00:00:00 2001 From: Harald Sitter Date: Mon, 12 May 2025 14:36:55 +0200 Subject: [PATCH] add a helper for the btrfs migration notably this allows us to recursively delete and snapshot things --- .gitignore | 1 + btrfs-migrator/Cargo.lock | 607 ++++++++++++++++++ btrfs-migrator/Cargo.lock.license | 2 + btrfs-migrator/Cargo.toml | 16 + btrfs-migrator/src/main.rs | 175 +++++ build.sh | 3 + .../usr/bin/_kde-linux-rootfs-transition | 30 +- .../lib/initcpio/install/systemd-extension | 1 + .../kde-linux-mount-generator | 11 +- 9 files changed, 827 insertions(+), 19 deletions(-) create mode 100644 btrfs-migrator/Cargo.lock create mode 100644 btrfs-migrator/Cargo.lock.license create mode 100644 btrfs-migrator/Cargo.toml create mode 100644 btrfs-migrator/src/main.rs diff --git a/.gitignore b/.gitignore index 9507f5f..cc0c495 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ /upload-vacuum/upload-vacuum /upload-vacuum/vendor/ /btrfs-cleanup/target/ +/btrfs-migrator/target/ mkosi.local.conf diff --git a/btrfs-migrator/Cargo.lock b/btrfs-migrator/Cargo.lock new file mode 100644 index 0000000..c96cc4b --- /dev/null +++ b/btrfs-migrator/Cargo.lock @@ -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" diff --git a/btrfs-migrator/Cargo.lock.license b/btrfs-migrator/Cargo.lock.license new file mode 100644 index 0000000..23c1849 --- /dev/null +++ b/btrfs-migrator/Cargo.lock.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: CC0-1.0 +SPDX-FileCopyrightText: none diff --git a/btrfs-migrator/Cargo.toml b/btrfs-migrator/Cargo.toml new file mode 100644 index 0000000..c8f8b87 --- /dev/null +++ b/btrfs-migrator/Cargo.toml @@ -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" diff --git a/btrfs-migrator/src/main.rs b/btrfs-migrator/src/main.rs new file mode 100644 index 0000000..511704b --- /dev/null +++ b/btrfs-migrator/src/main.rs @@ -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 + +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> { + 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> { + let args: Vec = 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) + } + } +} diff --git a/build.sh b/build.sh index 8912d02..65eb127 100755 --- a/build.sh +++ b/build.sh @@ -62,6 +62,9 @@ export SYSTEMD_LOG_LEVEL=debug # 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}" \ diff --git a/mkosi.extra/usr/bin/_kde-linux-rootfs-transition b/mkosi.extra/usr/bin/_kde-linux-rootfs-transition index 707536c..3ed4b62 100755 --- a/mkosi.extra/usr/bin/_kde-linux-rootfs-transition +++ b/mkosi.extra/usr/bin/_kde-linux-rootfs-transition @@ -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 diff --git a/mkosi.extra/usr/lib/initcpio/install/systemd-extension b/mkosi.extra/usr/lib/initcpio/install/systemd-extension index 1c757a4..470770a 100644 --- a/mkosi.extra/usr/lib/initcpio/install/systemd-extension +++ b/mkosi.extra/usr/lib/initcpio/install/systemd-extension @@ -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 \ diff --git a/mkosi.extra/usr/lib/systemd/system-generators/kde-linux-mount-generator b/mkosi.extra/usr/lib/systemd/system-generators/kde-linux-mount-generator index 3e2b1c5..cfdc60a 100755 --- a/mkosi.extra/usr/lib/systemd/system-generators/kde-linux-mount-generator +++ b/mkosi.extra/usr/lib/systemd/system-generators/kde-linux-mount-generator @@ -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