<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Johannes Hertenstein</title>
    <description>The latest articles on DEV Community by Johannes Hertenstein (@j6s).</description>
    <link>https://dev.to/j6s</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F168148%2Fd87e31d6-4c8b-4405-81f8-66f412b6f15f.png</url>
      <title>DEV Community: Johannes Hertenstein</title>
      <link>https://dev.to/j6s</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/j6s"/>
    <language>en</language>
    <item>
      <title>Installing Linux on ZFS</title>
      <dc:creator>Johannes Hertenstein</dc:creator>
      <pubDate>Sun, 05 Jun 2022 17:48:47 +0000</pubDate>
      <link>https://dev.to/j6s/installing-linux-on-zfs-1k1</link>
      <guid>https://dev.to/j6s/installing-linux-on-zfs-1k1</guid>
      <description>&lt;p&gt;ZFS is a fascinating filesystem that is packed with features. It is mainly geared towards data storage use cases such as&lt;br&gt;
NAS or Datacenter usage. One feature however intrigued me for workstation usage: The ability to not only take incremental&lt;br&gt;
snapshots, but also transfer those over the network to another ZFS filesystem. This feature could solve a lot of my current&lt;br&gt;
backup woes - so I set out on a journey to install Linux on ZFS.&lt;/p&gt;

&lt;p&gt;I quickly learned, that guides about this topic are very cargocult-y, repeating things guides before them have said without&lt;br&gt;
questioning &lt;strong&gt;why&lt;/strong&gt; those things were said. This leads to a situation where these guides are needlessly complicated and often&lt;br&gt;
fail to address the few important pieces of information. This is where this series of blog posts starts: My goal is to provide&lt;br&gt;
a &lt;strong&gt;simple&lt;/strong&gt; guide to running Linux on ZFS written largely from scratch. I will try to leave no stone unturned and nothing implied&lt;br&gt;
so readers can get a feel for which parts of the guide are important for a running system and which are down to my personal&lt;br&gt;
preferences.&lt;/p&gt;

&lt;p&gt;To start things off, let's install archlinux on ZFS. I will be using Arch Linux for these guides as it's installation process (and&lt;br&gt;
its lack of automation) lend itself very well towards this kind of systems exploration: If you have to do everything yourself&lt;br&gt;
you have no choice but to learn how things work.&lt;/p&gt;
&lt;h2&gt;
  
  
  0. Assumptions
&lt;/h2&gt;

&lt;p&gt;This guide makes a couple of assumptions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You area installing this on a UEFI based system. This should be true for all modern PCs but if you are trying this in a virtual machine, you may have to explicitly configure it that way &lt;sup id="fnref1"&gt;1&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;No dual-booting is required&lt;/li&gt;
&lt;li&gt;We are installing on a 64bit intel based system&lt;/li&gt;
&lt;li&gt;We are going to use grub&lt;/li&gt;
&lt;li&gt;We are not using ZFS encryption. In order to use encryption on ANY dataset in the root pool, the &lt;code&gt;/boot&lt;/code&gt; directory must be located on a different partition similar to LUKS setups. I will create a follow-up article explaining how Linux on encrypted ZFS works.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  1. Build a Live ISO that contains ZFS
&lt;/h2&gt;

&lt;p&gt;Like a lot of distributions, archlinux live environments do not ship with ZFS. This is mainly due to the licensing disagreement &lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;br&gt;
that comes up a lot when talking about ZFS in linux environments. For archlinux, one can use the &lt;code&gt;archzfs&lt;/code&gt; repository &lt;sup id="fnref3"&gt;3&lt;/sup&gt;. In order to&lt;br&gt;
have ZFS included in an archlinux live environment, one has to build their own archiso including the arch packages.&lt;/p&gt;

