diff --git a/pmb/helpers/frontend.py b/pmb/helpers/frontend.py index 5bbf863c..044115be 100644 --- a/pmb/helpers/frontend.py +++ b/pmb/helpers/frontend.py @@ -241,6 +241,10 @@ def install(args): if args.rsync and not args.disk: raise ValueError("Installation using rsync only works with --disk.") + if args.rsync and args.filesystem == "btrfs": + raise ValueError("Installation using rsync" + " is not currently supported on btrfs filesystem.") + # On-device installer checks # Note that this can't be in the mutually exclusive group that has most of # the conflicting options, because then it would not work with --disk. diff --git a/pmb/install/_install.py b/pmb/install/_install.py index 05524308..a50722a9 100644 --- a/pmb/install/_install.py +++ b/pmb/install/_install.py @@ -153,10 +153,8 @@ def create_home_from_skel(args): Create /home/{user} from /etc/skel """ rootfs = args.work + "/chroot_native/mnt/install" - if args.filesystem == "btrfs": - pmb.helpers.run.root(args, - ["btrfs", "subvol", "create", rootfs + "/home"]) - else: + # In btrfs, home subvol & home dir is created in format.py + if args.filesystem != "btrfs": pmb.helpers.run.root(args, ["mkdir", rootfs + "/home"]) homedir = rootfs + "/home/" + args.user if os.path.exists(f"{rootfs}/etc/skel"): @@ -779,9 +777,12 @@ def create_fstab(args, layout, suffix): # btrfs gets separate subvolumes for root, var and home fstab = f""" # -{root_mount_point} / {root_filesystem} subvol=root,compress=zstd:2,ssd 0 0 -{root_mount_point} /home {root_filesystem} subvol=home,compress=zstd:2,ssd 0 0 -{root_mount_point} /var {root_filesystem} subvol=var,compress=zstd:2,ssd 0 0 +{root_mount_point} / btrfs subvol=@,compress=zstd:2,ssd 0 0 +{root_mount_point} /home btrfs subvol=@home,compress=zstd:2,ssd 0 0 +{root_mount_point} /root btrfs subvol=@root,compress=zstd:2,ssd 0 0 +{root_mount_point} /srv btrfs subvol=@srv,compress=zstd:2,ssd 0 0 +{root_mount_point} /var btrfs subvol=@var,ssd 0 0 +{root_mount_point} /.snapshots btrfs subvol=@snapshots,compress=zstd:2,ssd 0 0 {boot_mount_point} /boot {boot_filesystem} defaults 0 0 """.lstrip() diff --git a/pmb/install/format.py b/pmb/install/format.py index b1f7b485..6419d073 100644 --- a/pmb/install/format.py +++ b/pmb/install/format.py @@ -86,6 +86,71 @@ def get_root_filesystem(args): return ret +def prepare_btrfs_subvolumes(args, device, mountpoint): + """ + Create separate subvolumes if root filesystem is btrfs. + This lets us do snapshots and rollbacks of relevant parts + of the filesystem. + /var contains logs, VMs, containers, flatpaks; and shouldn't roll back, + /root is root's home directory and shouldn't roll back, + /tmp has temporary files, snapshotting them is unnecessary, + /srv contains data for web and FTP servers, and shouldn't roll back, + /snapshots should be a separate subvol so that changing the root subvol + doesn't affect snapshots + """ + pmb.chroot.root(args, + ["btrfs", "subvol", "create", + f"{mountpoint}/@", + f"{mountpoint}/@home", + f"{mountpoint}/@root", + f"{mountpoint}/@snapshots", + f"{mountpoint}/@srv", + f"{mountpoint}/@tmp", + f"{mountpoint}/@var"]) + + # Set the default root subvolume to be separate from top level btrfs + # subvol. This lets us easily swap out current root subvol with an + # earlier snapshot. + pmb.chroot.root(args, + ["btrfs", "subvol", "set-default", f"{mountpoint}/@"]) + + # Make directories to mount subvols onto + pmb.chroot.root(args, ["umount", mountpoint]) + pmb.chroot.root(args, ["mount", device, mountpoint]) + pmb.chroot.root(args, ["mkdir", + f"{mountpoint}/home", + f"{mountpoint}/root", + f"{mountpoint}/.snapshots", + f"{mountpoint}/srv", + f"{mountpoint}/var"]) + + # snapshots contain sensitive information, + # and should only be readable by root. + pmb.chroot.root(args, ["chmod", "700", f"{mountpoint}/root"]) + pmb.chroot.root(args, ["chmod", "700", f"{mountpoint}/.snapshots"]) + + # Mount subvols + pmb.chroot.root(args, + ["mount", "-o", "subvol=@var", + device, f"{mountpoint}/var"]) + pmb.chroot.root(args, + ["mount", "-o", "subvol=@home", + device, f"{mountpoint}/home"]) + pmb.chroot.root(args, + ["mount", "-o", "subvol=@root", + device, f"{mountpoint}/root"]) + pmb.chroot.root(args, + ["mount", "-o", "subvol=@srv", + device, f"{mountpoint}/srv"]) + pmb.chroot.root(args, + ["mount", "-o", "subvol=@snapshots", + device, f"{mountpoint}/.snapshots"]) + + # Disable CoW for /var, to avoid write multiplication + # and slowdown on databases, containers and VM images. + pmb.chroot.root(args, ["chattr", "+C", f"{mountpoint}/var"]) + + def format_and_mount_root(args, device, root_label, disk): """ :param device: root partition on install block device (e.g. /dev/installp2) @@ -124,12 +189,9 @@ def format_and_mount_root(args, device, root_label, disk): pmb.chroot.root(args, ["mkdir", "-p", mountpoint]) pmb.chroot.root(args, ["mount", device, mountpoint]) - # Create separate subvolumes if root filesystem is btrfs if filesystem == "btrfs": - pmb.chroot.root(args, - ["btrfs", "subvol", "create", mountpoint + "/root"]) - pmb.chroot.root(args, - ["btrfs", "subvol", "create", mountpoint + "/var"]) + # Make flat btrfs subvolume layout + prepare_btrfs_subvolumes(args, device, mountpoint) def format(args, layout, boot_label, root_label, disk):