about summary refs log tree commit diff homepage
path: root/blog/butter.md
blob: 1009273d54699dd23a4661a3b1b3bf38e7e0c008 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
+++
rss = "How I reinstalled NixOS on Btrfs with an amnesiac root
       and backed up my data"
date = Date(2021, 11, 14)
tags = ["backup", "btrfs", "fun", "nixos", "recipe"]
+++

# NixOS on Btrfs+tmpfs

In 2018, dad bought me a new laptop to replace the good ole Compaq nx7010
whose screen unfortunately got infected by some sort of microbe and dieded
shortly afterwards.  The new one, whilst having a considerably worse build
quality (like all other late-2010s ones when compared to mid-2000s models),
had a dozen times as much storage: a 250 GB M.2 SSD and a 500 GB SATA HDD.

My data hoarding habit has grown exponentially ever since.  Initially,
I used to back up the data from the SSD to the HDD but after a few years,
I ran out of space and decided to get some more storage.  Instead of buying
a portable hard disk like a normal person would, I went for an SATA SSD,
as it was rather difficult to find a 7200 rpm 2.5-inch[^metric] HDD
in the market at the time.

I then asked my father for a spare SATA-to-USB case (he switched to using
a dock a while ago, and like other dads, nothing is ever thrown away)
and prepared to swap the drives.  As cloning the data would have been too easy,
I decided to *spice things up* by reinstalling the OS.  Back then I was
dual-booting Debian and NixOS, but the former had hardly been ever booted
for months so it was time to let it go:

![Elsa rolling on the floor crying](/assets/let-it-go.png)

In addition, I wanted to hop on the new and shinny[^new] train of Btrfs.
It has compression, snapshots and subvolumes, what's not to love?  Let's
replace something I'd been using for nearly a decade with a file system
I had absolutely zero experience with, what could possibly go wrong, right?

\toc

## Reinstallation

I was going to reinstall NixOS with an ephemeral root,
which had been covered to death in the following brilliant resources:

* [Erase your darlings: immutable infrastructure for mutable systems][grahamc]
* [NixOS ❄: tmpfs as root][elis]
* [Nix community's impermanence modules][impermanence]
* [Paranoid NixOS Setup][christine]

The only twist here is that I was using Btrfs instead of ZFS or ext4 like in
other guides.  This choice would influence how to back up in the later section.

### Preparation

First of all, I temporarily copied data to the SATA SSD from the M.2,
including [my Nix configurations].  Using either `cp` or `rsync` didn't
seem to have any effect on the performance, and in the mean time I also
went ahead and grabbed a [NixOS unstable live image] and `dd`'ed it to
a flash drive.  As I'm tracking unstable, installing from the same version
would allowed me to skip switching the channel and a lot of downloading.

### Partitioning

After booting up the live image, I opened up a root shell with `sudo -i`.
As expected, `fdisk` reports the M.2 SSD as `/dev/nvme0n1`.  Paranoid as always,
I decided to give the EFI system partition a whole gibibyte, swap eight
to match memory[^memory] and the rest as a single chonky Btrfs partition:

```sh
parted /dev/nvme0n1 -- mklabel gpt
parted /dev/nvme0n1 -- mkpart ESP fat32 1MiB 1GiB
parted /dev/nvme0n1 -- set 1 boot on
mkfs.vfat /dev/nvme0n1p1

parted /dev/nvme0n1 -- mkpart Swap linux-swap 1GiB 9GiB
mkswap -L Swap /dev/nvme0n1p2
swapon /dev/nvme0n1p2

parted /dev/nvme0n1 -- mkpart primary 9GiB 100%
mkfs.btrfs -L Butter /dev/nvme0n1p3
```

As I typed this, I realized that I should have set up encryption
for the last partition so I would probably need to reinstall in the near future
to fix this mistake.  Anyway, with the target system's root mounted as tmpfs,
I would need to persist `/nix` (obviously), `/etc` (mostly for authentication
and other secret stuff not included in `configuration.nix` that I was too lazy
to opt in individually), `/var/log`, `/root` and `/home`:

```sh
mount /dev/nvme0n1p3 /mnt
btrfs subvolume create /mnt/nix
btrfs subvolume create /mnt/etc
btrfs subvolume create /mnt/log
btrfs subvolume create /mnt/root
btrfs subvolume create /mnt/home
umount /mnt
```

Most subvolumes can be mounted with `noatime`, except for `/home`
where I frequently need to sort files by modification time.  All of them
should have forced compression though:

```sh
mount -t tmpfs -o mode=755 none /mnt
mkdir -p /mnt/{boot,nix,etc,var/log,root,home}
mount /dev/nvme0n1p1 /mnt/boot
mount -o subvol=nix,compress-force=zstd,noatime /dev/nvme0n1p3 /mnt/nix
mount -o subvol=etc,compress-force=zstd,noatime /dev/nvme0n1p3 /mnt/etc
mount -o subvol=log,compress-force=zstd,noatime /dev/nvme0n1p3 /mnt/var/log
mount -o subvol=root,compress-force=zstd,noatime /dev/nvme0n1p3 /mnt/root
mount -o subvol=home,compress-force=zstd /dev/nvme0n1p3 /mnt/home
```

### Configuration

With everything mounted, `nixos-generate-config --root /mnt` could be run
to generate a basic configuration.  But wait, didn't I say something
about my dot files?  That's correct, but it's not easy to handcraft
the `hardware-configuration.nix`.  After making sure all are mounted
with the right options and `services.fstrim.enable` is `true`, I copied
other configuration files to `/etc/nixos` and finished this step.

### Installation

NixOS installation is as simple as running `nixos-install`.  But my job was
not done after setting the root password and rebooting into the new system.
It was working, but not functional.  There was nothing meaningful for me
to do on it, so I had to log in (as root), `passwd`'ed the user and copied
the home folder back from the temporary drive.

After freeing the new SATA SSD, I also filled it with butter.  Yes,
all the way, no GPT, no MBR, just Btrfs, whose subvolumes were used
in place of partitions:

```sh
mkfs.btrfs -f -L Fly /dev/sdb
mkdir -p /mnt
mount /dev/sdb /mnt
btrfs subvolume create /mnt/movies
```

At that time the only disposable data I had were my movies collection.
The HDD also contained other data but they were rebalanced at `/home`
(on the M.2).  After swapping the SATA SSD inside the laptop, I logged in
as the normal user and get the exact same environment before the reinstallation.

### Profits

Thanks to subvolumes and compression, the free spaces were no longer fragmented
and I think I gained like 100 GB (not counting the old Debian's root).  Backup
would also be less painful with Btrfs snapshots (instead of plain `rsync`
like I used to) as shown as follows.

## Backup

With all data migrated, the HDD could be used for backing up.  First,
some legacy data I no longer access were moved there, then I started to back up
my `/home` partition:

### Initialization

Having learned my lesson, I did not forget to set up [LUKS] this time:

```sh
cryptsetup luksFormat /dev/sdb
cryptsetup luksOpen /dev/sdb backup
```

To make use of snapshots, the backup drive gotta be Btrfs as well.
The compression level was turned up to 14 this time (default was 3):

```sh
mkfs.btrfs -L Backup /dev/mapper/backup
mkdir /backup
mount -o noatime,compress-force=zstd:14 /dev/mapper/backup /backup
```

Following [Btrfs Wiki], I made the first `/home` snapshot and sent it
to the backup drive:

```sh
btrfs subvolume create /backup/home
today=$(date --iso-8601)
btrfs subvolume snapshot -r /home /home/$today
sync
btrfs send /home/$today | btrfs receive /backup/home
sync
```

### Repetition

For next backups, I also mounted the drive and created a snapshot:

```sh
cryptsetup luksOpen /dev/sdb backup
mkdir -p /backup
mount -o noatime,compress-force=zstd:14 /dev/mapper/backup /backup
today=$(date --iso-8601)
btrfs subvolume snapshot -r /home /home/$today
sync
```

Say the latest snapshot was on the `$previous` day, I only needed to send
the difference between the old and new backup.  Afterwards, it is safe to delete the local `$previous` snapshot to save some space.

```sh
btrfs send -p /home/$previous /home/$today | btrfs receive /backup/home
btrfs subvolume delete /home/$previous
sync
```

Finally, unmount the drive and close the LUKS volume:

```sh
umount /backup
cryptsetup luksClose backup
```

Is this more complicated than good ole `rsync`?  Yes.  Is it safer?  Also yes,
thanks to copy-on-write.  Would I bother using one of the tools suggested
in the wiki?  Probably not, I've already documented everything in this article
in case I forget anything.

[^metric]: 63.5 mm for those outside of the land of guns and burgers
[^new]: OK, maybe not new, but certainly shinny
[^memory]: Slightly larger since some of the memory is dedicated to graphics

[grahamc]: https://grahamc.com/blog/erase-your-darlings
[elis]: https://elis.nu/blog/2020/05/nixos-tmpfs-as-root
[impermanence]: https://github.com/nix-community/impermanence
[christine]: https://christine.website/blog/paranoid-nixos-2021-07-18
[my Nix configurations]: https://git.sr.ht/~cnx/dotfiles/tree/master/item/nix
[NixOS unstable live image]: https://channels.nixos.org/nixos-unstable
[LUKS]: https://gitlab.com/cryptsetup/cryptsetup
[Btrfs Wiki]: https://btrfs.wiki.kernel.org/index.php/Incremental_Backup