&lt;p&gt;I will skip over this fairly quickly, for more information check out the archwiki page on ZFS &lt;sup id="fnref4"&gt;4&lt;/sup&gt;, the one on archiso &lt;sup id="fnref5"&gt;5&lt;/sup&gt; and my&lt;br&gt;
repository containing the building blocks described here &lt;sup id="fnref6"&gt;6&lt;/sup&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;code&gt;archiso&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create a new archiso profile by copying &lt;code&gt;/usr/share/archiso/configs/releng/&lt;/code&gt; into a directory of your own (e.g. &lt;code&gt;./archlive&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Trust the pacman key and add the archzfs repository to the &lt;code&gt;pacman.conf&lt;/code&gt; file in your archiso directory&lt;/li&gt;
&lt;li&gt;Append &lt;code&gt;linux-headers&lt;/code&gt;, &lt;code&gt;zfs-dkms&lt;/code&gt;, &lt;code&gt;zfs-utils&lt;/code&gt; to the list of packages that will be installed in the live environment located in &lt;code&gt;packages.x86_64&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Optional: In order to save yourself the tedious job of manually typing the pacman key later on, create a file containing it in &lt;code&gt;airrootfs&lt;/code&gt;. If you are using my repository, then there will be &lt;code&gt;/zfs-key.sh&lt;/code&gt; with a script to add the key and &lt;code&gt;/zfs-pacman.conf&lt;/code&gt; with the pacman configuration.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  2. Booting the Live environment
&lt;/h2&gt;

&lt;p&gt;After booting into the live environment, we'll have to check a couple of things before we start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;loadkeys&lt;/code&gt; in order to load a different keymap if you don't use QWERTY (e.g. &lt;code&gt;loadkeys de&lt;/code&gt; or &lt;code&gt;loadkeys colemak&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;ping 1.1.1.1&lt;/code&gt; to check if we have internet connectivity. Networking should work out of the box for wired networks. Wireless networking can be setup using &lt;code&gt;iwctl&lt;/code&gt; &lt;sup id="fnref7"&gt;7&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;Ensure, that the &lt;code&gt;zfs&lt;/code&gt; and &lt;code&gt;zpool&lt;/code&gt; commands exist&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  3. Partitioning your drive
&lt;/h2&gt;

&lt;p&gt;At this point, we can begin preparing our drive. First, identify the device you want to use as your bootdrive by using &lt;code&gt;lsblk&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;lsblk
    NAME  MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
    loop0   7:0    0 784.6M  1 loop /run/archiso/airootfs
    sr0    11:0    1 943.3M  0 rom  /run/archiso/bootmnt
    sda   254:0    0    50G  0 disk 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, I am going to use &lt;code&gt;/dev/sda&lt;/code&gt; as my boot drive. If your system boots off of an NVMe drive, then this will likely&lt;br&gt;
be &lt;code&gt;/dev/nvme0n1&lt;/code&gt; or similar for you. We are going to use &lt;code&gt;cgdisk&lt;/code&gt; in order to partition the drive (WARNING: This will delete&lt;br&gt;
the data that is currently on that drive):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create one partition of 500M in size. This will be the EFI partition that your System uses for booting. Use the type &lt;code&gt;EF00&lt;/code&gt; to indicate the pact that this is a EFI partition&lt;/li&gt;
&lt;li&gt;Create a secondary partition that spans the rest of your drive. The type for this drive is not really important.  Popular choices are &lt;code&gt;bf00&lt;/code&gt; (Solaris root, Solaris being the 'original' ZFS supporting OS), &lt;code&gt;8300&lt;/code&gt; (Linux Filesystem) or &lt;code&gt;8304&lt;/code&gt; (Linux root) &lt;sup id="fnref8"&gt;8&lt;/sup&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                            cgdisk 1.0.9 

                        Disk Drive: /dev/vda
                      Size: 104857600, 50.0 GiB

 Part. #     Size        Partition Type            Partition Name
 ----------------------------------------------------------------
             1007.0 KiB  free space
    1        500.0 MiB   EFI system partition      EFI
    2        49.5 GiB    Linux filesystem          zroot
             1007.5 KiB  free space
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;Partition setup inside cgdisk &lt;sup id="fnref9"&gt;9&lt;/sup&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Write the partition and exit &lt;code&gt;cgdisk&lt;/code&gt; . Now, &lt;code&gt;lsblk&lt;/code&gt; should indicate 2 drives: &lt;code&gt;/dev/sda1&lt;/code&gt; (will be EFI) and &lt;code&gt;/dev/sda2&lt;/code&gt; (will be ZFS).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;lsblk         
    NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
    loop0    7:0    0 784.6M  1 loop /run/archiso/airootfs
    sr0     11:0    1 943.3M  0 rom  /run/archiso/bootmnt
    sda    254:0    0    50G  0 disk 
    ├─sda1 254:1    0   500M  0 part 
    └─sda2 254:2    0  49.5G  0 part 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a final step during the partitioning phase, we will use &lt;code&gt;mkfs.vfat /etc/sda1&lt;/code&gt; in order to initialize a FAT filesystem for the EFI drive.&lt;br&gt;
Unfortunately, FAT is all EFI supports &lt;sup id="fnref10"&gt;10&lt;/sup&gt; - this limitation applies to all filesystems however. Even for default ext4 Linux systems, a FAT system&lt;br&gt;
is always involved in booting. This fact doesn't matter however, because the EFI partition only contains reltaively volatile data: It can be&lt;br&gt;
recreated from the data in the system at any point in time (In case of data loss &amp;amp; reinstallation, be sure to recreate the EFI data using the &lt;code&gt;grub-install&lt;/code&gt; command). &lt;/p&gt;
&lt;h2&gt;
  
  
  4. Setup ZFS
&lt;/h2&gt;

&lt;p&gt;After all of this preparation, it is finally time to start with the interesting part: Actually setting up the ZFS zpool and datasets. In this part I will assume&lt;br&gt;
that you are at least faintly familiar with the concepts behind ZFS, but will do my best to describe them as we go. To skip ahead a bit, the following is&lt;br&gt;
the dataset setup we are setting up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;zfs list
    NAME                USED  AVAIL     REFER  MOUNTPOINT
    zroot              1.01M  48.0G       96K  none
    zroot/DATA          288K  48.0G       96K  none
    zroot/DATA/docker    96K  48.0G       96K  /var/lib/docker
    zroot/DATA/home      96K  48.0G       96K  /home
    zroot/ROOT           96K  48.0G       96K  /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under our root dataset, there are 2 'logical' datasets which are not mounted but used to logically separate the data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ROOT&lt;/code&gt; will contain our system root&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DATA&lt;/code&gt; will contain datasets for userdata (e.g. home directory, docker files, ...)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This distinction is done because snapshots are created on a per-dataset basis. By separating system data and user data well, we will be able to roll back&lt;br&gt;
a failed system upgrade in the future without compromising our user data. For alternative dataset configurations, check "4.2 Aside: Alternative dataset&lt;br&gt;
configurations"&lt;/p&gt;

&lt;p&gt;We are going to start out creating a zpool, which describes an array of one or more physical disks that are handled as a single unit by ZFS. When using&lt;br&gt;
multiple disks, ZFS can arrange them in multiple RAID configurations, but for this simple guide we are going to assume that a single drive is being used.&lt;br&gt;
A zpool then contains datasets: You can think of these as a cross between directories and partitions. Creating a zpool will always also create a dataset&lt;br&gt;
with the same name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;zpool create &lt;span class="se"&gt;\&lt;/span&gt;
    zroot &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;ashift&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-O&lt;/span&gt; &lt;span class="nv"&gt;acltype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;posixacl &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-O&lt;/span&gt; &lt;span class="nv"&gt;relatime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-O&lt;/span&gt; &lt;span class="nv"&gt;xattr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sa &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-O&lt;/span&gt; &lt;span class="nv"&gt;mountpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;none &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-O&lt;/span&gt; &lt;span class="nv"&gt;canmount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;off &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-R&lt;/span&gt; /mnt &lt;span class="se"&gt;\&lt;/span&gt;
    /dev/sda2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that's a lot of options. Options set with &lt;code&gt;-o&lt;/code&gt; are options we are setting on the zpool while options set with &lt;code&gt;-O&lt;/code&gt; are those we are setting on the dataset.&lt;br&gt;
Let's go through these options 1 by 1 and explain what each one does. I will also include information if the option is strictly necessary (as in, this option is&lt;br&gt;
required for booting) or recommended (as in, you should probably set the option for optimal performance and compatibility)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;zroot&lt;/code&gt; is the name of the zpool we are creating. You are free to call it whatever you want, but it seems that for this use case &lt;code&gt;zroot&lt;/code&gt; has been agreed upon as a convention.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-o ashift=12&lt;/code&gt; sets the pools blocksizes to 4K (2 ^ 12 bytes). This should match the sectorsize of modern hard drives and can be only set once when creating the pool. This value is sometimes incorrectly detected as 9, making the pools performance less than suboptimal. &lt;sup id="fnref11"&gt;11&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-O acltype=posixacl&lt;/code&gt; instructs ZFS to use POSIX compatible ACLs (Access Control Lists). This option is not strictly required on the root partition for the whole system - but at least &lt;code&gt;/var/log/journal&lt;/code&gt; needs it to be set. &lt;sup id="fnref12"&gt;12&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-O xattr=sa&lt;/code&gt; Sets the storage mechanism for extended attributes. Some applications and use cases (including the ACLs mentioned above) add additional metadata to files and ZFS has multiple mechanisms of storing it. &lt;code&gt;xattr=sa&lt;/code&gt; will instruct ZFS to save the metadata on the files inode itself. This means that reading metadata does not cause another read operation, making reading and writing metadata more performant &lt;sup id="fnref13"&gt;13&lt;/sup&gt;. This comes at the cost of compatibility, however: At the time of writing, only ZFS on Linux and supposedly OpenZFS on macOS support this xattr storage (Although I haven't tested the latter. Check the openzfs wiki &lt;sup id="fnref14"&gt;14&lt;/sup&gt; for more information). Your ZFS dataset will still be mountable and accessible on FreeBSD, but extended attributes will be lost. Since this dataset is meant to be used exclusively with Linux, the lack of compatibility is ok.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-O relatime=on&lt;/code&gt; by default, most filesystems will save file access timestamps for files. This however means, that file metadata has to be refreshed every single time a file is accessed, making a read equal to a read and a write. This behaviour can be disabled by &lt;code&gt;atime=off&lt;/code&gt; in order to disable tracking of accesstime completely. &lt;code&gt;relatime&lt;/code&gt; is a compromise between atime tracking and no atime tracking, saving the timestamp only when the file is updated or if the last access is more than 24h in the past, making it not write for every read operation&lt;sup id="fnref15"&gt;15&lt;/sup&gt;. It is said, that for maximum compatibility, &lt;code&gt;relatime&lt;/code&gt; should be used on systems - I for my part have run ext4 based systems &lt;code&gt;noatime&lt;/code&gt; (equivalent of ZFS &lt;code&gt;atime=off&lt;/code&gt;) for more than 10 years at this point and haven't had an issue so far. So if you want to be safe, use &lt;code&gt;relatime=on&lt;/code&gt; - if you want things to be more efficient, use &lt;code&gt;atime=off&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-O mountpoint=none&lt;/code&gt; &amp;amp; &lt;code&gt;-O canmount=off&lt;/code&gt; Tells ZFS that this dataset is not mountable. It will only exist as a way to structure the rest of our datasets.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-R /mnt&lt;/code&gt; Is not an option for the zpool itself, but instructs ZFS to mount our datasets relative to &lt;code&gt;/mnt&lt;/code&gt;. This means our root dataset with &lt;code&gt;mountpoint=/&lt;/code&gt; will be mounted at &lt;code&gt;/mnt&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/dev/sda2&lt;/code&gt; Is the identifier of the blockdevice we want to use for our zpool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point we can start adding datasets to our pool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;zfs create &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;mountpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/ &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;canmount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noauto zroot/ROOT
root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;zfs create zroot/DATA
root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;zfs create &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;mountpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home zroot/home
root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;zfs create &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;mountpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/docker zroot/DATA/docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of additional notes about the datasets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;-o mountpoint=...&lt;/code&gt; to set the mountpoint of the dataset if the parent does not have a mountpoint&lt;/li&gt;
&lt;li&gt;The dataset for the system-root (&lt;code&gt;zroot/ROOT&lt;/code&gt;) has to have &lt;code&gt;canmount=noauto&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;By default, when importing a zpool, ZFS will automatically mount all datasets on it. &lt;code&gt;canmount=noauto&lt;/code&gt; tells ZFS that while this dataset is mountable, it should not be mounted automatically. Then booting, initramfs automatically mounts the root dataset, so ZFS will not have to mount it later on &lt;sup id="fnref16"&gt;16&lt;/sup&gt;. In live environments this means that we'll have to mount it manually using &lt;code&gt;zfs mount {DATASET_NAME}&lt;/code&gt;. Setting &lt;code&gt;canmount=noauto&lt;/code&gt; is required for the root dataset.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To double check, that a) the pool is setup correctly and b) all datasets are mounted correctly we are going to export and reimport the pool (ZFS calls the process of removing a pool from the&lt;br&gt;
system "exporting" and adding it "importing". Think of it as ejecting and inserting a thumb drive).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;zpool &lt;span class="nb"&gt;export &lt;/span&gt;zroot
root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;zpool import &lt;span class="nt"&gt;-R&lt;/span&gt; /mnt &lt;span class="nt"&gt;-N&lt;/span&gt; zroot
root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;zfs mount zroot/ROOT
root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;zfs mount &lt;span class="nt"&gt;-a&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of notes about this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We are using &lt;code&gt;-R /mnt&lt;/code&gt; and &lt;code&gt;-N&lt;/code&gt; for &lt;code&gt;zpool import&lt;/code&gt;. We have used &lt;code&gt;-R /mnt&lt;/code&gt; before when creating the pool: It causes the datasets to be mounted relative to &lt;code&gt;/mnt&lt;/code&gt;. &lt;code&gt;-N&lt;/code&gt; causes no datasets to be mounted by default. This is important because our root dataset has &lt;code&gt;canmount=noauto&lt;/code&gt; set and mounting other datasets automatically would cause them to be mounted in the wrong order&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zfs mount -a&lt;/code&gt; mounts all datasets that are automatically mountable&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.1 Aside: Device identifier
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Usually the recommendation with ZFS is to use /dev/disk/by-id/... instead of /dev/... device IDs since they are more stable&lt;/li&gt;
&lt;li&gt;With this kind of use case however, I opted for the more unstable /dev/... identifier in order to make switching physical hard drives easier&lt;/li&gt;
&lt;li&gt;If you use /dev/disk/by-id/... you have to set the environment variable &lt;code&gt;ZPOOL_VDEV_NAME_PATH&lt;/code&gt; in order for grub to be installable correctly &lt;sup id="fnref17"&gt;17&lt;/sup&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.2 Aside: Alternative dataset configurations
&lt;/h3&gt;

&lt;p&gt;There are multiple alternative ways of structuring your datasets that mostly come down to personal preference. The main rules (&lt;code&gt;/&lt;/code&gt; having to have &lt;code&gt;canmount=noauto&lt;/code&gt;)&lt;br&gt;
are the same for all of them - they just differ in the dataset layout.&lt;/p&gt;
&lt;h4&gt;
  
  
  4.2.1 Multiple roots
&lt;/h4&gt;

&lt;p&gt;Some guides place the system root in &lt;code&gt;zroot/ROOT/default&lt;/code&gt; in order to add support for multiple systems booting from the same pool with the same data directories.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;zfs list 
    NAME                 USED  AVAIL     REFER  MOUNTPOINT
    zroot               1.11M  48.0G       96K  none
    zroot/DATA           288K  48.0G       96K  none
    zroot/DATA/docker     96K  48.0G       96K  /var/lib/docker
    zroot/DATA/home       96K  48.0G       96K  /home
    zroot/ROOT           192K  48.0G       96K  none
    zroot/ROOT/default    96K  48.0G       96K  /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.2.2 Not separating ROOT &amp;amp; DATA
&lt;/h4&gt;

&lt;p&gt;The simplest setup would be to use the root-dataset as the system root mounted at &lt;code&gt;/&lt;/code&gt; and children datasets for data. Since by default datasets define a directory tree as&lt;br&gt;
well, you will have to be careful to set &lt;code&gt;canmount=off&lt;/code&gt; on parent-datasets of your data-directories in order to have all system data in the root dataset instead of scattered&lt;br&gt;
across multiple ones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;zfs list                          
    NAME                   USED  AVAIL     REFER  MOUNTPOINT
    zroot                 1.18M  48.0G       96K  /
    zroot/home              96K  48.0G       96K  /home
    zroot/var              288K  48.0G       96K  /var
    zroot/var/lib          192K  48.0G       96K  /var/lib
    zroot/var/lib/docker    96K  48.0G       96K  /var/lib/docker
root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;zfs get canmount                  
    NAME                  PROPERTY  VALUE     SOURCE
    zroot                 canmount  noauto    &lt;span class="nb"&gt;local
    &lt;/span&gt;zroot/home            canmount  on        &lt;span class="nb"&gt;local
    &lt;/span&gt;zroot/var             canmount  off       &lt;span class="nb"&gt;local
    &lt;/span&gt;zroot/var/lib         canmount  off       &lt;span class="nb"&gt;local
    &lt;/span&gt;zroot/var/lib/docker  canmount  on        &lt;span class="nb"&gt;local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Bootstrap System
&lt;/h2&gt;

&lt;p&gt;At this, we can bootstrap an arch system using &lt;code&gt;pacstrap&lt;/code&gt; as we would with regular systems.&lt;br&gt;
For more detailed information, check the arch installation guide &lt;sup id="fnref18"&gt;18&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Before we start, we will have to mount our EFI partition as &lt;code&gt;/mnt/boot/efi&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /mnt/boot/efi
root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;mount /dev/sda1 /mnt/boot/efi
root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;pacstrap /mnt base base-devel linux linux-firmware linux-headers dkms efibootmgr grub neovim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the inclusion of &lt;code&gt;linux-headers&lt;/code&gt; and &lt;code&gt;dkms&lt;/code&gt; - these packages will be required a bit later on when installing zfs inside the bootstrapped system. I also installed&lt;br&gt;
&lt;code&gt;neovim&lt;/code&gt; here as it is my preferred text editor. If you prefer a different text editor, then install a different one - you'll just need &lt;em&gt;something&lt;/em&gt; to edit files in a second.&lt;/p&gt;

&lt;p&gt;In preparation for a later step, we can also generate a &lt;code&gt;/etc/fstab&lt;/code&gt; file for our new system and immediately edit it: The script will include all mount points, however&lt;br&gt;
most of them are handled by zfs and don't need to be in &lt;code&gt;/etc/fstab&lt;/code&gt;. Only the non-zfs (in this case only the EFI-partition) need to be in the file. Comment out all&lt;br&gt;
ZFS datasets in the resulting &lt;code&gt;/mnt/etc/fstab&lt;/code&gt; and save the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;genfstab &lt;span class="nt"&gt;-U&lt;/span&gt; /mnt &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /mnt/etc/fstab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Install ZFS
&lt;/h2&gt;

&lt;p&gt;At this point we have a bootstrapped archlinux system in &lt;code&gt;/mnt&lt;/code&gt; but that system a) does not know about zfs and b) is not bootable. First, we are going to tackle the&lt;br&gt;
first point: Install zfs inside the new system.&lt;/p&gt;

