Copy a drive on a running machine
#################################
:date: 2024-06-08
:summary: Copying from an old drive to a new drive while the machine is running

I was close to running out of disk space on the homelab machine I use for
running services with personal data that I run in my home (I feel more
comfortable having physical control). I was too lazy to connect the machine to
a monitor and keyboard and reboot from a thumbdrive so I tried to copy the old
drive to a new drive while the machine was running. Here's what I did:

#. Stop running processes that have open files. For me it was stopping Docker
   containers, some services (I have most things running in containers so most
   of the services are ones that come with a standard OS installation).

   .. code:: shell

        docker container ls | awk 'NR>1 {print $1}' | xargs docker stop
        sudo systemctl stop fwupd.service systemd-timesyncd.service docker.service docker.socket containerd.service cron.service systemd-journald.service systemd-journald.socket systemd-journald-dev-log.socket


#. :code:`cd` to a directory on a different drive to avoid keeping the path
   open (:code:`cd /tmp`). Remount all partitions as read-only: :code:`sudo mount
   --all -o remount,ro -t vfat,btrfs,ext4`. This may fail and you will see an
   error with a path that couldn't be remounted due to open files. Run
   :code:`lsof /path/that/has/open/files`, find the processes that still have
   open files and either stop that service or kill the process (stopping the
   service is better as it won't get restarted). Repeat until the partitions
   are mounted read-only. Verify with the :code:`mount` command. Run
   :code:`sudo sync` and now you should be able to copy the drive as there
   won't writes to it while copying and the copy will be consistent.

#. Start a session on the host using :code:`tmux` and copy
   :code:`dd if=/dev/sda of=/dev/nvme0n1`. Now we wait.

#. Once the copy is complete, run :code:`sudo sync` again and :code:`echo 1 |
   sudo tee /sys/block/nvme0n1/device/rescan`. Now we need to resize the data
   partition, :code:`sudo parted /dev/nvme0n1 --script resizepart 3 100%` (in
   my case the data partition was the 3rd partition after the EFI and root
   partitions). Open the encrypted partition :code:`sudo cryptsetup open
   /dev/nvme0n1p3 _dev_nvme0n1p2` and resize the encrypted partition
   :code:`sudo cryptsetup resize _dev_nvme0n1p3`. Last resize is the filesystem
   in the encrypted partition, we mount it :code:`sudo mount
   /dev/mapper/_dev_nvme0n1p3 /mnt` and resize it :code:`sudo btrfs filesystem
   resize max /mnt`. Umount the data partition :code:`sudo umount /mnt`.

#. Let's reinstall the boot loader.

   .. code:: shell

        sudo mount /dev/nvme0n1p2 /mnt
        sudo mount /dev/nvme0n1p1 /mnt/boot/efi
        for dir in dev proc sys; do sudo mount --bind "/${dir}" "/mnt/${dir}"; done
        sudo chroot /mnt
        grub-install /dev/nvme0n1
        exit
        for dir in dev proc sys; do sudo umount "/mnt/${dir}"; done
        umount /mnt/boot/efi
        umount /mnt

#. Because we copied the existing partitions, their UUIDs will remain the same
   so there's no need to update mounts. Stop the machine, remove the old drive
   and boot. With a little luck the UEFI BIOS will not need configuration to
   use the new drive and the machine will boot happily.