&lt;p&gt;ZFS can save data about a zpool in a cachefile. In a later step, the &lt;code&gt;zfs&lt;/code&gt; hook will copy the cachefile into the initramfs in order for our booting kernel to know where&lt;br&gt;
to find the pool. This however is where a bit of weirdness comes in: The zpool cache is generated by the ZFS kernel module in the main system hosting the kernel.&lt;br&gt;
This means we will have to first create the cache and then copy it into the new system by hand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /mnt/etc/zfs
root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;zpool &lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;cachefile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/zfs/zpool.cache zroot 
root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; /etc/zfs/zpool.cache /mnt/etc/zfs/zpool.cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: You are not free to choose any path. &lt;code&gt;/etc/zfs/zpool.cache&lt;/code&gt; is hardcoded in the initramfs hook for zfs. &lt;br&gt;
This surprised me, but you can check for yourself in  &lt;code&gt;/usr/lib/initcpio/install/zfs&lt;/code&gt; &lt;sup id="fnref19"&gt;19&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;At this point, it is time to use &lt;code&gt;arch-chroot&lt;/code&gt; in order to change into our newly installed system in order to continue the installation process. We will continue by adding&lt;br&gt;
the archzfs pacman repository and keys as we did earlier while creating the ISO.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;arch-chroot /mnt                  
&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;[archzfs]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Server = https://archzfs.com/&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;repo/&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;arch&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/pacman.conf 
&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;pacman-key &lt;span class="nt"&gt;-r&lt;/span&gt; DDF7DB817396A49B2A2723F7403BD972F75D9D76
&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;pacman-key &lt;span class="nt"&gt;--lsign-key&lt;/span&gt; DDF7DB817396A49B2A2723F7403BD972F75D9D76
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Especially adding the keys can be a bit awkward on real hardware as it includes transcribing a hash from another screen.&lt;br&gt;
In case you are using the archiso I built (see Step 1), you can make this step easier by using scripts I have built in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /zfs-pacman.conf &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /mnt/etc/pacman.conf
root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; /zfs-key.sh /mnt/zfs-key.sh
root@archiso ~ &lt;span class="nv"&gt;$ &lt;/span&gt;arch-chroot /mnt              
&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;/zfs-key.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can install ZFS by installing the &lt;code&gt;zfs-dkms&lt;/code&gt; and &lt;code&gt;zfs-utils&lt;/code&gt; packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;pacman &lt;span class="nt"&gt;-Sy&lt;/span&gt; zfs-dkms zfs-utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, ZFS should be installed. We can confirm this by using the &lt;code&gt;zfs&lt;/code&gt; and &lt;code&gt;zpool&lt;/code&gt; commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;zpool list 
NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
zroot  49.5G  2.48G  47.0G        -         -     0%     5%  1.00x    ONLINE  /mnt
&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;zfs list 
NAME                USED  AVAIL     REFER  MOUNTPOINT
zroot              2.45G  45.5G       96K  none
zroot/DATA          288K  45.5G       96K  none
zroot/DATA/docker    96K  45.5G       96K  /mnt/var/lib/docker
zroot/DATA/home      96K  45.5G       96K  /mnt/home
zroot/ROOT         2.45G  45.5G     2.45G  /mnt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we can activate the following systemd services to ensure that ZFS will be initialized correctly upon boot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;systemctl enable zfs.target&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;systemctl enable zfs-import-cache.service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;systemctl enable zfs-mount.service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;systemctl enable zfs-import.target&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6.1 Aside: zfs-linux vs zfs-dkms
&lt;/h3&gt;

&lt;p&gt;The archzfs repository contains 2 different ways of installing ZFS &lt;sup id="fnref20"&gt;20&lt;/sup&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;zfs-linux&lt;/code&gt; (or &lt;code&gt;archzfs-linux-lts&lt;/code&gt;, &lt;code&gt;archzfs-linux-zen&lt;/code&gt;, ...) packages provides the kernel modules specific to these kernels&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;zfs-dkms&lt;/code&gt; uses dkms &lt;sup id="fnref21"&gt;21&lt;/sup&gt; in order to be compatible with all kernel versions. This comes at a cost of having to rebuild the kernel module every time you switch or upgrade the kernel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am opting to use the latter for 2 reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It reduces the mental load of having to install the correct package for your kernel&lt;/li&gt;
&lt;li&gt;I have been running into version incompatibilities between the kernel package and the zfs package due to the archlinux kernel being more recent than the archzfs repository anticipated&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  7. Configure bootloader &amp;amp; kernel images
&lt;/h2&gt;

&lt;p&gt;With our system bootstrapped and ZFS installed in it, it is time to get it into a bootable state. Mainly this means configuring initramfs to mount ZFS datasets and installing grub&lt;br&gt;
as a bootloader.&lt;/p&gt;

&lt;p&gt;The high-level overview of how a linux system usually boots is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The mainboards EFI is configured to start a bootloader (in our case grub)&lt;/li&gt;
&lt;li&gt;The bootloader then loads an image (the so called initramfs), which contains the kernel and the minimum set of applications in order to start the rest of the system. The bootloader also passes certain configuration to that initramfs image.&lt;/li&gt;
&lt;li&gt;initramfs is responsible for is tasked with bringing the system into a running state. This mainly includes mounting the system root partition (or dataset) as &lt;code&gt;/&lt;/code&gt;. If the system partition is encrypted, then initramfs is also responsible for decrypting the partition (e.g. by prompting the user for a password)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To add ZFS support to this whole chain of events, first we'll have to add the &lt;code&gt;zfs&lt;/code&gt; hook to &lt;code&gt;/etc/mkinitcpio.conf&lt;/code&gt;. The hook should be added before &lt;code&gt;filesystems&lt;/code&gt; and &lt;code&gt;keyboard&lt;/code&gt; should be added before &lt;code&gt;zfs&lt;/code&gt;. &lt;code&gt;fsck&lt;/code&gt; is specific to journaling filesystems, so it is not important for zfs. As such, the resulting line should look as follows:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HOOKS=(base udev autodetect modconf block keyboard zfs filesystems)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Save the file and use &lt;code&gt;mkinitcpio&lt;/code&gt; to regenerate the initramfs images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;mkinitcpio &lt;span class="nt"&gt;-P&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we'll have to configure grub to pass down the correct bootdevice configuration to the initramfs image. To do this, edit &lt;code&gt;/etc/default/grub&lt;/code&gt; and adjust the &lt;code&gt;GRUB_CMDLINE_LINUX=&lt;/code&gt; variable.&lt;br&gt;
Add &lt;code&gt;root=zfs&lt;/code&gt; and &lt;code&gt;zfs={ROOT_DATASET_NAME}&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GRUB_CMDLINE_LINUX="root=zfs zfs=zroot/ROOT"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Lastly, we'll need to install grub as a EFI boot option and generate its config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;grub-install &lt;span class="nt"&gt;--target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;x86_64-efi &lt;span class="nt"&gt;--efi-directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/boot/efi &lt;span class="nt"&gt;--bootloader-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arch-zfs
&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;grub-mkconfig &lt;span class="nt"&gt;-o&lt;/span&gt; /boot/grub/grub.cfg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes about this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--bootloader-id&lt;/code&gt; can be any string. It is what will show up in your EFI configuration&lt;/li&gt;
&lt;li&gt;If you have setup your zpool using a disk id in place of the disk path (e.g. &lt;code&gt;/dev/disk/by-id/...&lt;/code&gt; instead of &lt;code&gt;/dev/...&lt;/code&gt;), then &lt;code&gt;grub-mkconfig&lt;/code&gt; will likely fail with &lt;code&gt;grub-install: error: failed to get canonical path of `/dev/bus-Your_Disk_ID-part#&lt;/code&gt;'. In this case you will have to set the environment variable &lt;code&gt;ZPOOL_VDEV_NAME_PATH=1&lt;/code&gt; &lt;sup id="fnref17"&gt;17&lt;/sup&gt;. To set it globally for future grub config updates, add it to ''/etc/profile'&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7.1 Aside: bootfs
&lt;/h3&gt;

&lt;p&gt;An alternative approach to setting the dataset that should be used for booting is setting the &lt;code&gt;bootfs&lt;/code&gt; parameter on the pool.&lt;br&gt;
This way the dataset name can be changed much more easily without having to go through grub config.&lt;/p&gt;

&lt;p&gt;To do so, use &lt;code&gt;root=zfs zfs=bootfs&lt;/code&gt; in &lt;code&gt;/etc/default/grub&lt;/code&gt; and set the &lt;code&gt;bootfs&lt;/code&gt; option on the zpool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;zpool &lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;bootfs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;zroot/ROOT zroot 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method can be interesting in order to more easily boot off of a snapshot of your system. I personally prefer the simplicity of&lt;br&gt;
setting the dataset name directly in the grub config. If a system upgrade goes wrong, I will more likely completely rollback the&lt;br&gt;
dataset to the last snapshot instead of booting off of the snapshot itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2 Aside: Grub root= format
&lt;/h3&gt;

&lt;p&gt;There are 2 formats to specify the &lt;code&gt;root=...&lt;/code&gt; string in &lt;code&gt;/etc/default/grub&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;root=zfs zfs={DATASET_NAME}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;root=ZFS={DATASET_NAME}&lt;/code&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both do the same thing - when researching the topic, you will see some guides use one format and others use the other.&lt;br&gt;
If you are curious about more details as well as additional options, check out the mkinitcpio install script &lt;sup id="fnref22"&gt;22&lt;/sup&gt;&lt;br&gt;
as well as the script that will be embedded in the initramfs &lt;sup id="fnref23"&gt;23&lt;/sup&gt;. There's much less magic in there than you might think.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Configure Rest of the System
&lt;/h2&gt;

&lt;p&gt;At this point, all ZFS specific configuration has been done, and we'll have to finish configuring the system. This is not ZFS specific, so I will glaze over this. If you want more&lt;br&gt;
information about this step, check out the arch installation guide &lt;sup id="fnref24"&gt;24&lt;/sup&gt;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-sf&lt;/span&gt; /usr/share/zoneinfo/Europe/Berlin /etc/localtime
&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;hwclock &lt;span class="nt"&gt;--systohc&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;nvim /etc/locale.gen
&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;locale-gen
Generating locales...
    de_DE.UTF-8... &lt;span class="k"&gt;done
    &lt;/span&gt;en_DK.UTF-8... &lt;span class="k"&gt;done
    &lt;/span&gt;en_US.UTF-8... &lt;span class="k"&gt;done
&lt;/span&gt;Generation complete.
&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"LANG=en_US.UTF-8&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;LV_TIME=en_DK.UTF-8"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/locale.conf
&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'KEYMAP=colemak'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/vconsole.conf    &lt;span class="nv"&gt;$ &lt;/span&gt;Or your preferred keyboard layout
&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'arch-zfs-testmachine'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/hostname
&lt;span class="o"&gt;[&lt;/span&gt;root@archiso /]&lt;span class="nv"&gt;$ &lt;/span&gt;passwd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need to connect to Wi-Fi or have your IP address configured via DHCP, you should also install &lt;code&gt;iwd&lt;/code&gt; and &lt;code&gt;dhcpcd&lt;/code&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  9. Reboot
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;exit&lt;/code&gt; to exit the chroot environment and &lt;code&gt;reboot&lt;/code&gt; to reboot your system. You should now boot into your newly installed archlinux system running on ZFS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qdZ8DCZP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pgospjmee4tnp37ut2ax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qdZ8DCZP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pgospjmee4tnp37ut2ax.png" alt="A freshly booted ArchLinux installation running on top of ZFS" width="880" height="379"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A freshly booted ArchLinux installation running on top of ZFS&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Honorable mentions
&lt;/h2&gt;

&lt;p&gt;There are a couple of guides on installing ZFS on archlinux:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The official OpenZFS documentation contains a section named "Root on ZFS" &lt;sup id="fnref25"&gt;25&lt;/sup&gt;. This is the most complete guide, but it guides you through an extremely complicated setup. I don't recommend using this guide directly - but it is very helpful as a reference&lt;/li&gt;
&lt;li&gt;Arch-Wiki contains a page on installing arch on ZFS &lt;sup id="fnref26"&gt;26&lt;/sup&gt;. It is not as complicated as the official guide, but does not explain a lot of things&lt;/li&gt;
&lt;li&gt;The YouTube channel "Stephens Tech Talks" has a video guide &lt;sup id="fnref27"&gt;27&lt;/sup&gt; which is the simplest guide so far, showing a full runthrough of the whole thing. Mostly mirrors the arch guide, but guides you through a 'golden path'. Really, this was the first guide I had found that made me understand what was going on.&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;BIOS-boot systems should work similarly but without the EFI Partition and with a different &lt;code&gt;grub-install&lt;/code&gt; command. I haven't tried it though, so I can't vouch for it ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/License.html"&gt;https://openzfs.github.io/openzfs-docs/License.html&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://github.com/archzfs/archzfs/wiki"&gt;https://github.com/archzfs/archzfs/wiki&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;&lt;a href="https://wiki.archlinux.org/title/ZFS#Create_an_Archiso_image_with_ZFS_support"&gt;https://wiki.archlinux.org/title/ZFS#Create_an_Archiso_image_with_ZFS_support&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;&lt;a href="https://wiki.archlinux.org/title/Archiso"&gt;https://wiki.archlinux.org/title/Archiso&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;&lt;a href="https://github.com/j6s/archiso-zfs"&gt;https://github.com/j6s/archiso-zfs&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;&lt;a href="https://wiki.archlinux.org/title/Iwd#Connect_to_a_network"&gt;https://wiki.archlinux.org/title/Iwd#Connect_to_a_network&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn8"&gt;
&lt;p&gt;&lt;a href="https://zfsonlinux.topicbox.com/groups/zfs-discuss/T5177f234d7c777ab-M68f3f3eee18142560b193538/proper-partition-type-linux"&gt;https://zfsonlinux.topicbox.com/groups/zfs-discuss/T5177f234d7c777ab-M68f3f3eee18142560b193538/proper-partition-type-linux&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn9"&gt;
&lt;p&gt;Depending on the size and layout of your disk, free space may be inserted automatically. This is normal. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn10"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/EFI_system_partition"&gt;https://en.wikipedia.org/wiki/EFI_system_partition&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn11"&gt;
&lt;p&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Workload%20Tuning.html#alignment-shift-ashift"&gt;https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Workload%20Tuning.html#alignment-shift-ashift&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn12"&gt;
&lt;p&gt;&lt;a href="https://askubuntu.com/questions/970886/journalctl-says-failed-to-search-journal-acl-operation-not-supported"&gt;https://askubuntu.com/questions/970886/journalctl-says-failed-to-search-journal-acl-operation-not-supported&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn13"&gt;
&lt;p&gt;&lt;a href="https://github.com/openzfs/zfs/commit/82a37189aac955c81a59a5ecc3400475adb56355"&gt;https://github.com/openzfs/zfs/commit/82a37189aac955c81a59a5ecc3400475adb56355&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn14"&gt;
&lt;p&gt;&lt;a href="https://openzfs.org/wiki/Features#SA_based_xattrs"&gt;https://openzfs.org/wiki/Features#SA_based_xattrs&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn15"&gt;
&lt;p&gt;&lt;a href="https://blog.confirm.ch/mount-options-atime-vs-relatime/"&gt;https://blog.confirm.ch/mount-options-atime-vs-relatime/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn16"&gt;
&lt;p&gt;&lt;a href="https://github.com/archzfs/zfs-utils/blob/f6e3a5e93796bbb4919ff611d22b55ae692c67e8/zfs-utils.initcpio.hook#L110"&gt;https://github.com/archzfs/zfs-utils/blob/f6e3a5e93796bbb4919ff611d22b55ae692c67e8/zfs-utils.initcpio.hook#L110&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn17"&gt;
&lt;p&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/Getting%20Started/Arch%20Linux/Root%20on%20ZFS/5-bootloader.html"&gt;https://openzfs.github.io/openzfs-docs/Getting%20Started/Arch%20Linux/Root%20on%20ZFS/5-bootloader.html&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn18"&gt;
&lt;p&gt;&lt;a href="https://wiki.archlinux.org/title/Installation_guide#Installation"&gt;https://wiki.archlinux.org/title/Installation_guide#Installation&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn19"&gt;
&lt;p&gt;&lt;a href="https://github.com/archzfs/zfs-utils/blob/f6e3a5e93796bbb4919ff611d22b55ae692c67e8/zfs-utils.initcpio.install#L44"&gt;https://github.com/archzfs/zfs-utils/blob/f6e3a5e93796bbb4919ff611d22b55ae692c67e8/zfs-utils.initcpio.install#L44&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn20"&gt;
&lt;p&gt;&lt;a href="https://github.com/archzfs/archzfs/wiki#included-package-groups"&gt;https://github.com/archzfs/archzfs/wiki#included-package-groups&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn21"&gt;
&lt;p&gt;&lt;a href="https://wiki.archlinux.org/title/Dynamic_Kernel_Module_Support"&gt;https://wiki.archlinux.org/title/Dynamic_Kernel_Module_Support&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn22"&gt;
&lt;p&gt;&lt;a href="https://github.com/archzfs/zfs-utils/blob/f6e3a5e93796bbb4919ff611d22b55ae692c67e8/zfs-utils.initcpio.install"&gt;https://github.com/archzfs/zfs-utils/blob/f6e3a5e93796bbb4919ff611d22b55ae692c67e8/zfs-utils.initcpio.install&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn23"&gt;
&lt;p&gt;&lt;a href="https://github.com/archzfs/zfs-utils/blob/f6e3a5e93796bbb4919ff611d22b55ae692c67e8/zfs-utils.initcpio.hook"&gt;https://github.com/archzfs/zfs-utils/blob/f6e3a5e93796bbb4919ff611d22b55ae692c67e8/zfs-utils.initcpio.hook&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn24"&gt;
&lt;p&gt;&lt;a href="https://wiki.archlinux.org/title/Installation_guide#Configure_the_system"&gt;https://wiki.archlinux.org/title/Installation_guide#Configure_the_system&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn25"&gt;
&lt;p&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/Getting%20Started/Arch%20Linux/Root%20on%20ZFS/0-overview.html"&gt;https://openzfs.github.io/openzfs-docs/Getting%20Started/Arch%20Linux/Root%20on%20ZFS/0-overview.html&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn26"&gt;
&lt;p&gt;&lt;a href="https://wiki.archlinux.org/title/Install_Arch_Linux_on_ZFS"&gt;https://wiki.archlinux.org/title/Install_Arch_Linux_on_ZFS&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn27"&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=CcSjnqreUcQ"&gt;https://www.youtube.com/watch?v=CcSjnqreUcQ&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>linux</category>
      <category>zfs</category>
    </item>
    <item>
      <title>Replacing invalid UTF-8 octets</title>
      <dc:creator>Johannes Hertenstein</dc:creator>
      <pubDate>Wed, 10 Jul 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/j6s/replacing-invalid-utf-8-octets-3e83</link>
      <guid>https://dev.to/j6s/replacing-invalid-utf-8-octets-3e83</guid>
      <description>&lt;h3&gt;
  
  
  Why?
&lt;/h3&gt;

&lt;p&gt;Ever since unicode has become common between systems encoding related problems have largely gone away.Every now and then you receive some UTF-8 encoded strings that have some unexpected code points (e.g. control characters) in them,but that’s fairly easy to solve - You don’t even have to do it yourself, you can use ready made libararies such as &lt;a href="https://packagist.org/packages/patchwork/utf8"&gt;&lt;code&gt;patchwork/utf8&lt;/code&gt;&lt;/a&gt;for it.&lt;/p&gt;

&lt;p&gt;Recently however I have stumbled upon something new in an API response that I had never seen before: The contentscontained octets (bytes) that are not valid in UTF-8 codepoints and break some languages (such as PHP) UTF-8 handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Say what?
&lt;/h3&gt;

&lt;p&gt;A codepoint in UTF-8 describes a single character. UTF-8 uses a pagination approach in order tolet often used characters use less space while still being able to accomadate thousands of codepoints. This way a single codepoint can consist of between 1-4 octets. To do this the most significantbits of an octet are used to signal information about the pagination.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/UTF-8#Description"&gt;wikipedia article about UTF-8&lt;/a&gt; does a great job of explaining the concept.Here is a short summary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If an octet begins with &lt;code&gt;0xxxxxx&lt;/code&gt; then this octet is a standalone code point. The lowest standalone code point is &lt;code&gt;\x00&lt;/code&gt;, the highest &lt;code&gt;\x7F&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If an octet begins with &lt;code&gt;110xxxx&lt;/code&gt; then it is expected that another octet starting with &lt;code&gt;10xxxxxx&lt;/code&gt; follows. Both octets together are the full codepoint. The lowest octet containing a 2-page indicator is &lt;code&gt;\xC0&lt;/code&gt; while the highest is &lt;code&gt;\xDF&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If an octet begins with &lt;code&gt;1110xxx&lt;/code&gt; then it is expected 2 other octets starting with &lt;code&gt;10xxxxxx&lt;/code&gt; follow. All octets together are the full codepoint. The lowest octet containing a 3-page indicator is &lt;code&gt;\xE0&lt;/code&gt; while the highest is &lt;code&gt;\xEF&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If an octet begins with &lt;code&gt;11110xx&lt;/code&gt; then it is expected 2 other octets starting with &lt;code&gt;10xxxxxx&lt;/code&gt; follow. All octets together are the full codepoint. The lowest octet containing a 4-page indicator is &lt;code&gt;\xF0&lt;/code&gt; while the highest is &lt;code&gt;\xF7&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The lowest octet containing a following page indicator (&lt;code&gt;10xxxxxx&lt;/code&gt;) is &lt;code&gt;\x80&lt;/code&gt; while the highest is &lt;code&gt;\xBF&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This fact also means however:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;That any octet starting with &lt;code&gt;10xxxxxx&lt;/code&gt; that is not preceeded by a pagination indicator is invalid.&lt;/li&gt;
&lt;li&gt;That any octet starting with &lt;code&gt;110xxxx&lt;/code&gt;, &lt;code&gt;1110xxx&lt;/code&gt; or &lt;code&gt;11110xx&lt;/code&gt; not followed by the appropriate number of pagination indicators (&lt;code&gt;10xxxxx&lt;/code&gt;) is invalid&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To avoid confusion: These invalid octets are not invalid / unwanted codepoints. They are invalid bytes that do not add up to a full code point making the whole string an invalid UTF-8 string.&lt;/p&gt;

&lt;h3&gt;
  
  
  The solution
&lt;/h3&gt;

&lt;p&gt;As with many things regex are a solution - in my case the only performant solution I could come up with.The example below shows the regular expressions used to replace the invalid octets with a space in PHP - although this solution should work in any language that has full regex support.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 2-page indicator without 1 page behind it&lt;/span&gt;
&lt;span class="nv"&gt;$string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/[\xC0-\xDF](?![\x80-\xBF])/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 3-page indicator without 2 pages behind it&lt;/span&gt;
&lt;span class="nv"&gt;$string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/[\xE0-\xEF](?![\x80-\xBF][\x80-\xBF])/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 4-page indicator without 3 pages behind it&lt;/span&gt;
&lt;span class="nv"&gt;$string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/[\xF0-\xF7](?![\x80-\xBF][\x80-\xBF][\x80-\xBF])/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Paginated character without either another paginated character or page indicator in front of it.&lt;/span&gt;
&lt;span class="nv"&gt;$string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/(?&amp;lt;!([\xC0-\xF7]|[\x80-\xBF]))[\x80-\xBF]/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this the string is a valid UTF-8 string again only containing octet sequences that are valid codepoints in UTF-8.This means that other common UTF-8 sanitization measures can be taken such as using the &lt;code&gt;/u&lt;/code&gt; flag for regular expressions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Remove control characters and unused code points (requires valid UTF-8)&lt;/span&gt;
&lt;span class="nv"&gt;$string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/\p{C}/u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Replace various kinds of whitespace with a single space&lt;/span&gt;
&lt;span class="nv"&gt;$string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/\s+/u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Why not one big regex?
&lt;/h4&gt;

&lt;p&gt;Looking at this you can see 6 regular expressions that all replace things with a space - so you may wonder “wouldn’t this be more efficient in a single regex”?In fact, all of this can be built into a single regular expression using the pipe &lt;code&gt;|&lt;/code&gt; character pretty easily.I wondered about this and set out to test it.&lt;/p&gt;

&lt;p&gt;According to my (very limited results) there were no performance differences when using 6 small regular expressions vs one big one.I tested this with 1000 iterations on a 15MB text file and monitored runtime as well as peak memory usage: Both did not really change.&lt;/p&gt;

&lt;p&gt;Because they are roughly the same I opted for 6 small regular expressions as this makes it easier to logically separate them as well as document them accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The disclaimer
&lt;/h3&gt;

&lt;p&gt;A wise man once said&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“if you ever find yourself thinking &lt;em&gt;‘A regex would be the perfect solution to this’&lt;/em&gt; you will soon find you have two problems”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some problems are only feasibly solvable by using regular expressions. These times are dire and you should not rush over these kinds of implementations.Regular expressions are notoriosly hard to read and debug and I am very sure that there are still errors lurking in the expressions above.&lt;/p&gt;

&lt;p&gt;In times like this the only solution to preserve your sanity and to keep your project moving without ignoring edge cases is to write tests:Don’t take my word for the regular expressions above - If you end up using them be sure to include tests for all kinds of incredibly dumb invalid stringsyou can think of - and then some. If you cannot guarantee that something has no bugs then at least test for the edgecases you know of.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Converting Audible *.aax files</title>
      <dc:creator>Johannes Hertenstein</dc:creator>
      <pubDate>Sat, 04 May 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/j6s/converting-audible-aax-files-27go</link>
      <guid>https://dev.to/j6s/converting-audible-aax-files-27go</guid>
      <description>&lt;p&gt;Audible has a great selection of audiobooks that can be downloaded and listened to on the go. However, there is one big caviat: All audiobooks have DRM on them and can only be listened to using the official audible app.&lt;/p&gt;

&lt;p&gt;Wan’t to listen to the audiobook using your car’s stereo? Good luck. Wan’t to preserve the audiobook because you want to listen to it in 10 years time? Nope. Wan’t to use an android device without google play services or (heaven forbid) are looking forward to the linux smartphones landing in 2019? Amazon is laughing at you.&lt;/p&gt;

&lt;p&gt;Luckily, things are not as dire as my last paragraph would lead you to believe. If you have googled a bit you will most certainly have seen &lt;a href="https://en.code-bude.net/2017/02/12/how-to-convert-audible-aax-files-to-mp3-in-linux/"&gt;this blogpost on code-bude.net&lt;/a&gt; which outlines how to convert aax file to mp3. Since 2017 however things have gotten easier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The selenium based &lt;a href="https://github.com/inAudible-NG/audible-activator"&gt;&lt;code&gt;audible-activator&lt;/code&gt;&lt;/a&gt; tool has been preceeded by an offline tool / rcrack preset called &lt;a href="https://github.com/inAudible-NG/tables"&gt;&lt;code&gt;inAudible-NG/tables&lt;/code&gt;&lt;/a&gt; which should be more reliable (I could not get audible-activator to work).&lt;/li&gt;
&lt;li&gt;No other tool is needed: &lt;code&gt;ffmpeg&lt;/code&gt; supports audible conversion out of the box.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What do we need?
&lt;/h2&gt;

&lt;p&gt;To go forward we need to install 2 pieces of software: - &lt;a href="https://github.com/inAudible-NG/tables"&gt;&lt;code&gt;inAudible-NG/tables&lt;/code&gt;&lt;/a&gt; can be cloned for 1-time usage - &lt;code&gt;ffmpeg&lt;/code&gt; is probably already installed on your system&lt;/p&gt;

&lt;p&gt;&lt;code&gt;inAudible-NG/tables&lt;/code&gt; will provide us with a activation hash that is needed to decrypt the files. This hash is unique per account - meaning you only need to generate it once for all of your audiobooks.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ffmpeg&lt;/code&gt; will convert the files from the propriatary .aax format to whatever you like. I recommend using opus (for best compression) or mp3 (for best support).&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the activation bytes
&lt;/h2&gt;

&lt;p&gt;To extract the ‘activation bytes’ we first need a file hash of an aax file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ffprobe audiobook.aax
[...]
[aax] file checksum == 27ae5bf7df0bab8401776657d90dca85XXXXXXXX
[aax] activation_bytes option is missing!
[...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This hash is then passed to rcrack (from &lt;code&gt;inAudible-NG/tables&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./rcrack . -h 27ae5b47df0bab6401776657d90dca85XXXXXXXX
[...]
result
----------------------------------------------------------------
27ae5b47df0bab6401776657d90dca85XXXXXXXX [...] hex:c345eXXX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Converting the file
&lt;/h2&gt;

&lt;p&gt;Converting the file can be done with all of the usual ffmpeg options with an additional &lt;code&gt;-activation_bytes&lt;/code&gt; option containing the hex hash from above. Example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ ffmpeg -i audiobook.aax -activation_bytes c345eXXX freedom.mp3&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;All steps shown here are meant for file preservation. If you want to listen to quality audiobooks then buy, don’t pirate.&lt;/li&gt;
&lt;li&gt;The hashes and activation bytes in the examples above are obviously anonymized.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
    </item>
    <item>
      <title>Decrypting boot drives remotely using dropbear</title>
      <dc:creator>Johannes Hertenstein</dc:creator>
      <pubDate>Tue, 05 Mar 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/j6s/decrypting-boot-drives-remotely-using-dropbear-2hdf</link>
      <guid>https://dev.to/j6s/decrypting-boot-drives-remotely-using-dropbear-2hdf</guid>
      <description>&lt;p&gt;Thesedays there is no reason not to encrypt your bootdisk: I would even say that you are acting negligently if you don’t.&lt;/p&gt;

&lt;p&gt;There are moments where you cannot be physically present to decrypt a drive: For example in a server, a NAS or if you want to access your desktop PC remotely. Wouldn’t it be nice to be able to ssh into your machine in order to enter the encryption password? With &lt;code&gt;dropbear&lt;/code&gt; that’s possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Dropbear seems to have been very actively developed over the last couple of years - a lot of guides you will find on the internet are outdated. This article is up-to-date as of the beginning of 2019.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;p&gt;This article assumes a up-to-date Debian or Ubuntu system - though similar ready to use initramfs packages are available for other systems. All steps have been tested on Debian 10 but should work on Ubuntu in exactly the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installating &lt;code&gt;dropbear&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Dropbear consists of 2 components: - &lt;code&gt;dropbear&lt;/code&gt; is a very lightweight SSH server - &lt;code&gt;dropbear-initramfs&lt;/code&gt; is a initramfs integration for the &lt;code&gt;dropbear&lt;/code&gt; SSH Server.&lt;/p&gt;

&lt;p&gt;I have said initramfs a bunch without explaining what it does. For all intents and purposes initramfs can be thought of a micro-system that starts before you operating system that takes care of some plumbing (such as decrypting and mounting drives).&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring &lt;code&gt;dropbear&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;dropbear-initramfs&lt;/code&gt; only minimal configuration is needed: The only thing you have to do in order to get everything to work is add the public key of your client device to &lt;code&gt;/etc/dropbear-initramfs/authorized_keys&lt;/code&gt; and run &lt;code&gt;sudo update-initramfs -u&lt;/code&gt; to update the initramfs image.&lt;/p&gt;

&lt;p&gt;When rebooting the PCs IP-Address will be printend to the screen. You can now connect to the System using &lt;code&gt;ssh root@{YOUR_IP}&lt;/code&gt; and use &lt;code&gt;cryptroot-unlock&lt;/code&gt; in order to unlock your disks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring a static IP-Address
&lt;/h2&gt;

&lt;p&gt;Of course, looking at the screen to get the IP Address defeats the purpose - thus we have to make sure that the PC uses a static IP-Address while in initramfs. This configuration is different from the one already present in (&lt;code&gt;/etc/network/interfaces&lt;/code&gt; or via NetworkManager) as it has to be present before the system is decrypted and booted.&lt;/p&gt;

&lt;p&gt;To do that edit &lt;code&gt;/etc/initramfs-tools/initramfs.conf&lt;/code&gt; and add a line under the &lt;code&gt;DEVICE=&lt;/code&gt; line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IP=192.168.0.30:192.168.0.1:255.255.255.0::enp5s0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line is in the format &lt;code&gt;IP=ipaddress::gateway::netmask::hostname:eth&lt;/code&gt; - the hostname can be omitted.&lt;/p&gt;

&lt;p&gt;After running &lt;code&gt;sudo update-initramfs -u&lt;/code&gt; again to update the initramfs image our PC will now boot using that static IP Address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid host key colissions on the client
&lt;/h2&gt;

&lt;p&gt;If you regularly ssh into the machine you might notices SSH warning you about changing host keys - this is because openssh and dropbear are 2 separate SSH Keys with separate sets of host keys. Using the same key for both is not recommended as initramfs is not encrypted.&lt;/p&gt;

&lt;p&gt;To avoid host key colissions you can configure a separate trusted hosts store in the &lt;code&gt;~/.ssh/config&lt;/code&gt; of your client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host jo-desktop-unlock
    Hostname 192.168.0.30
    User root
    UserKnownHostsFile ~/.ssh/known_hosts.initramfs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extra: Only allow decryption
&lt;/h2&gt;

&lt;p&gt;Dropbear drops you into a shell by default - this has the main disadvantage that you have to remember the &lt;code&gt;cryptroot-unlock&lt;/code&gt; command (there is no real help in the shell) which is error prone.&lt;/p&gt;

&lt;p&gt;Luckily &lt;code&gt;dropbear&lt;/code&gt; has a way of running a specific command immediately after connecting. To immediately run the unlock command add the following to &lt;code&gt;/etc/dropbear-initramfs/config&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DROPBEAR_OPTIONS='-c cryptroot-unlock'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>linux</category>
      <category>encryption</category>
    </item>
    <item>
      <title>Desktop applications in containers</title>
      <dc:creator>Johannes Hertenstein</dc:creator>
      <pubDate>Thu, 14 Feb 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/j6s/desktop-applications-in-containers-4lf6</link>
      <guid>https://dev.to/j6s/desktop-applications-in-containers-4lf6</guid>
      <description>&lt;p&gt;I have been playing heavily with docker in the last couple of weeks and the idea of encapsulating applications including all of their dependencies and cruft they bring into a kind of ‘sub-system’ that only has well defined shared resources with the host did not only speak to me when thinking about servers and development environments. I have seen a trend with modern, closed source applications: They all start to provide their own repository for your package manager instead of bothering with the official ones. Adding a third party repository to your package manager simply to install spotify or slack is a question of trust - the list of third party repositories should be minimal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dockerize it
&lt;/h2&gt;

&lt;p&gt;Since in Linux everything is a file and docker can mount files to containers the thought of putting applications into containers is not very far fetched: It’s as easy as mounting the correct set of sockets to the container and the containerized application is able to talk to the system resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  X11
&lt;/h3&gt;

&lt;p&gt;In order for graphical output to work there are 3 things that need to be done:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The host must allow remote connections to X11 (since the container is seen as remote from the point of X11). This can be done by using &lt;code&gt;xhost local:root&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The X11 socket (Located under &lt;code&gt;/tmp/.X11-unix&lt;/code&gt;) needs to be mounted to the container&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;$DISPLAY&lt;/code&gt; environment variable needs to be passed down to the container&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To test if the connection to X11 is working correctly the following can be executed to setup a simple container containing the &lt;code&gt;xeyes&lt;/code&gt; application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

docker build -t 'thej6s/xeyes' - &amp;lt;&amp;lt; __EOF__
FROM debian
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y x11-apps
ENV DISPLAY $DISPLAY
CMD xeyes
__EOF__

XSOCK=/tmp/.X11-unix
xhost local:root
docker run -v $XSOCK --net host 'thej6s/xeyes'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6-xu06Wd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thej6s.com/article-images/xeyes-docker.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6-xu06Wd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thej6s.com/article-images/xeyes-docker.png" alt="xeyes running inside of docker" width="832" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sound: Alsa
&lt;/h3&gt;

&lt;p&gt;The next big hardware device that a desktop application might want to use is sound input and output. The simplest way is to let the guest handle all of the audio related tasks using alsa acessing the audio device directly. This would work similar to the X11 socket above - but with the &lt;code&gt;/dev/snd&lt;/code&gt; device.&lt;/p&gt;

&lt;p&gt;This works - but has a major drawback: It places all of the control over audio into the containers. Imagine having to ssh into multiple containers to regulate your volume.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sound: Pulseaudio
&lt;/h3&gt;

&lt;p&gt;Most distributions and most users are using pulseaudio in order to configure and manager their sound environment. A dockerized application should play into the global pulse instance instead of acessing the audio device directly. This way all dockerized applications are still managable by using a tool such as &lt;code&gt;pavucontrol&lt;/code&gt; on the host.&lt;/p&gt;

&lt;p&gt;This however presents a couple of difficulties: - Pulseaudio is started as a user service and is bound to the current machine and user&lt;/p&gt;

&lt;p&gt;In order to overcome these hurdles a couple of steps need to be taken: 1. Create an environment that is accepted by pulseaudio IPC - Create a user in the container with the same uid as the user on the host system - Mount &lt;code&gt;/etc/machine-id&lt;/code&gt; into the container 2. Mount the pulse audio socket (&lt;code&gt;/run/user/${UID}/pulse&lt;/code&gt;) into the container&lt;/p&gt;

&lt;p&gt;The following starts firefox in a container with support for pulseaudio for sound:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XSOCK=/tmp/.X11-unix
UID=$(id -u)

docker build -t 'j6s/firefox' - &amp;lt;&amp;lt; __EOF__
FROM debian

RUN apt-get update &amp;amp;&amp;amp; apt-get install -y firefox-esr

ENV HOME /home/user
RUN useradd -u ${UID} \
        --create-home --home-dir \
        /home/user user &amp;amp;&amp;amp; \
    usermod -a -G audio user &amp;amp;&amp;amp; \
    chown -R user:user /home/user

USER user
WORKDIR /home/user
CMD firefox-esr
__EOF__

docker run --rm \
    -v $XSOCK:$XSOCK \
    -v /etc/machine-id:/etc/machine-id \
    -v /run/user/${UID}/pulse:/run/user/${UID}/pulse \
    -e "DISPLAY=${DISPLAY}" \
    --name firefox \
    'j6s/firefox' \
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UjsXDd26--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thej6s.com/article-images/firefox-docker.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UjsXDd26--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thej6s.com/article-images/firefox-docker.png" alt="Firefox running inside of a container" width="798" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Spotify
&lt;/h2&gt;

&lt;p&gt;Let’s revisit how I started this article: The idea of encapsulating third party closed source applications appealed to me - that was the point of all of this. Spotify is the easiest example, as all that it needs is X11 and sound output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

XSOCK=/tmp/.X11-unix
UID=$(id -u)
DIR=$(pwd)

function run {
    echo -e "$ $@"
    eval $@
}

run mkdir -p data/config data/cache
run chown -R ${UID} data/
run chmod -R 755 data/

run docker build -t 'j6s/spotify' - &amp;lt;&amp;lt; __EOF__
FROM debian

RUN apt-get update &amp;amp;&amp;amp; apt-get install -y gpg
RUN apt-key adv \
        --keyserver hkp://keyserver.ubuntu.com:80 \
        --recv-keys 931FF8E79F0876134EDDBDCCA87FF9DF48BF1C90 &amp;amp;&amp;amp; \
    echo 'deb http://repository.spotify.com stable non-free' &amp;gt; /etc/apt/sources.list.d/spotify.list &amp;amp;&amp;amp; \
    apt-get update &amp;amp;&amp;amp;\
    apt-get install -y -q --no-install-recommends spotify-client

RUN apt-get install -y -q --no-install-recommends \
        pulseaudio \
        libgl1-mesa-dri \
        libgl1-mesa-glx

ENV HOME /home/user
RUN useradd -u ${UID} --create-home --home-dir /home/user user &amp;amp;&amp;amp; \
    usermod -a -G audio user &amp;amp;&amp;amp; \
    chown -R user:user /home/user

USER user
WORKDIR /home/user
CMD spotify
__EOF__

run docker run --rm \
    -v $XSOCK:$XSOCK \
    -v /etc/machine-id:/etc/machine-id \
    -v /run/user/${UID}/pulse:/run/user/${UID}/pulse \
    -v ${DIR}/data/config:/home/user/.config \
    -v ${DIR}/data/cache:/home/user/.cache \
    -e "DISPLAY=${DISPLAY}" \
    --name spotify \
    'j6s/spotify' \
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z7ijg4eF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thej6s.com/article-images/spotify-docker.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z7ijg4eF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thej6s.com/article-images/spotify-docker.png" alt="Spotify running inside of a docker container" width="816" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;p&gt;The following blogpost (and especially the github repository by the author) is very interesting when it comes to desktop applications running inside of containers: &lt;a href="https://blog.jessfraz.com/post/docker-containers-on-the-desktop/"&gt;https://blog.jessfraz.com/post/docker-containers-on-the-desktop/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I wrote a follow-up article: &lt;a href="https://thej6s.com/articles/2019-02-20__encapsulating-nonfree-applications-using-docker/"&gt;Encapsulating nonfree applications using docker&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>docker</category>
    </item>
    <item>
      <title>Automatic E-Mail attachement extraction</title>
      <dc:creator>Johannes Hertenstein</dc:creator>
      <pubDate>Thu, 03 Jan 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/j6s/automatic-e-mail-attachement-extraction-2ja7</link>
      <guid>https://dev.to/j6s/automatic-e-mail-attachement-extraction-2ja7</guid>
      <description>&lt;p&gt;I got a &lt;a href="https://getrocketbook.com/"&gt;reusable notebook&lt;/a&gt; for Christmas which is accompanied by a simple app that makes scanning your notes really easy. Scans of your notes are converted into PDF files which you can send yourself via E-Mail.&lt;/p&gt;

&lt;p&gt;All of that is near — but I would prefer having them in a special folder that is synced across my devices as that folder is part of my &lt;a href="https://gettingthingsdone.com/wp-content/uploads/2014/10/Weekly_Review_Checklist.pdf"&gt;weekly review&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So an idea popped into my head: Could I configure a mail client that simply saves attachments sent to a special mail address into a folder automatically — similar to how there are special Kindle &amp;amp; Evernote E-Mail addresses that save the contents to the respective services? Turns out, there is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An always-on Linux computer such as a raspberry pi, a server or a NAS&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getmail&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Package named &lt;code&gt;getmail4&lt;/code&gt; on Debian.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;code&gt;procmail&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;munpack&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Package named &lt;code&gt;mpack&lt;/code&gt; on Debian.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;E-Mail is a pretty clearly defined system that works very unix-y on servers: Multiple tools are involved that all do a single thing — but they do that single thing very well. I this case our stack uses &lt;code&gt;getmail&lt;/code&gt; for fetching mail from a server via IMAP or SMTP, &lt;code&gt;procmail&lt;/code&gt; for filtering those mails and &lt;code&gt;munpack&lt;/code&gt; in order to extract attachments to those mails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up &lt;code&gt;getmail&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;In order to use getmail, we will setup a configuration file in &lt;code&gt;~/.getmail/getmailrc&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[retriever]
type=SimpleIMAPSSLRetriever
server=imap.myserver.com
username=my_username
password=my_password

[destination]
type=MDA_external
path=/usr/bin/procmail

[options]
verbose=0
read_all=false
delete=false
delete_after=0
delete_bigger_than=0
max_bytes_per_session=0
max_message_size=0
max_messages_per_session=0
delivered=false
received=false
message_log=~/getmail.log
message_log_syslog=false
message_log_verbose=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;[retriever]&lt;/code&gt; section defines where the mails are being fetched from: In this case using IMAP over SSL from &lt;code&gt;imap.myserver.com&lt;/code&gt; using &lt;code&gt;mys username&lt;/code&gt; and &lt;code&gt;my_password&lt;/code&gt;. &lt;code&gt;getmail&lt;/code&gt; ships with retrievers for all major E-Mail protocols which can be seen in the &lt;a href="https://www.systutorials.com/docs/linux/packages/getmail-4.54.0/configuration.html#conf-retriever"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;[destination]&lt;/code&gt; section then defines what is called an MDA — a &lt;strong&gt;M&lt;/strong&gt; ail &lt;strong&gt;D&lt;/strong&gt; elivery &lt;strong&gt;A&lt;/strong&gt; gent: A different application that will deliver / process the mails. &lt;code&gt;getmail&lt;/code&gt; supports a couple of other different destinations but &lt;code&gt;MDA_external&lt;/code&gt; is what we need in order to pass on the fetched mails to &lt;code&gt;procmail&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At this point we have successfully configured &lt;code&gt;getmail&lt;/code&gt; in order to connect to the IMAP server and fetch E-Mails from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sorting mails using &lt;code&gt;procmail&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;procmail&lt;/code&gt; is a simple application that can be used as an MDA in order to filter and sort Mails into different mailboxes or pass them on to other processes if they match certain criteria. It uses as configuration file in &lt;code&gt;.procmailrc&lt;/code&gt; which looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PATH=/usr/bin:/bin:/usr/local/bin:$HOME/bin:$PATH

# Process all mails that arrive for save-notes@mydomain.com
:0
* ^TOsave-notes@mydomain\.com
| munpack -q -t -C $HOME/dropping_area
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;procmailrc&lt;/code&gt; format takes some getting used to. - &lt;code&gt;:0&lt;/code&gt; denotes the beginning of a new rule - &lt;code&gt;* ^TOsave-notes@mydomain\.com&lt;/code&gt; defines conditions that must be matched. In this case all mails that are sent to &lt;code&gt;save-notes@mydomain.com&lt;/code&gt; are being processed. - &lt;code&gt;| munpack -q -t -C $HOME/dropping_area&lt;/code&gt; defines the action to take with that mail. I this case the mail is being piped to &lt;code&gt;munpack&lt;/code&gt; which extracts all attachments into &lt;code&gt;~/dropping_area&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  It’s done
&lt;/h2&gt;

&lt;p&gt;Now, every time &lt;code&gt;getmail&lt;/code&gt; is being executed new mails will be fetched from the server, filtered and attachments will be extracted. To periodically execute &lt;code&gt;getmail&lt;/code&gt; a simple cronjob can be added for the current user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*/5 * * * * getmail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why not use fetchmail?
&lt;/h2&gt;

&lt;p&gt;When researching this topic you will find a lot of solutions using &lt;code&gt;fetchmail&lt;/code&gt; instead of &lt;code&gt;getmail&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, using &lt;code&gt;fetchmail&lt;/code&gt; has a major disadvantage: &lt;code&gt;fetchmail&lt;/code&gt; fetches all unread messages from the server and marks them as read. This behaviour is not wanted for situations with ‘catchall’ mail addresses, where only a small portion of the E-Mails are actually sent to this special mail address.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getmail&lt;/code&gt; tracks which mails have already been processed by using the message id instead of relying on the ‘read’ state on the server thereby not modifying any state on the server itself.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>email</category>
    </item>
  </channel>
</rss>
