<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Self Hosting</title><link>https://selfhosting.too-many-machines.com/</link><description>Recent content on Self Hosting</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>© Igor Wawrzyniak</copyright><lastBuildDate>Fri, 10 Jul 2026 14:00:00 +0200</lastBuildDate><atom:link href="https://selfhosting.too-many-machines.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Storage server, part 5: migrating from mdadm to btrfs and unencrypted to encrypted</title><link>https://selfhosting.too-many-machines.com/home/nas-5/</link><pubDate>Fri, 10 Jul 2026 14:00:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/home/nas-5/</guid><description>&lt;p&gt;In &lt;a class="link" href="https://selfhosting.too-many-machines.com/home/nas-3/" &gt;part 3&lt;/a&gt; and &lt;a class="link" href="https://selfhosting.too-many-machines.com/home/nas-4/" &gt;part 4&lt;/a&gt; I explained how I configured my NAS with encrypted drives and btrfs in RAID1 mode. Strictly speaking, it wasn&amp;rsquo;t true. When I originally built this server a few years ago, I used mdadm RAID1 and no encryption. I soon regretted the decision.&lt;/p&gt;
&lt;p&gt;Some time later, when I replaced some old hard drives with higher-capacity ones, I decided now it&amp;rsquo;s a good time. Changing the setup requires moving the data around. Having some drives less than 50% full simplifies it. This post is the actual migration, done live on my NAS (&amp;ldquo;firefly&amp;rdquo;) without buying any new hardware.&lt;/p&gt;
&lt;h2 id="the-starting-point"&gt;The starting point
&lt;/h2&gt;&lt;p&gt;Before the change, &lt;code&gt;sdb&lt;/code&gt; and &lt;code&gt;sde&lt;/code&gt; (2x3TB) were an mdadm RAID1 array (&lt;code&gt;md0&lt;/code&gt;), with LVM (&lt;code&gt;datavg&lt;/code&gt;) on top providing two mounted logical volumes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;othervol&lt;/code&gt; at &lt;code&gt;/data/other&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;noshare&lt;/code&gt; at &lt;code&gt;/data/noshare&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Four HDDs (&lt;code&gt;sdc&lt;/code&gt;, &lt;code&gt;sdd&lt;/code&gt;, &lt;code&gt;sdf&lt;/code&gt;, &lt;code&gt;sdg&lt;/code&gt;) were ext4, managed by SnapRAID, unencrypted. System drive &lt;code&gt;sda&lt;/code&gt; is left untouched.&lt;/p&gt;
&lt;p&gt;Target state:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sdb&lt;/code&gt; + &lt;code&gt;sde&lt;/code&gt;: LUKS on each disk individually, btrfs RAID1 on top, so bitrot gets corrected automatically&lt;/li&gt;
&lt;li&gt;every SnapRAID disk: LUKS on each disk individually too, ext4 on top of it&lt;/li&gt;
&lt;li&gt;one password unlocks everything, via a script run manually over SSH after boot, never at boot itself&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-process"&gt;The process
&lt;/h2&gt;&lt;h3 id="step-1-make-sure-nothing-writes-to-the-drives"&gt;Step 1: make sure nothing writes to the drives
&lt;/h3&gt;&lt;p&gt;A few things on this NAS (Grafana, Jellyfin, Syncthing, Samba) read or write to &lt;code&gt;/data/other&lt;/code&gt; or &lt;code&gt;/data/noshare&lt;/code&gt;. I didn&amp;rsquo;t want files to change mid-process. And later, those paths will be empty until the unlock script runs. I stopped them and disabled autostart:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; container in &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;DOCKER_CONTAINERS[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; docker stop &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$container&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; docker update --restart no &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$container&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; service in &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;SYSTEMD_SERVICES[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; systemctl stop &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$service&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; systemctl disable &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$service&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I also disabled IDrive scheduler, so it wouldn&amp;rsquo;t back up my local copies.&lt;/p&gt;
&lt;h3 id="step-2-local-copy"&gt;Step 2: local copy
&lt;/h3&gt;&lt;p&gt;Reformatting &lt;code&gt;sdb&lt;/code&gt;/&lt;code&gt;sde&lt;/code&gt; destroys everything on them, so I needed a temporary local copy (I have an offsite backup on IDrive, but restoring would be slow, plus 2 copies are safer than 1). I decided to use one of the SnapRAID video volumes which had enough free space:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cp -a /data/other /data/filmy/backup/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cp -a /data/noshare /data/filmy/backup/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Before wiping the old array, I verified the copy was bit-for-bit correct. There went another few hours.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rsync -ac --dry-run --stats /data/noshare/ /data/filmy/backup/noshare/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rsync -ac --dry-run --stats /data/other/ /data/filmy/backup/other/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;No files listed means both copies are identical. You might wonder why I used rsync for verification only, but not for doing the actual copy. Rsync has many features - it can resume, transfer over a network, copy only changed parts etc. But it also means for a simple task of copying files to an empty directory on the same machine, it&amp;rsquo;s noticeably slower. And, when you&amp;rsquo;re moving back and forth hundreds of gigabytes, a slower tool could mean an extra few hours.&lt;/p&gt;
&lt;h3 id="step-3-tearing-down-the-old-mdadmlvm"&gt;Step 3: tearing down the old mdadm/LVM
&lt;/h3&gt;&lt;p&gt;Now that I had a local copy, &lt;code&gt;sdb&lt;/code&gt;/&lt;code&gt;sde&lt;/code&gt; could be wiped. I prepared a script, supported by Claude Code. Nobody reviews my home scripts and there&amp;rsquo;s no test environment. Which is why I don&amp;rsquo;t trust the AI agent, but I don&amp;rsquo;t trust myself either (I don&amp;rsquo;t hallucinate, but I make typos and can mix up device names). Here&amp;rsquo;s the relevant part.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; mp in &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;OLD_LV_MOUNTPOINTS[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; umount &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$mp&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; 2&amp;gt;/dev/null &lt;span style="color:#f92672"&gt;||&lt;/span&gt; true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; vgs &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$OLD_LVM_VG&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &amp;amp;&amp;gt;/dev/null; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vgchange -an &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$OLD_LVM_VG&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vgremove -f &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$OLD_LVM_VG&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; -e &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$OLD_MD_DEVICE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mdadm --stop &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$OLD_MD_DEVICE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; dev in &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;BTRFS_RAID1_DEVICES[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mdadm --zero-superblock &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$dev&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; 2&amp;gt;/dev/null &lt;span style="color:#f92672"&gt;||&lt;/span&gt; true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wipefs -a &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$dev&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sed -i &lt;span style="color:#e6db74"&gt;&amp;#39;/ARRAY \/dev\/md0/d&amp;#39;&lt;/span&gt; /etc/mdadm/mdadm.conf 2&amp;gt;/dev/null &lt;span style="color:#f92672"&gt;||&lt;/span&gt; true
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The few &lt;code&gt;|| true&lt;/code&gt; parts and the &lt;code&gt;if&lt;/code&gt; check give a bit of idempotency: if the script fails, it can be rerun. Not as safe as an Ansible playbook would be, but easier to create for a one-off task.&lt;/p&gt;
&lt;p&gt;What it does is: unmount the filesystems, deactivate and remove the volume group, stop the array, zero the mdadm superblock on each member disk, &lt;code&gt;wipefs&lt;/code&gt; to clear any remaining LVM/mdadm signatures, and remove the array from &lt;code&gt;mdadm.conf&lt;/code&gt; so it doesn&amp;rsquo;t try (and fail) to reassemble it on the next boot.&lt;/p&gt;
&lt;h3 id="step-4-luks-on-each-disk"&gt;Step 4: LUKS on each disk
&lt;/h3&gt;&lt;p&gt;The order of layers is important: physical disks, LUKS, then btrfs. For the &amp;ldquo;one password for everything&amp;rdquo; requirement, I generated a single random keyfile once and used it directly as the LUKS key material on every disk in the whole setup (this pair and the SnapRAID ones later):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; ! -f &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$KEYFILE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dd &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/dev/urandom of&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$KEYFILE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; bs&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;512&lt;/span&gt; count&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; chmod &lt;span style="color:#ae81ff"&gt;600&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$KEYFILE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;LUKS can have multiple keys unlocking the same device. I added a second key slot on each disk with a passphrase that I saved in my password manager, in case the keyfile is ever lost or corrupted.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;read -r -s -p &lt;span style="color:#e6db74"&gt;&amp;#34;Recovery passphrase: &amp;#34;&lt;/span&gt; RECOVERY_PASSPHRASE
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;read -r -s -p &lt;span style="color:#e6db74"&gt;&amp;#34;Confirm recovery passphrase: &amp;#34;&lt;/span&gt; RECOVERY_PASSPHRASE_CONFIRM
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$RECOVERY_PASSPHRASE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; !&lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$RECOVERY_PASSPHRASE_CONFIRM&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;Passphrases don&amp;#39;t match.&amp;#34;&lt;/span&gt; &amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; exit &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;unset RECOVERY_PASSPHRASE_CONFIRM
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then created the encrypted devices and added the recovery passphrase as a second slot:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mapper_devices&lt;span style="color:#f92672"&gt;=()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;i&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; dev in &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;BTRFS_RAID1_DEVICES[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;BTRFS_MAPPER_PREFIX&lt;span style="color:#e6db74"&gt;}${&lt;/span&gt;i&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cryptsetup luksFormat --type luks2 &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$dev&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; --key-file &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$KEYFILE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; printf &lt;span style="color:#e6db74"&gt;&amp;#39;%s&amp;#39;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$RECOVERY_PASSPHRASE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; | cryptsetup luksAddKey &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$dev&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; --key-file &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$KEYFILE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; -
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cryptsetup luksOpen &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$dev&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$name&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; --key-file &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$KEYFILE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mapper_devices&lt;span style="color:#f92672"&gt;+=(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/dev/mapper/&lt;/span&gt;$name&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; i&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$((&lt;/span&gt;i &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;unset RECOVERY_PASSPHRASE
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="step-5-btrfs-raid1-on-top-of-luks-devices"&gt;Step 5: btrfs RAID1 on top of LUKS devices
&lt;/h3&gt;&lt;p&gt;With both disks open as &lt;code&gt;/dev/mapper/luks-btrfs1&lt;/code&gt; and &lt;code&gt;luks-btrfs2&lt;/code&gt;, creating the actual RAID1 pool is a single command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkfs.btrfs -m raid1 -d raid1 -L &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$BTRFS_LABEL&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;mapper_devices[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Btrfs replaces the LVM layer I used to have, so instead of logical volumes I created subvolumes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir -p /mnt/btrfs-root
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mount &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;mapper_devices[0]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; /mnt/btrfs-root
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; entry in &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;BTRFS_SUBVOLUMES[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; subvol&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;entry%%:*&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; btrfs subvolume create &lt;span style="color:#e6db74"&gt;&amp;#34;/mnt/btrfs-root/&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;subvol&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;umount /mnt/btrfs-root
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; entry in &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;BTRFS_SUBVOLUMES[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; subvol&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;entry%%:*&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mountpoint&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;entry#*:&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; -z &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$mountpoint&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mkdir -p &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$mountpoint&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mount -o &lt;span style="color:#e6db74"&gt;&amp;#34;subvol=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;subvol&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;mapper_devices[0]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$mountpoint&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice both subvolumes mount through &lt;code&gt;mapper_devices[0]&lt;/code&gt; (the first disk). With a multi-device btrfs filesystem you only mount one member - any of them - and btrfs deals with finding the other one and keeping them in sync.&lt;/p&gt;
&lt;p&gt;Finally, the script added &lt;code&gt;/etc/fstab&lt;/code&gt; entries for both mountpoints, but with &lt;code&gt;noauto&lt;/code&gt; so they&amp;rsquo;re not mounted at boot. I removed the old entries from fstab manually.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; entry in &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;BTRFS_SUBVOLUMES[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; subvol&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;entry%%:*&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mountpoint&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;entry#*:&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; -z &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$mountpoint&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;/dev/mapper/&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;BTRFS_MAPPER_PREFIX&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;1 &lt;/span&gt;$mountpoint&lt;span style="color:#e6db74"&gt; btrfs subvol=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;subvol&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;,noauto,nofail,nodev,noexec,noatime 0 0&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; /etc/fstab
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="step-6-moving-the-data-back"&gt;Step 6: moving the data back
&lt;/h3&gt;&lt;p&gt;With the new pool mounted and empty, I copied the data back from its temporary home. This time I used a &lt;code&gt;tar&lt;/code&gt; pipe instead of &lt;code&gt;cp -a&lt;/code&gt;. Since both directories have a lot of small files and &lt;code&gt;tar&lt;/code&gt; avoids the per-file overhead, I was hoping it would be faster. It was, actually, a bit slower. Oh well.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tar -cf - -C /data/filmy/backup/other . | tar -xf - -C /data/other
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tar -cf - -C /data/filmy/backup/noshare . | tar -xf - -C /data/noshare
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once both finished, I verified with &lt;code&gt;rsync&lt;/code&gt; again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rsync -ac --dry-run --stats /data/filmy/backup/other/ /data/other/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rsync -ac --dry-run --stats /data/filmy/backup/noshare/ /data/noshare/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Only after both came back clean did I delete the temporary copy on &lt;code&gt;/data/filmy&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="step-7-verifying-the-raid1-pool"&gt;Step 7: verifying the RAID1 pool
&lt;/h3&gt;&lt;p&gt;Btrfs hides some of its internal structure from standard Linux tools (though not as much as ZFS). The &lt;code&gt;lsblk&lt;/code&gt; command that&amp;rsquo;s very useful on a machine with many hard drives only shows mount points on one of the underlying devices:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sdb └─luks-btrfs1 /data/noshare
 /data/other
sde └─luks-btrfs2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That is expected. The real check is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;btrfs filesystem show /data/other
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;btrfs filesystem usage /data/other
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;They confirm the RAID1 setup. Another command you might be used to: &lt;code&gt;df -h&lt;/code&gt;, is not accurate on btrfs, the command you need is &lt;code&gt;btrfs filesystem df /data/other&lt;/code&gt;. For a simple setup like this - just a bunch of files copied all in one batch, no snapshots, no compression - the numbers were very close, but it&amp;rsquo;s not always the case.&lt;/p&gt;
&lt;p&gt;Also worth noting, the btrfs commands above on /data/noshare and /data/other give exactly the same results. That&amp;rsquo;s because these are subvolumes of the same filesystem.&lt;/p&gt;
&lt;h3 id="step-8-the-deferred-single-password-unlock-script"&gt;Step 8: the deferred, single-password unlock script
&lt;/h3&gt;&lt;p&gt;The LUKS keyfile gets GPG-encrypted with a human passphrase, and the plaintext copy gets shredded once every disk (this pair, and eventually the SnapRAID disks) is formatted with it.&lt;/p&gt;
&lt;p&gt;The unlock script, run manually over SSH after boot, asks for the password once:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;read -r -s -p &lt;span style="color:#e6db74"&gt;&amp;#34;Password: &amp;#34;&lt;/span&gt; PASSPHRASE
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo -n &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$PASSPHRASE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; | gpg --batch --yes --passphrase-fd &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; --decrypt &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$KEYFILE_ENC&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &amp;gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$KEYFILE_TMP&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;unset PASSPHRASE
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;chmod &lt;span style="color:#ae81ff"&gt;600&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$KEYFILE_TMP&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The decrypted keyfile is saved to &lt;code&gt;/dev/shm&lt;/code&gt; (tmpfs, never written to disk), and gets shredded on exit via a trap, whether the script succeeds or fails. From there, it&amp;rsquo;s the mirror image of the setup script - open both LUKS containers by UUID, scan for the btrfs devices, mount the subvolumes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; uuid in &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;BTRFS_RAID1_UUIDS[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;BTRFS_MAPPER_PREFIX&lt;span style="color:#e6db74"&gt;}${&lt;/span&gt;i&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cryptsetup luksOpen &lt;span style="color:#e6db74"&gt;&amp;#34;/dev/disk/by-uuid/&lt;/span&gt;$uuid&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$name&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; --key-file &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$KEYFILE_TMP&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; btrfs_mapper_devices&lt;span style="color:#f92672"&gt;+=(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/dev/mapper/&lt;/span&gt;$name&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; i&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$((&lt;/span&gt;i &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;btrfs device scan
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I was testing this in stages: the SnapRAID disks weren&amp;rsquo;t encrypted yet, so their section of the unlock script was commented out for now. After mounting the filesystem, the script also starts the dependent services again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; container in &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;DOCKER_CONTAINERS[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; docker start &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$container&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; service in &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;SYSTEMD_SERVICES[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; systemctl start &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$service&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="step-9-encrypting-the-first-snapraid-disk"&gt;Step 9: encrypting the first SnapRAID disk
&lt;/h3&gt;&lt;p&gt;Similarly to the mdadm device, I had to make local copies using free space on the other drives. This means working one disk at a time.&lt;/p&gt;
&lt;p&gt;First up: &lt;code&gt;/data/seriale&lt;/code&gt; on &lt;code&gt;/dev/sdg1&lt;/code&gt;. Once its contents were safely copied to &lt;code&gt;/data/filmy/backup&lt;/code&gt;, the disk itself could be wiped and re-encrypted. I turned the relevant part of the setup script into something reusable, since I&amp;rsquo;ll be running it four times (the remaining video disks, plus the parity disk):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;umount &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$MOUNTPOINT&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; 2&amp;gt;/dev/null &lt;span style="color:#f92672"&gt;||&lt;/span&gt; true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;awk -v mp&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$MOUNTPOINT&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;$2 != mp&amp;#39;&lt;/span&gt; /etc/fstab &amp;gt; /etc/fstab.tmp &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mv /etc/fstab.tmp /etc/fstab
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;wipefs -a &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$PARTITION&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I switched the fstab cleanup from a &lt;code&gt;sed&lt;/code&gt; pattern match to an &lt;code&gt;awk&lt;/code&gt; filter on the exact mountpoint field - a substring match could accidentally catch something like &lt;code&gt;/data/seriale1&lt;/code&gt;. In the past, I used more, but smaller drives; old paths were still present here and there.&lt;/p&gt;
&lt;p&gt;Same key setup as the btrfs pair: the same &lt;code&gt;master.key&lt;/code&gt;, and the same recovery passphrase added as a second key slot.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cryptsetup luksFormat --type luks2 &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$PARTITION&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; --key-file &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$KEYFILE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;printf &lt;span style="color:#e6db74"&gt;&amp;#39;%s&amp;#39;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$RECOVERY_PASSPHRASE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; | cryptsetup luksAddKey &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$PARTITION&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; --key-file &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$KEYFILE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; -
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cryptsetup luksOpen &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$PARTITION&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$MAPPER_NAME&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; --key-file &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$KEYFILE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I then recreated ext4 with the same options that I used before:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkfs.ext4 -m &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; -T largefile4 -L &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$LABEL&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/dev/mapper/&lt;/span&gt;$MAPPER_NAME&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir -p &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$MOUNTPOINT&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mount &lt;span style="color:#e6db74"&gt;&amp;#34;/dev/mapper/&lt;/span&gt;$MAPPER_NAME&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$MOUNTPOINT&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;/dev/mapper/&lt;/span&gt;$MAPPER_NAME&lt;span style="color:#e6db74"&gt; &lt;/span&gt;$MOUNTPOINT&lt;span style="color:#e6db74"&gt; ext4 noauto,nofail,nodev,noatime 0 0&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; /etc/fstab
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, the data goes back onto &lt;code&gt;/data/seriale&lt;/code&gt;, verified the same way as step 6, and its LUKS UUID gets added to the (still commented-out) &lt;code&gt;SNAPRAID_DISKS&lt;/code&gt; block in the unlock script.&lt;/p&gt;
&lt;h3 id="step-10-encrypting-the-rest-of-the-snapraid-disks"&gt;Step 10: encrypting the rest of the SnapRAID disks
&lt;/h3&gt;&lt;p&gt;Then it&amp;rsquo;s the same routine again for &lt;code&gt;filmy&lt;/code&gt;, &lt;code&gt;video2&lt;/code&gt; and &lt;code&gt;parity1&lt;/code&gt;. Only once it was done, I uncommented the SnapRAID block from the &lt;code&gt;unlock-mount.sh&lt;/code&gt; script. I ran &lt;code&gt;snapraid --force-uuid check | tee snapraid.log 2&amp;gt;&amp;amp;1&lt;/code&gt; to confirm the array&amp;rsquo;s fine. SnapRAID refuses to run when more than one disk changes, so the force parameter is needed. Then I shredded the unencrypted &lt;code&gt;master.key&lt;/code&gt;. Finally, a reboot, to confirm everything was working as planned. It was!&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary
&lt;/h2&gt;&lt;p&gt;The whole process took three days. Most of that was waiting for the file copy or verification (or idle time when it was done but I was away). Actual time spent at the terminal - maybe 2 or 3 hours.&lt;/p&gt;
&lt;p&gt;As a result, I got bitrot protection on my main array, plus all the nice features of btrfs. I&amp;rsquo;m not enabling compression (most space is used by already compressed files, such as JPEG photos, MP3 audio or archives). I&amp;rsquo;m surely going to use snapshots.&lt;/p&gt;
&lt;p&gt;Encryption was the most important goal. Most files on the NAS aren&amp;rsquo;t confidential - I wouldn&amp;rsquo;t care if my music collection leaks. But some are personal. My laptops have been encrypted for years, by encrypting the NAS I closed a major hole. Even if it was unlikely to be exploited, it didn&amp;rsquo;t feel proper.&lt;/p&gt;
&lt;h2 id="tips"&gt;Tips
&lt;/h2&gt;&lt;p&gt;I doubt anyone would be doing exactly the same thing, but maybe something similar?&lt;/p&gt;
&lt;h3 id="drive-letters-can-change"&gt;Drive letters can change
&lt;/h3&gt;&lt;p&gt;After a reboot, /dev/sda can become /dev/sde. There are several ways to deal with it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;UUIDs&lt;/strong&gt; are the best permanent solution to use in /etc/fstab or scripts. They don&amp;rsquo;t change, unless you reformat the filesystem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;lsblk&lt;/strong&gt; is your friend. For the commands you type manually, short drive paths are convenient, &lt;code&gt;lsblk&lt;/code&gt; is the quickest way to check all drives. Or, a less handy &lt;code&gt;lsblk -o NAME,TRAN,UUID,FSTYPE,MOUNTPOINT&lt;/code&gt; gives you UUIDs and transport type (usb, sata, nvme and others).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filesystem labels&lt;/strong&gt; can be used in fstab or mount commands, but on an encrypted machine, they&amp;rsquo;re only visible once the drives are unlocked.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LVM&lt;/strong&gt;, if used, can scan all devices for PVs, with the same caveat about encrypted drives.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="use-screen-or-tmux"&gt;Use screen or tmux
&lt;/h3&gt;&lt;p&gt;All the copying or verification commands take hours to run. If your server is running headless and you log in with ssh from another machine, you need to make sure the commands won&amp;rsquo;t be stopped if your connection is interrupted.&lt;/p&gt;
&lt;h3 id="have-more-than-one-copy"&gt;Have more than one copy
&lt;/h3&gt;&lt;p&gt;I only worked on local copies during the migration. But it&amp;rsquo;s easy to make a slip and type &amp;ldquo;wipefs /dev/sdf1/&amp;rdquo; when you actually meant &amp;ldquo;sdg1&amp;rdquo;. It didn&amp;rsquo;t happen to me, but I was prepared for it. Off-site backup gives peace of mind, local backup is faster, have both.&lt;/p&gt;
&lt;h3 id="monitor-hard-drive-throughput"&gt;Monitor hard drive throughput
&lt;/h3&gt;&lt;p&gt;Two useful commands: &lt;code&gt;iotop&lt;/code&gt; shows which process reads/writes most data. If the throughput is lower than expected, &lt;code&gt;iostat -x 1&lt;/code&gt; will show whether source or target is to blame - look for high &lt;em&gt;%util&lt;/em&gt; and &lt;em&gt;w_await&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In my case, the first attempt to make a local copy started at 250MB/s, but in a few seconds dropped to 30MB/s. Iostat showed that the destination disk was the bottleneck. Turned out the disk I&amp;rsquo;d picked was an SMR model. And that&amp;rsquo;s exactly what&amp;rsquo;s expected from SMR drives - once their write cache fills up, write speed drops to unacceptable values. I checked my HDDs, luckily one of my CMR drives (WD Red Plus) also had enough free space. Copying speed was a more sensible 100MB/s (the bottleneck was now the source drive, and I couldn&amp;rsquo;t achieve anything better from this hardware).&lt;/p&gt;
&lt;p&gt;Later in the same copy job, speed dropped to just a few MB/s for a different reason: a scheduled mdadm RAID consistency check had started and was competing for reads on the same array. That was more tricky to find, since the kernel task didn&amp;rsquo;t show up on iotop. &lt;code&gt;cat /proc/mdstat&lt;/code&gt; showed what was happening and that the task would take 10 hours to complete; &lt;code&gt;echo idle &amp;gt; /sys/block/md0/md/sync_action&lt;/code&gt; paused it. I didn&amp;rsquo;t need the check, in a moment I&amp;rsquo;d be wiping the array anyway.&lt;/p&gt;</description></item><item><title>Storage server, part 3: ZFS and btrfs vs traditional filesystems</title><link>https://selfhosting.too-many-machines.com/home/nas-3/</link><pubDate>Thu, 09 Jul 2026 14:00:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/home/nas-3/</guid><description>&lt;h2 id="what-i-have-now"&gt;What I have now
&lt;/h2&gt;&lt;p&gt;I use &lt;strong&gt;btrfs RAID1&lt;/strong&gt; for all miscellaneous data filesystems on my NAS and &lt;strong&gt;ext4 with SnapRAID&lt;/strong&gt; for video files. Previously, I used ext4 for all. Both approaches have their merits. But first, one important definition.&lt;/p&gt;
&lt;h2 id="what-is-bitrot-silent-data-corruption"&gt;What is bitrot (silent data corruption)?
&lt;/h2&gt;&lt;p&gt;Digital data, in general, is resistant to errors. There&amp;rsquo;s always a large enough difference between 0 and 1, on any storage or transmission media, that background noise and other random factors won&amp;rsquo;t flip one to the other. Unless the noise is so high it corrupts many bits, which is at least immediately detectable, but it can&amp;rsquo;t flip a single bit.&lt;/p&gt;
&lt;p&gt;Except it sometimes can. It&amp;rsquo;s not impossible, just very improbable, and if your device contains billions of bits, it will happen - especially on modern high-density media, and especially when it ages. The causes are varied: magnetic domains weakening on HDDs, charge leaking from NAND cells on unpowered SSDs and USB drives, a short voltage spike, tiny movement of HDD head during write, cosmic rays (this one seems to get the most coverage, but it&amp;rsquo;s the least probable).&lt;/p&gt;
&lt;p&gt;The effect is the same. The device continues to function, but some individual bits are changed. No error is reported by the OS or drive electronics. Sometimes, when you read files from many years ago you might notice a weird rectangle in a JPG photo, or an MP3 file starts skipping. You begin to wonder whether the file was corrupt from the beginning, after all, digital files don&amp;rsquo;t age. Now you know: they do.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s more of a concern for a NAS than for a regular computer. It&amp;rsquo;s a matter of probability: the more data you have, and the longer it sits there without being rewritten, the more likely the bit flip is.&lt;/p&gt;
&lt;h3 id="protecting-against-bitrot"&gt;Protecting against bitrot
&lt;/h3&gt;&lt;p&gt;Standard RAID doesn&amp;rsquo;t protect against bitrot, neither does standard backup. They might detect that two copies are different, but have no way to tell which one is right. You can store checksums of your files and regularly verify if they changed.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s exactly what ZFS and btrfs do. They both store a checksum alongside every block of data. On every read, the filesystem verifies the checksum; if it doesn&amp;rsquo;t match, and you have redundancy (mirror, RAID-Z, RAID1), it automatically reconstructs the correct data from the good copy and can rewrite the bad block.&lt;/p&gt;
&lt;p&gt;They also have a &amp;ldquo;scrub&amp;rdquo; command, that you should run regularly. It reads part of your dataset and verifies checksums, to also detect errors on those files that you don&amp;rsquo;t read regularly.&lt;/p&gt;
&lt;p&gt;SnapRAID also stores checksums of all files. But they are only checked if you run &lt;code&gt;snapraid scrub&lt;/code&gt; (or &lt;code&gt;snapraid check&lt;/code&gt;, which runs full verification of the whole drive), not on every read. All the more reason to schedule regular scrubs.&lt;/p&gt;
&lt;h2 id="modern-filesystem"&gt;Modern filesystem
&lt;/h2&gt;&lt;p&gt;ZFS and btrfs have some common features. It&amp;rsquo;s not a coincidence - btrfs was inspired by ZFS and despite not sharing code, they ended in a similar place. There are other filesystems like that, on Linux and other platforms, though they don&amp;rsquo;t have a full set of features.&lt;/p&gt;
&lt;p&gt;Both ZFS and btrfs combine RAID and a volume manager with filesystem functionality. While old-time sysadmins (me included) may frown on this approach - so against the traditional Unix spirit of one tool doing one thing - there are reasons for it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bitrot correction, as described earlier. If you separated the layers, RAID would have 2 copies of data but doesn&amp;rsquo;t know which one is correct; filesystem would detect bitrot, but would have no way to retrieve the correct copy.&lt;/li&gt;
&lt;li&gt;Array rebuild efficiency. Traditional RAID copies all blocks, because it has no way of knowing which are used, ZFS and btrfs skip empty blocks.&lt;/li&gt;
&lt;li&gt;Better performance without manual tuning. You don&amp;rsquo;t need to align the filesystem&amp;rsquo;s block size to the RAID layer&amp;rsquo;s stripe width, you don&amp;rsquo;t even need to know what that means.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both also feature Copy-on-Write (CoW) snapshots. Snapshots look like an exact copy of a filesystem, but they only need extra space for the data that&amp;rsquo;s different. Once you get used to working with snapshots, you&amp;rsquo;ll wonder how you could have lived without them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creating &amp;ldquo;system restore points&amp;rdquo; before a risky operation such as a distro upgrade and instantly rolling back if something goes wrong.&lt;/li&gt;
&lt;li&gt;Fast local backup with the ability to browse through old versions of a file (note, it doesn&amp;rsquo;t replace regular backup, it lives on the same computer).&lt;/li&gt;
&lt;li&gt;Efficiently storing multiple VMs that are mostly identical.&lt;/li&gt;
&lt;li&gt;A read-only snapshot ensures the data is not modified in the middle of a backup session.&lt;/li&gt;
&lt;li&gt;Snapshots can be sent and received over a network, very efficiently (only changed blocks) allowing fast off-site backup or remote replication.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="zfs"&gt;ZFS
&lt;/h3&gt;&lt;p&gt;Older of the two. Started on Solaris in 2001. Later, when large parts of Solaris were published under an open source licence, ported to FreeBSD. The &amp;ldquo;ZFS on Linux&amp;rdquo; project began in 2008, but wasn&amp;rsquo;t considered stable before version 0.6.0 from 2013.&lt;/p&gt;
&lt;p&gt;Licence doesn&amp;rsquo;t allow integrating it directly into the Linux kernel, the module needs to be built separately. Some distros ship ZFS kernel module and tools. Some don&amp;rsquo;t, but a determined enough user can build the module themself. Yet, it feels a bit like a second class citizen in Linux. You can&amp;rsquo;t boot the OS from it (except recent versions of Ubuntu that have experimental support), native Linux tools don&amp;rsquo;t work with ZFS - you need to use ZFS tools that use a different vocabulary. Due to being out of kernel, it&amp;rsquo;s often difficult to use it with Secure Boot. Other downsides include high resource usage (depending on features used, it might need a few GB of RAM for itself plus a noticeable amount of CPU time) and difficult performance tuning.&lt;/p&gt;
&lt;p&gt;On the plus side, it&amp;rsquo;s the better tested of the two and has more features. It can do equivalents of RAID1, 5 and 6 - and do them in a safer way. A known problem with RAID5/6 is called write hole. If the machine shuts down uncleanly in the middle of a write (e.g. due to a kernel bug or power outage), it&amp;rsquo;s possible that the data was written, but metadata wasn&amp;rsquo;t, leaving inconsistent state. In ZFS, write operations are atomic, they either fully succeed or fully fail.&lt;/p&gt;
&lt;p&gt;I tested ZFS and decided against it, mostly due to friction with Linux (though appetite for RAM was a factor too). If your goal is something more akin to a commercial NAS, where you&amp;rsquo;re more interested in file storage than tinkering, you might be satisfied with ZFS. Or if you don&amp;rsquo;t have many years of Linux experience and therefore a strong bias.&lt;/p&gt;
&lt;h3 id="btrfs"&gt;btrfs
&lt;/h3&gt;&lt;p&gt;The filesystem was developed in the late 2000s as a response to ZFS licensing problems. It has most (but not all) features of ZFS and is superficially similar, even if under the hood it&amp;rsquo;s quite different.&lt;/p&gt;
&lt;p&gt;Btrfs was merged into the mainline Linux kernel before it became stable. When it stabilised a few years later, it continued adding new, experimental features. Some users didn&amp;rsquo;t understand which parts of btrfs were safe to use. This is the source of the bad reputation of btrfs.&lt;/p&gt;
&lt;p&gt;In fact, these days it&amp;rsquo;s safe to use btrfs on a single disk or in RAID1/10 mode. The features that distinguish it from traditional filesystems (data checksumming, snapshots, subvolumes, compression) work reliably. It is widely used in production e.g. at Facebook, on Synology devices, as a default filesystem in several Linux distros.&lt;/p&gt;
&lt;p&gt;The remaining experimental part is RAID5 and 6. Stability is improving with each new version, but it&amp;rsquo;s still considered not safe for production. Most posts about btrfs problems are from people who used the RAID5 mode, or from the very early years.&lt;/p&gt;
&lt;p&gt;Compared to ZFS, btrfs is a bit more flexible - handles adding/removing disks in an existing array more easily. It also avoids licensing problems and all the &amp;ldquo;not invented here&amp;rdquo; problems that come from it. No extra modules that can lag behind the current kernel version or fail to build. Last but not least, for a greybeard sysadmin - less different from what I&amp;rsquo;m used to.&lt;/p&gt;
&lt;h2 id="traditional-filesystems"&gt;Traditional filesystems
&lt;/h2&gt;&lt;p&gt;Old, boring filesystems such as &lt;strong&gt;XFS&lt;/strong&gt; and (especially) &lt;strong&gt;ext4&lt;/strong&gt; still have some upsides. They are simple and therefore predictable. They don&amp;rsquo;t have hundreds of parameters for performance tuning (they have a few, but defaults mostly work). Decades of fsck, forensic tools, and &amp;ldquo;how to recover an ext4 filesystem&amp;rdquo; knowledge exist, including commercial data-recovery services. We can safely assume that all edge cases around unusual configurations were already discovered and fixed. Decoupling RAID, LVM and the filesystem means that if something isn&amp;rsquo;t right, you&amp;rsquo;re debugging one layer at a time. For simple use cases, reading and writing files (which, after all, are the main tasks of filesystems) they are usually faster. They use less RAM and CPU and don&amp;rsquo;t need that much disk space for metadata.&lt;/p&gt;
&lt;p&gt;Copy-on-write filesystems have an additional failure mode. They hate running out of disk space, and sometimes you can be hit by &amp;ldquo;write error, filesystem full?&amp;rdquo; message when df shows plenty of space left. Occasionally, you need to run &lt;code&gt;btrfs defrag&lt;/code&gt;, &lt;code&gt;btrfs rebalance&lt;/code&gt; or their ZFS equivalents (in my experience, in some unusual cases &amp;ldquo;occasionally&amp;rdquo; turns to &amp;ldquo;every night from cron&amp;rdquo;, but these were real servers with high utilisation, not a home NAS).&lt;/p&gt;
&lt;p&gt;That is why I used ext4 for my SnapRAID filesystems. SnapRAID handles bitrot protection, I don&amp;rsquo;t need two layers doing the same, and I don&amp;rsquo;t need snapshots for my video files - there was no reason for adding complexity. For the regular data, the balance was different. I decided that self-healing of btrfs is worth the additional hassle of setting up something I&amp;rsquo;m less familiar with.&lt;/p&gt;</description></item><item><title>Green IT</title><link>https://selfhosting.too-many-machines.com/general/green-it/</link><pubDate>Tue, 07 Jul 2026 00:00:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/general/green-it/</guid><description>&lt;img src="https://selfhosting.too-many-machines.com/general/green-it/laptops.jpg" alt="Featured image of post Green IT" /&gt;&lt;p&gt;I try to keep my IT usage reasonably environmentally friendly. Not in an extreme way - not using the computers at all would be the best for the planet, but quite impractical - but I do think about the footprint of what I run, and I think most of the community talks about the wrong part of it.&lt;/p&gt;
&lt;h2 id="energy-usage-is-the-secondary-concern"&gt;Energy usage is the secondary concern
&lt;/h2&gt;&lt;p&gt;Whenever &amp;ldquo;green IT&amp;rdquo; comes up in self-hosting circles, the conversation almost always goes straight to electricity. People tend to focus on how little power their new hardware uses, but forget about the impact of manufacturing. It takes a lot of energy to make a computer, not to mention scarce resources. Recycling old products is mostly non-existent. Or if it&amp;rsquo;s ever done, it&amp;rsquo;s usually done in the cheapest, but harmful ways, e.g. burning plastic to keep the metals. Companies often advertise their products as environmentally friendly due to reduced energy usage, but conveniently forget about those other costs.&lt;/p&gt;
&lt;p&gt;Note: power usage is &lt;em&gt;secondary&lt;/em&gt;, not &lt;em&gt;irrelevant&lt;/em&gt;. Especially if you&amp;rsquo;re running the device 24/7. I&amp;rsquo;ll get back to it later.&lt;/p&gt;
&lt;h2 id="reuse-what-you-already-have"&gt;Reuse what you already have
&lt;/h2&gt;&lt;p&gt;Before buying anything, I look at my pile of old hardware. An old laptop that&amp;rsquo;s too slow for daily use is often still perfectly capable of being an experimental machine. An old Android phone or tablet, too slow for a multi-purpose device or with a dead battery, still has a microphone, good quality camera, high-DPI screen and a multi-core CPU in GHz range. You can repurpose it as a dashcam/security camera/wildlife camera, a wall-mounted dashboard for a smart home or a media player.&lt;/p&gt;
&lt;h2 id="buying-second-hand"&gt;Buying second hand
&lt;/h2&gt;&lt;p&gt;When I do need something I don&amp;rsquo;t already have, second hand is the default, not new. There&amp;rsquo;s a market of off-lease laptops - mostly business-class devices such as ThinkPads and Dell Latitudes. &amp;ldquo;Business&amp;rdquo; in that case means dull colours, a non-gaming GPU, but a good build quality. They tend to have fewer mechanical problems than &amp;ldquo;home&amp;rdquo; series laptops. Corporations replace their hardware every few years to reduce their operational costs, but at home there&amp;rsquo;s a different economy. Older hardware is more at risk of failing, but that&amp;rsquo;s acceptable. If you continue using your hardware until it breaks and buy second-hand, you&amp;rsquo;ll end up with a mixture of different manufacturers and technologies. A logistical nightmare? Not really, why would I care if my service is running on a Lenovo laptop, Dell terminal or Raspberry Pi board if it&amp;rsquo;s running exactly the same? There is some value in unification, e.g. it&amp;rsquo;s convenient if your laptops share the same power bricks, or if you can scavenge one computer to repair another one. But don&amp;rsquo;t sweat too much about it.&lt;/p&gt;
&lt;h3 id="dont-go-too-old"&gt;Don&amp;rsquo;t go too old
&lt;/h3&gt;&lt;p&gt;That doesn&amp;rsquo;t mean any old hardware will do - there&amp;rsquo;s a balance. A 20-year-old machine will have a fraction of the processing power of a newer one and you would likely end up using several of them. The risk of failure is high and parts are hard to buy. At some point the electricity and hassle cost outweighs the manufacturing cost you&amp;rsquo;re avoiding.&lt;/p&gt;
&lt;p&gt;Specifically, for a daily-use laptop, I look for one powered by USB-C. It&amp;rsquo;s really convenient to use the same charger for laptops, phones and 90% of other rechargeable devices. Many, but not all, laptops made after 2018 have one. For experimental devices that I only use at home, I might accept a proprietary barrel connector, but not for the one I travel with.&lt;/p&gt;
&lt;p&gt;If you use Windows, remember that Windows 11 requires a newish CPU (8th gen Intel Core, AMD Ryzen 2000). There&amp;rsquo;s a lot of compatible used devices available (most laptops from 2018 onwards), just be aware when buying. And if you use Linux, that means you can buy a perfectly usable laptop with a 7th gen Core really cheaply.&lt;/p&gt;
&lt;p&gt;The same balance applies to routers and switches. A second-hand enterprise switch or router that&amp;rsquo;s around five years old could be a good buy - it&amp;rsquo;ll run current firmware, handle the speeds you actually need at home, and draw a sensible amount of power. A switch or router from 20 years ago is a different matter: it doesn&amp;rsquo;t support current protocols, it draws more power for a fraction of the throughput, and the vendor stopped shipping security updates a long time ago.&lt;/p&gt;
&lt;h3 id="whats-actually-worth-buying-new"&gt;What&amp;rsquo;s actually worth buying new
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PC Power supplies.&lt;/strong&gt; They don&amp;rsquo;t last as long as most computer hardware. They have fans and large capacitors which wear down with use, so an old PSU might not have much life left. Then there&amp;rsquo;s an issue of power efficiency. Old PSUs were horrible when running at low power, when the computer needed 40W, they drew twice as much. New ones are usually certified, 80+ means they are at least 80% efficient. Higher certifications such as 80+ Gold or Platinum are even better, but also considerably more expensive, so do your maths - if they save you $10 per year but cost $100 more, that doesn&amp;rsquo;t look like a good deal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hard drives.&lt;/strong&gt; Hard drives have a limited lifespan. Considering that their platters spin at 5400 RPM or more, their heads fly just some nanometers above them and have to be positioned with an almost atomic-level precision, it&amp;rsquo;s a wonder they work at all! But inevitably they fail. If you can get them very cheaply (free is a good price) and are prepared for failure (you should be anyway, even with new drives) then go ahead. There&amp;rsquo;s also a matter of capacity. When you need 2TB of storage, would you rather use a 2TB drive or 10x 200GB? Multiple disks take more power and use precious space in the case. Anything smaller than 1TB is probably useless for a NAS. Though an old and small disk might still be useful for an experimental device.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flash-based storage.&lt;/strong&gt; Such as SSDs, SD cards, thumb drives. You need to be even more careful than with HDDs. While they don&amp;rsquo;t wear down mechanically, they have a limited number of writes. An old SSD might be already useless. And unlike HDDs which usually show some bad sectors but continue to work, they often fail rapidly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batteries.&lt;/strong&gt; Batteries degrade even sitting unused on a shelf. When you buy a used laptop, it always has reduced capacity. It could be acceptable. But when it isn&amp;rsquo;t and you want to replace the battery, get a new one. It can be a 3rd party, doesn&amp;rsquo;t need to be original - as long as it comes from a trusted supplier.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Small components.&lt;/strong&gt; I wouldn&amp;rsquo;t bother trying to find a used SATA-to-NVMe adapter or a 3.5&amp;quot; to 2.5&amp;quot; HDD caddy. It&amp;rsquo;s not worth the hassle and I doubt it&amp;rsquo;s even possible in most cases.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="reducing-power-usage"&gt;Reducing power usage
&lt;/h2&gt;&lt;p&gt;Power usage is the second concern after manufacturing impact. Many self-hosters choose single-board computers such as Raspberry Pi for their projects. Some models of Pi Zero use below 1W. They&amp;rsquo;re a good choice if you need a single-purpose device.&lt;/p&gt;
&lt;p&gt;But you will need proper PCs for some stuff. Companies discovered the value of server consolidation in the early 2000s. Virtualization and later containers allowed running multiple separate workloads on one server. For this reason, you might want to stop using very old hardware even if it still works: it&amp;rsquo;s cheaper to run 2 reasonably modern PCs than 10 very old ones.&lt;/p&gt;
&lt;p&gt;One way to use old or low-power hardware (or fewer computers) is to choose your software carefully. 20 years ago computers were used in a very similar way despite being considerably slower. Sometimes you don&amp;rsquo;t have a choice, because the company standardises on one product. Or maybe the software you&amp;rsquo;re choosing has high hardware requirements, but also some features you really need. That&amp;rsquo;s OK. But especially for server software that you only configure once and then it runs quietly in the background doing its job, there&amp;rsquo;s sometimes an old alternative that needs just a few megabytes of RAM and a new, trendy one that takes 2GB.&lt;/p&gt;
&lt;p&gt;Proper configuration matters, too. Modern CPUs scale down when not in use and that should happen automatically with any Linux or Windows system unless you happen to have some buggy combination of hardware, firmware, drivers and OS.&lt;/p&gt;
&lt;p&gt;You might also consider powering down HDDs when not in use. Spinning up and down reduces the lifetime of disks, but staying on all the time also has some impact. If you power cycle your HDD every 5 minutes you will kill it in a few months, but one cycle per day shouldn&amp;rsquo;t be worse than always on. Disks in the home NAS are often unused for hours or days in a row (especially for larger setups with multiple disks). Low power devices might run from SD cards or thumb drives in some cases.&lt;/p&gt;
&lt;p&gt;A regular server in the office or datacentre runs 24/7, but that isn&amp;rsquo;t always necessary at home. You&amp;rsquo;re not going to use your NAS when you sleep or are away from home, right? It might use some of the time to do backups or other maintenance tasks, but probably doesn&amp;rsquo;t need that much time. Consider powering up the server only when required to save power, at an inconvenience of having to wait a few minutes when you need it. It&amp;rsquo;s a tradeoff with different answers for different people.&lt;/p&gt;
&lt;h2 id="choosing-computers-for-self-hosting"&gt;Choosing computers for self hosting
&lt;/h2&gt;&lt;h3 id="raspberry-pi"&gt;Raspberry Pi
&lt;/h3&gt;&lt;p&gt;A default choice for many self-hosters. It&amp;rsquo;s small and uses very little power. But it&amp;rsquo;s never been cheap for the processing power you get - an old PC can do the same work as a stack of Pis, but costs next to nothing. But sometimes you need a separate machine and a small form is an important factor. I have two Pis and could certainly use more.&lt;/p&gt;
&lt;h3 id="other-single-board-computers"&gt;Other single-board computers
&lt;/h3&gt;&lt;p&gt;Some producers also use Pi in their names, e.g. Banana Pi or Orange Pi, to jump on the bandwagon, but be careful, they have nothing in common with the Raspberry Pi. Others don&amp;rsquo;t, I think that&amp;rsquo;s more honest. I experimented with a few and the results were mixed. They offer similar or sometimes better hardware for a fraction of the price, but also get a fraction of the support. Whatever you try to do on the Pi, someone already did that and wrote a blog or posted a video tutorial. On another
board, you&amp;rsquo;re usually on your own. Raspberries will get the newest software in a few days and it&amp;rsquo;s going to be tested by thousands, on other boards you have to use a 3-year-old version or compile it yourself. If that&amp;rsquo;s OK for you then go ahead, if you&amp;rsquo;re new to Linux,
you better stick to Raspberry Pis.&lt;/p&gt;
&lt;h3 id="dell-wyse-thin-clients"&gt;Dell Wyse thin clients
&lt;/h3&gt;&lt;p&gt;They are often cheaper than single-board computers and have similar processing power. They have Intel Atom processors or AMD equivalents, 1 or 2 GB of RAM, some kind of storage such as SD, MMC or even a small HDD. They need a few watts, depending on the exact hardware. Their intended usage is a terminal, usually they run a built-in operating system called ThinOS whose only job is to connect to a virtual desktop server such as Citrix. But it can be replaced with Linux.&lt;/p&gt;
&lt;h3 id="old-laptops"&gt;Old laptops
&lt;/h3&gt;&lt;p&gt;You probably already have a few. If not, you can buy them really cheaply. Most only take about 5W when idle and maybe 40W under high load. A broken screen or keyboard is not an issue for server usage. A battery, even if it lost 95% of the original
capacity, is useful as a built-in UPS: it&amp;rsquo;s still good for a few minutes, enough to move around the house or shut down safely during a power outage.&lt;/p&gt;
&lt;p&gt;The downside of a laptop is its inconvenient form. If you need to run several at a time, they take up a lot of space if you keep them open - and many will overheat if you run them closed (some don&amp;rsquo;t mind and you can just pile them). Laptops are hard to repair, especially old ones. If you have several of the same type and know which way to hold a screwdriver, you might use some as organ donors. Or you might get unlucky and all of them will need the same part.&lt;/p&gt;
&lt;h3 id="small-form-factor-pcs"&gt;Small form factor PCs
&lt;/h3&gt;&lt;p&gt;For example Lenovo ThinkCentre. They share many traits with laptops. Just like them they are small, hard to upgrade, moderately power efficient and can run standard PC software. Generally cheaper than laptops when comparing the same generation hardware, easier to fit several in a small space, but lack the built-in UPS functionality.&lt;/p&gt;
&lt;h3 id="proper-pcs-in-tower-cases"&gt;Proper PCs in tower cases
&lt;/h3&gt;&lt;p&gt;They have one great feature: the ability to upgrade or replace hardware. You probably need one to use as a storage server (unless you prefer an off-the-shelf NAS). Do you need one for your daily work? If you want some serious processing power, you can get a powerful PC for 1/3 of the price of an equally powerful laptop.&lt;/p&gt;
&lt;h3 id="second-hand-servers"&gt;Second-hand servers
&lt;/h3&gt;&lt;p&gt;I don&amp;rsquo;t recommend them, unless you know what you&amp;rsquo;re doing. It&amp;rsquo;s a surprisingly cheap way to buy really powerful hardware - you can buy one with multiple CPUs, huge RAM and a rack-mountable case for the price of one Pi. But that&amp;rsquo;s because nobody wants them at home. Some old servers take a few hundred watts even when idle and possibly more than 1kW under load. That&amp;rsquo;s about the same as a hairdryer, no wonder they make a similar noise! You might consider one if it&amp;rsquo;s only going to be on occasionally, e.g. if you need a powerful machine for rendering videos or compiling. Having said that, some newer servers are more efficient. Another problem with servers is hardware compatibility: you can get replacement parts for standard PCs everywhere, servers have proprietary power supplies, need hard drive caddies etc.&lt;/p&gt;</description></item><item><title>Creating a photo gallery with Hugo and Mapael</title><link>https://selfhosting.too-many-machines.com/internet/gallery/</link><pubDate>Mon, 06 Jul 2026 11:30:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/internet/gallery/</guid><description>&lt;p&gt;The photo gallery was my initial reason for switching from Pelican to Hugo. Pelican didn&amp;rsquo;t have a good gallery theme. Plus, customising themes in Pelican was hard, and I had a specific goal in mind.&lt;/p&gt;
&lt;p&gt;Years ago, I posted my photos on TrekEarth, but this site is no more. I missed it, so I thought about publishing my travel photos first and having a map interface. Of course, I wouldn&amp;rsquo;t get the community this way, but you can&amp;rsquo;t have everything.&lt;/p&gt;
&lt;p&gt;So I decided to give a chance to another static generator, Hugo. I considered it before, I chose Pelican because it was written in Python (but I never touched the source code, so it&amp;rsquo;s irrelevant) and I perceived it to be simpler to use (maybe, not sure about it now).&lt;/p&gt;
&lt;h2 id="getting-started-with-hugo-gallery"&gt;Getting started with Hugo Gallery
&lt;/h2&gt;&lt;p&gt;I looked at available themes and chose one aptly named &amp;ldquo;Gallery&amp;rdquo;, created a repo on GitHub and initialised the site with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hugo new site web-gallery --force &lt;span style="color:#75715e"&gt;# force required because the directory was not empty - it contained git files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd web-gallery
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git submodule add --depth&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; https://github.com/nicokaiser/hugo-theme-gallery.git themes/gallery
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now some configuration in &lt;strong&gt;hugo.toml&lt;/strong&gt;. I copied it from the example website with obvious modifications:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;baseURL&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;https://photo.too-many-machines.com/&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;languageCode&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;en&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;title&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Photo&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;theme&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;gallery&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;copyright&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;© Igor Wawrzyniak&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;disableKinds&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;[&amp;#34;taxonomy&amp;#34;, &amp;#34;term&amp;#34;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;defaultContentLanguage&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;en&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;enableRobotsTXT&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;timeZone&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Europe/London&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[params]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;title&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Photo&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; defaultTheme = &amp;#34;dark&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[author]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Igor Wawrzyniak&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[outputs]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;page&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;[&amp;#34;HTML&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; home = [&amp;#34;HTML&amp;#34;, &amp;#34;RSS&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; section = [&amp;#34;HTML&amp;#34;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[imaging]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resampleFilter&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;CatmullRom&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; quality = 75
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; [imaging.exif]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;	disableDate = false
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;	disableLatLong = true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;	includeFields = &amp;#34;ImageDescription&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[module]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;[module.hugoVersion]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;extended&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;false
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;	min = &amp;#34;0.112.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="adding-content"&gt;Adding content
&lt;/h2&gt;&lt;p&gt;Photos go into subdirectories of &amp;ldquo;content&amp;rdquo;. I created directories for countries (Poland, UK, Ireland, Belgium&amp;hellip;) then for cities or regions (Poland/Wroclaw, Poland/Bieszczady). Unless I only have a few photos from the country and don&amp;rsquo;t plan to add more soon, in which case I stopped at the country level. But, Hugo will ignore directories until I add an index file (which could be useful - I can add some photos, but if I&amp;rsquo;m not ready, just skip creating the index file).&lt;/p&gt;
&lt;p&gt;There are two types of pages in Hugo, branch and leaf. Leaf is the end node - a post or article on a normal website, a gallery in this case. Branch contains a list of posts/articles/galleries. They use different template files, but from the user perspective the most important thing to remember is: for branch you need to create an _index.md file, for leaf it&amp;rsquo;s index.md without an underscore.&lt;/p&gt;
&lt;p&gt;The only relevant part of the page is the front matter - the metadata section on top. It can be written in several formats (YAML, TOML, JSON and ORG). Again, I followed the examples from the Gallery theme:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;title&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Belfast&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;linktitle&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Belfast&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;description&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;In 2005, when I lived in Dublin, I made a weekend trip to Belfast. It made a huge, if not always positive impression on me. Several years after the violence subsided, you could still see the marks it left. Barbwire everywhere, schools looked like prisons and police stations looked like fortresses. Murals praising terrorists from both sides - although some peaceful murals began to appear.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s it, all my index pages look like this. Now I can type &amp;ldquo;hugo server&amp;rdquo; and click the local URL to test.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Hugo gallery" class="gallery-image" data-flex-basis="322px" data-flex-grow="134" height="819" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/internet/gallery/g1-gallery.png" srcset="https://selfhosting.too-many-machines.com/internet/gallery/g1-gallery_hu_ade907bea25008c5.png 800w, https://selfhosting.too-many-machines.com/internet/gallery/g1-gallery.png 1102w" width="1102"&gt;&lt;/p&gt;
&lt;h2 id="creating-the-map"&gt;Creating the map
&lt;/h2&gt;&lt;p&gt;Instead of a list, I wanted to have a zoomable, clickable map on the main page. Front end development is not my speciality, I didn&amp;rsquo;t even know what software to use, so I asked ChatGPT (today I would use Claude). It told me to use Mapael library and generated HTML and JavaScript for me. The code was almost correct (imports didn&amp;rsquo;t work and the HTML was missing one important div) but it was easy to fix. Now I needed to replace just one page in my generated website with my custom HTML. I had to learn a bit more than I intended about the way Hugo templates work, but it wasn&amp;rsquo;t so bad and might come in useful in future.&lt;/p&gt;
&lt;p&gt;&lt;img alt="ChatGPT advising to use Mapael" class="gallery-image" data-flex-basis="430px" data-flex-grow="179" height="703" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/internet/gallery/g2-gpt.png" srcset="https://selfhosting.too-many-machines.com/internet/gallery/g2-gpt_hu_949309e0cc3f5517.png 800w, https://selfhosting.too-many-machines.com/internet/gallery/g2-gpt.png 1262w" width="1262"&gt;&lt;/p&gt;
&lt;p&gt;First thing to understand, all files from /themes/gallery/layouts can be overridden by files in /layouts - no need to replace the whole theme. Second, the main page by default uses the standard template for a branch page. But, if a template for &amp;ldquo;home page&amp;rdquo; is present, it takes precedence. So, I created a file /layouts/home.html.html (yes, double extension) and for the first attempt, I just put my crude HTML in there. It worked! Now, two things left to do: improve my map (set proper initial zoom, colours, add links to countries) and replace the manually created page with a proper template. The goal was to add extra imports in the head section (for mapael JavaScript files) and replace the standard gallery list with my map, but keep the rest (menu, footer, CSS) for the consistent look.&lt;/p&gt;
&lt;p&gt;I ended up creating several more files (everything&amp;rsquo;s on my GitHub):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I put all Mapael JavaScript files in /static&lt;/li&gt;
&lt;li&gt;in the same place I put map.js with my map configuration&lt;/li&gt;
&lt;li&gt;/layouts/partials/mapael_head.html contains all the code from the theme&amp;rsquo;s /layouts/partials/site_head.html plus my extra JavaScript imports (not perfect, if the file changes in the theme I&amp;rsquo;ll have to manually replace it, but the theme didn&amp;rsquo;t provide any hook for custom imports)&lt;/li&gt;
&lt;li&gt;/layouts/home.html.html is based on the theme&amp;rsquo;s /layouts/_default/baseof.html, but instead of importing site_head.html it imports mapael_head.html and instead of &amp;ldquo;block main&amp;rdquo; it has mapael divs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the end, I got a world map that&amp;rsquo;s zoomed on Europe (if I travel somewhere further, I&amp;rsquo;ll just have to zoom out), that automatically adjusts to the screen size, the countries with galleries are clickable and marked by a different colour than the rest.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Map generated with Mapael" class="gallery-image" data-flex-basis="509px" data-flex-grow="212" height="361" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/internet/gallery/g3-map.png" width="766"&gt;&lt;/p&gt;</description></item><item><title>Configuring email: MX, SPF and DMARC</title><link>https://selfhosting.too-many-machines.com/internet/mikrus-email/</link><pubDate>Mon, 06 Jul 2026 03:00:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/internet/mikrus-email/</guid><description>&lt;p&gt;The &lt;a class="link" href="https://selfhosting.too-many-machines.com/internet/mikrus/" &gt;mikr.us post&lt;/a&gt; mentioned that Outsider bundles a free mail server. But these days, if you just run the mail server without setting a few special &lt;strong&gt;DNS records&lt;/strong&gt;, few servers will accept emails from it. And definitely not the big ones like Gmail and Outlook. We can all thank spammers for it.&lt;/p&gt;
&lt;h2 id="a-note-on-record-types"&gt;A note on record types
&lt;/h2&gt;&lt;p&gt;I&amp;rsquo;m assuming you&amp;rsquo;re using some kind of admin panel for DNS (if you can edit zone files directly, I envy you). In most places, they default to &lt;strong&gt;A&lt;/strong&gt; record when you add new entries. Make sure to choose the right type, &lt;strong&gt;MX&lt;/strong&gt; for the first one, &lt;strong&gt;TXT&lt;/strong&gt; for the other two.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Adding a DNS record" class="gallery-image" data-flex-basis="422px" data-flex-grow="176" height="658" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/internet/mikrus-email/adding-dns-record.png" srcset="https://selfhosting.too-many-machines.com/internet/mikrus-email/adding-dns-record_hu_eb92643cbeeb6f72.png 800w, https://selfhosting.too-many-machines.com/internet/mikrus-email/adding-dns-record.png 1159w" width="1159"&gt;&lt;/p&gt;
&lt;h2 id="mx-record-where-mail-is-delivered"&gt;MX record: where mail is delivered
&lt;/h2&gt;&lt;p&gt;Other mail-related DNS records are a relatively new addition (early 2000s for SPF and DKIM, 2012 for DMARC), are intended to prevent spam and viruses and are still optional. MX has been with us since ancient times (1987), it&amp;rsquo;s required and it has a different purpose: it tells the world which server accepts mail for your domain.&lt;/p&gt;
&lt;p&gt;In my case, the mail server is outsider.mikr.us and priority can be anything. A domain can have multiple mail servers, in which case the priority field decides which one is main and which are fallback.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Editing an MX record" class="gallery-image" data-flex-basis="754px" data-flex-grow="314" height="460" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/internet/mikrus-email/editing-mx-record.png" srcset="https://selfhosting.too-many-machines.com/internet/mikrus-email/editing-mx-record_hu_5379c9980d48bafb.png 800w, https://selfhosting.too-many-machines.com/internet/mikrus-email/editing-mx-record.png 1446w" width="1446"&gt;&lt;/p&gt;
&lt;h2 id="spf-saying-whos-allowed-to-send-as-you"&gt;SPF: saying who&amp;rsquo;s allowed to send as you
&lt;/h2&gt;&lt;p&gt;SPF (Sender Policy Framework) is a TXT record listing the servers that are allowed to send mail claiming to be from your domain. Receiving mail servers check it. If a message claims to be from you but comes from a server not on the list, it can be flagged as suspicious (and likely classified as spam by the mail client) or rejected. Without an SPF record at all, there&amp;rsquo;s no check - anyone can send mail pretending to be you@your-domain.&lt;/p&gt;
&lt;p&gt;Outsider&amp;rsquo;s panel shows the exact value to publish. For my VM, it looks like this: &lt;code&gt;v=spf1 a mx ip4:95.217.59.141 ip6:2a01:4f9:4a:1384:0:0:0:2 ~all&lt;/code&gt;. Check with your own provider, you might need something else if you use mikrus, you definitely need something else if you use a different server.&lt;/p&gt;
&lt;p&gt;Breaking that string down: &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;mx&lt;/code&gt; allow the server(s) pointed to by your domain&amp;rsquo;s A/AAAA and MX records to send mail, the explicit &lt;code&gt;ip4&lt;/code&gt;/&lt;code&gt;ip6&lt;/code&gt; add the VM&amp;rsquo;s addresses directly, and &lt;code&gt;~all&lt;/code&gt; means anything else, not from the list, should be soft-failed (recommends the receiving servers to treat the mail as suspicious but don&amp;rsquo;t reject it). Soft-fail is a safer option while testing, when you&amp;rsquo;re reasonably sure your setup is correct, change &lt;code&gt;~all&lt;/code&gt; to &lt;code&gt;-all&lt;/code&gt; which means hard-fail: ask the receivers to reject all mail that doesn&amp;rsquo;t come from your servers.&lt;/p&gt;
&lt;p&gt;Note: if you want to run your own mail server on the VPS and use this domain as the &amp;ldquo;from&amp;rdquo; address, don&amp;rsquo;t forget to add its IP to SPF.&lt;/p&gt;
&lt;h2 id="checking-if-it-worked"&gt;Checking if it worked
&lt;/h2&gt;&lt;p&gt;Give DNS a few minutes to propagate, then check both records resolve:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dig +short MX your-domain.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dig +short TXT your-domain.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One thing to watch for: &lt;code&gt;dig&lt;/code&gt; results can lag behind reality if your local resolver cached an earlier, empty answer. If a record you just added doesn&amp;rsquo;t show up, try querying a public resolver directly (&lt;code&gt;dig @8.8.8.8 ...&lt;/code&gt; or &lt;code&gt;dig @1.1.1.1 ...&lt;/code&gt;) before assuming something&amp;rsquo;s wrong.&lt;/p&gt;
&lt;p&gt;You should see &lt;code&gt;outsider.mikr.us&lt;/code&gt; for the MX query and several TXT records, one of them being &lt;code&gt;v=spf1 ...&lt;/code&gt;. That&amp;rsquo;s enough to get basic mail flowing with a minimal anti-spoofing check in place.&lt;/p&gt;
&lt;p&gt;You can also use one of many services that check mail security. I don&amp;rsquo;t have any preferred one, I just search for &amp;ldquo;check mail domain&amp;rdquo; whenever I need it. Or, if you use an AI agent such as Claude, you can ask it to check your domain and recommend further steps.&lt;/p&gt;
&lt;h2 id="dmarc-policy-and-reporting"&gt;DMARC: policy and reporting
&lt;/h2&gt;&lt;p&gt;DMARC builds on SPF (and DKIM, but I don&amp;rsquo;t have it) by telling receiving servers what to do when a message fails those checks, and where to send reports about it. It&amp;rsquo;s just another DNS TXT record, published at &lt;code&gt;_dmarc.your-domain.com&lt;/code&gt;, so you can add it yourself regardless of what your mail provider supports.&lt;/p&gt;
&lt;p&gt;Rather than setting up my own mailbox to receive the aggregate reports, I used Cloudflare&amp;rsquo;s built-in &amp;ldquo;Email DMARC Management&amp;rdquo; feature, which generates the record for you and collects the reports on its own domain:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;v=DMARC1; p=none; rua=mailto:c45b1153cbda491795b7b2f1c5d429f4@dmarc-reports.cloudflare.net
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;p=none&lt;/code&gt; means monitor-only for now - nothing gets rejected or quarantined because of DMARC, it just starts collecting data on who&amp;rsquo;s sending mail as your domain and whether it&amp;rsquo;s passing SPF. Once I&amp;rsquo;ve seen a few reports and I&amp;rsquo;m confident nothing legitimate is failing, I can tighten it to &lt;code&gt;quarantine&lt;/code&gt; and then to &lt;code&gt;reject&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="dkim-the-missing-piece"&gt;DKIM: the missing piece
&lt;/h2&gt;&lt;p&gt;DKIM (DomainKeys Identified Mail) works differently from SPF. Instead of listing which servers are allowed to send your mail, the sending server cryptographically signs each outgoing message with a private key, and adds a &lt;code&gt;DKIM-Signature&lt;/code&gt; header to it. The public half of that key is published in DNS, as a TXT record at &lt;code&gt;selector._domainkey.your-domain.com&lt;/code&gt; (the &amp;ldquo;selector&amp;rdquo; is just a name your mail provider picks, so it can rotate keys later). Receiving servers fetch that public key and verify the signature matches - which proves two things: the message really was sent by something holding your private key, and it wasn&amp;rsquo;t altered in transit.&lt;/p&gt;
&lt;p&gt;That second part is what SPF can&amp;rsquo;t do. SPF only checks where a message came from; it says nothing about the message itself, and it breaks entirely when mail is forwarded through a third party (the forwarding server isn&amp;rsquo;t in your SPF record, so the check fails even though the message is legitimate). A DKIM signature travels with the message itself, so it survives being forwarded and still verifies correctly at the final destination.&lt;/p&gt;
&lt;p&gt;Unfortunately, there&amp;rsquo;s no DKIM available for outsider.mikr.us, or at least I wasn&amp;rsquo;t able to find it in the docs.&lt;/p&gt;
&lt;p&gt;Consequences of not having it set up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Forwarded mail is more likely to be rejected.&lt;/strong&gt; DMARC passes if either SPF or DKIM passes. If SPF ever fails (say, because a message got forwarded), the message fails DMARC entirely, with no fallback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slightly worse spam-filtering reputation.&lt;/strong&gt; Big providers (Gmail, Outlook) use DKIM as one more signal when scoring incoming mail. Having only SPF and DMARC isn&amp;rsquo;t disqualifying, but it&amp;rsquo;s one less thing working in your favour.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No tamper-evidence.&lt;/strong&gt; Without a signature, nothing proves the message body wasn&amp;rsquo;t modified between leaving the sending server and arriving at the recipient&amp;rsquo;s inbox.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of that is catastrophic for a low-volume personal domain like mine - SPF plus DMARC already stops trivial spoofing, which was the main goal. But DKIM is the piece that would close the gap.&lt;/p&gt;</description></item><item><title>Cloudflare: webmaster's little helper</title><link>https://selfhosting.too-many-machines.com/internet/cloudflare/</link><pubDate>Mon, 06 Jul 2026 01:00:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/internet/cloudflare/</guid><description>&lt;img src="https://selfhosting.too-many-machines.com/internet/cloudflare/1.png" alt="Featured image of post Cloudflare: webmaster's little helper" /&gt;&lt;p&gt;It&amp;rsquo;s not exactly self hosting. It&amp;rsquo;s very much against the spirit of self hosting. But I put Cloudflare in front of my external services: for one important reason and several small conveniences.&lt;/p&gt;
&lt;p&gt;The good thing is the free tier service is more than enough for amateurs experimenting with personal websites and apps. In fact, it&amp;rsquo;s enough for many commercial applications.&lt;/p&gt;
&lt;h2 id="the-main-reason-fixing-the-ipv6-problem"&gt;The main reason: fixing the IPv6 problem
&lt;/h2&gt;&lt;p&gt;My photo gallery lives on &lt;a class="link" href="https://selfhosting.too-many-machines.com/internet/mikrus/" &gt;mikrus&lt;/a&gt;, which has no public IPv4 address - only IPv6. Cloudflare&amp;rsquo;s reverse proxy makes it easy to reach it from any standard, IPv4-only client. It might sound complicated, but it really took only a few minutes. Here are the steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Register a Cloudflare account if you don&amp;rsquo;t have it yet.&lt;/li&gt;
&lt;li&gt;Login to Cloudflare control panel, choose &amp;ldquo;Add domain&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;Next, login to the domain registrar (OVH in my case) and set the nameservers to those provided by Cloudflare. Note, I had problems with DNSSEC, can&amp;rsquo;t confirm it now but probably it would be easier to disable DNSSEC before the DNS change.&lt;/li&gt;
&lt;li&gt;Cloudflare will automatically fill some of the DNS zone, not everything though (zone transfer is disabled by default). In my case I had proper records for the main domain + www + MX, but it was missing subdomains (advent, selfhosting&amp;hellip;) so I added them manually. Make sure proxy is disabled initially, the goal is to recreate the old configuration first - don&amp;rsquo;t introduce too many modifications at the same time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DNS change might take some time to propagate (minutes, hours, depending on how you set the TTL). But if all went well, you won&amp;rsquo;t notice any downtime, since old servers provide exactly the same information as new servers. Now, if you&amp;rsquo;re sure the domain&amp;rsquo;s DNS is now served by Cloudflare (check with &lt;code&gt;host -t NS your-domain-name&lt;/code&gt;), here comes the best part: I added &amp;ldquo;photos.too-many-machines.com&amp;rdquo; AAAA record with IPv6 address of my VPS and this time turned the proxy option on. The magic happens in the background:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I only declared AAAA record, but Cloudflare added both AAAA and A (IPv6 and IPv4),&lt;/li&gt;
&lt;li&gt;and they don&amp;rsquo;t point to the address I provided, they point to one of Cloudflare servers,&lt;/li&gt;
&lt;li&gt;which will proxy the connection and connect to my VPS on IPv6 while serving clients on both protocols.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Cloudflare proxy setup" class="gallery-image" data-flex-basis="810px" data-flex-grow="337" height="559" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/internet/cloudflare/g4-proxy.png" srcset="https://selfhosting.too-many-machines.com/internet/cloudflare/g4-proxy_hu_1912da44372c86cc.png 800w, https://selfhosting.too-many-machines.com/internet/cloudflare/g4-proxy_hu_b21f41314277eb16.png 1600w, https://selfhosting.too-many-machines.com/internet/cloudflare/g4-proxy.png 1887w" width="1887"&gt;&lt;/p&gt;
&lt;h2 id="nice-things-on-top"&gt;Nice things on top
&lt;/h2&gt;&lt;p&gt;Once I moved my domain to Cloudflare DNS, a few other things come along for free:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TLS termination.&lt;/strong&gt; Cloudflare gets you a certificate automatically and terminates HTTPS at the edge. I don&amp;rsquo;t need to run Certbot or renew anything on my own server for the domains behind the proxy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DNS management.&lt;/strong&gt; The dashboard is simple, changes propagate fast, and it&amp;rsquo;s one place to manage records for every domain, regardless of which registrar I bought it from.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reverse proxy for multiple sites.&lt;/strong&gt; I later enabled the proxy option for all other subdomains. Proxying works very well for static websites. My websites load faster and I use much less of Outsider&amp;rsquo;s bandwidth quota.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DDoS protection.&lt;/strong&gt; I doubt anyone is seriously targeting my personal blog. But these days, some bots (e.g. for training new LLMs) scan websites so aggressively that they could unintentionally DDoS my tiny VPS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web Application Firewall.&lt;/strong&gt; Irrelevant for static websites, useful if you want to run web applications. Cloudflare will inspect the traffic and stop known attack patterns.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="downsides"&gt;Downsides
&lt;/h2&gt;&lt;p&gt;It&amp;rsquo;s not all upside, though:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;You&amp;rsquo;re relying on an external service.&lt;/strong&gt; Have a way out in case Cloudflare goes out of business (unlikely) or terms of service change (a bit more likely). In my case, the photo gallery would be the only problem, everything else could be reverted in a few minutes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You&amp;rsquo;re trusting a third party with your traffic.&lt;/strong&gt; TLS gets terminated at Cloudflare&amp;rsquo;s edge, which means Cloudflare can see your traffic in plaintext before it re-encrypts (or doesn&amp;rsquo;t) the connection to your origin. For a personal blog where all the content is supposed to be publicly available anyway, that&amp;rsquo;s not an issue. For a real app, it might be.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One more outage point.&lt;/strong&gt; If Cloudflare has a bad day, your site can go down even though your own server is perfectly healthy. Cloudflare is very reliable, but it had a &lt;a class="link" href="https://blog.cloudflare.com/18-november-2025-outage/" target="_blank" rel="noopener"
 &gt;famous outage in 2025&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Occasional edge-case weirdness.&lt;/strong&gt; Once traffic goes through a caching proxy instead of hitting your server directly, it could be harder to debug a problem with your app. Never happened to me, but it is a possibility.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Self hosting on the internet: mikr.us</title><link>https://selfhosting.too-many-machines.com/internet/mikrus/</link><pubDate>Mon, 06 Jul 2026 00:00:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/internet/mikrus/</guid><description>&lt;img src="https://selfhosting.too-many-machines.com/internet/mikrus/1.png" alt="Featured image of post Self hosting on the internet: mikr.us" /&gt;&lt;p&gt;If you read the rest of this site, you probably noticed a clear split. Everything under &amp;ldquo;Self hosting at home&amp;rdquo; runs on my NAS and stays on the local network - no exposing it to the internet. That&amp;rsquo;s on purpose. The services don&amp;rsquo;t need to be reachable from the outside world. And every open port is one more thing that can be attacked.&lt;/p&gt;
&lt;p&gt;But there are services that I want to live on the internet: this website, for example.
My photo gallery, some experimental apps. For that, you need a VPS: Virtual Private Server. A virtual machine which you can configure to your needs and easily recreate if you break something.&lt;/p&gt;
&lt;p&gt;These days, many admins automatically think of AWS, Google Cloud Platform or Azure when they need a VM. They have nice features such as replication and auto-scaling. But, if you need exactly one VM running 100% of the time, traditional hosting providers are much cheaper.&lt;/p&gt;
&lt;h2 id="why-mikrus"&gt;Why mikr.us
&lt;/h2&gt;&lt;p&gt;mikr.us is a Polish VPS provider aimed at learning and experimentation. It shows in two things: pricing and Service Level Agreement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A whole year costs about as much as a single month (sometimes just a few days) of a typical cloud VM. I pay 75 PLN a year, which is roughly 13 GBP / 15 EUR / 16 USD.&lt;/li&gt;
&lt;li&gt;For that price you obviously don&amp;rsquo;t get an enterprise SLA. But in practice it has been about as reliable as the more expensive services I&amp;rsquo;ve used.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that you can run commercial services from it, if you like. I know that many companies use it, especially for testing or to run monitoring scripts.&lt;/p&gt;
&lt;p&gt;There are also some quirks you have to accept:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The server only has an IPv6 address. Reaching it over IPv4 needs port redirection (you automatically get one for SSH and can configure some more, all on high ports), or a proxy in front of it if you want standard ports and your own domain (which is easier than it sounds and explained in the next post).&lt;/li&gt;
&lt;li&gt;The resources are modest, this is not where you run anything resource-hungry.&lt;/li&gt;
&lt;li&gt;You can&amp;rsquo;t run anything other than Linux and you can&amp;rsquo;t choose the kernel - it is shared.&lt;/li&gt;
&lt;li&gt;Service agreement specifically prohibits several applications that put too much load (media streaming, some game servers), plus the usual illegal or grey-area activities.&lt;/li&gt;
&lt;li&gt;Support is community-driven rather than enterprise-grade.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of that has been a real problem for me. It&amp;rsquo;s meant to be a bit of a hands-on, DIY experience, and you end up learning something in the process - which is exactly what I wanted.&lt;/p&gt;
&lt;h3 id="outsider"&gt;Outsider
&lt;/h3&gt;&lt;p&gt;A useful feature of mikr.us is called Outsider. It&amp;rsquo;s an extra service bundled in for free, and it gives you an easy, ready-to-go shared hosting setup: web server, PHP support, databases, mail server, automatic SSL certificates - all without touching your own server configuration.&lt;/p&gt;
&lt;p&gt;It works like a charm for static content (and would work fine for small PHP apps too), and it&amp;rsquo;s where I keep most of my websites. This one and a few other small pages. The real limits are 500MB of space and a fairly low bandwidth quota, which is more than enough for text and a reasonable number of images, but not for something like a photo gallery with hundreds of full-size photos. That&amp;rsquo;s the one thing I run on the actual VPS instead, behind Cloudflare, exactly like I described in the &lt;a class="link" href="https://selfhosting.too-many-machines.com/internet/gallery/" &gt;photo gallery post&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="other-conveniences"&gt;Other conveniences
&lt;/h3&gt;&lt;p&gt;There are loads of them, I haven&amp;rsquo;t even used half.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Strych&lt;/strong&gt; (attic) is a backup solution where you can rsync files from your mikr.us VM&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NOOBS&lt;/strong&gt; is a set of scripts for installing and configuring many popular applications&lt;/li&gt;
&lt;li&gt;A shared instance of &lt;strong&gt;WireGuard&lt;/strong&gt; if you need a VPN but don&amp;rsquo;t want to configure your own&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pusher&lt;/strong&gt; sends mail notification to the VPS owner&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;M.A.R.I.A.N&lt;/strong&gt; is a tool for diagnosing problems with the VPS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Amfetamina&lt;/strong&gt; is a temporary boost (more RAM and CPU) e.g. when compiling software&lt;/li&gt;
&lt;li&gt;Comes with a &lt;strong&gt;free subdomain&lt;/strong&gt; but you can assign your own domains&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="one-inconvenience"&gt;One inconvenience
&lt;/h3&gt;&lt;p&gt;The registration site is only in Polish, so is the documentation and the main VPS admin panel. These days, when all browsers can translate websites, it&amp;rsquo;s not a deal breaker.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Mikrus admin panel" class="gallery-image" data-flex-basis="368px" data-flex-grow="153" height="1087" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/internet/mikrus/mikrus-panel.png" srcset="https://selfhosting.too-many-machines.com/internet/mikrus/mikrus-panel_hu_afe557e4a87a3a1f.png 800w, https://selfhosting.too-many-machines.com/internet/mikrus/mikrus-panel_hu_e5601c3789d0af10.png 1600w, https://selfhosting.too-many-machines.com/internet/mikrus/mikrus-panel.png 1669w" width="1669"&gt;&lt;/p&gt;
&lt;p&gt;Outsider admin panel, where you&amp;rsquo;d probably spend more time, has multiple language versions. And if you reach out to the community (on Discord or Facebook) someone will surely answer in English.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Outsider admin panel" class="gallery-image" data-flex-basis="393px" data-flex-grow="164" height="1164" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/internet/mikrus/outsider-panel.png" srcset="https://selfhosting.too-many-machines.com/internet/mikrus/outsider-panel_hu_a73c6e342688e3df.png 800w, https://selfhosting.too-many-machines.com/internet/mikrus/outsider-panel_hu_749e55b578b760e0.png 1600w, https://selfhosting.too-many-machines.com/internet/mikrus/outsider-panel.png 1909w" width="1909"&gt;&lt;/p&gt;
&lt;h2 id="getting-started-with-mikrus"&gt;Getting started with mikr.us
&lt;/h2&gt;&lt;p&gt;Convinced already? Go to the &lt;a class="link" href="https://mikr.us/?r=34eb52ab" target="_blank" rel="noopener"
 &gt;mikr.us website&lt;/a&gt; (full disclosure: it&amp;rsquo;s an affiliate link, if you use it I&amp;rsquo;ll get 2 months free) and select the version you need.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Frog&lt;/strong&gt; is an almost-free option - only a one-time payment of 5 PLN (that&amp;rsquo;s about $1 or €1), but it&amp;rsquo;s very limited&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mikrus 1.0&lt;/strong&gt; has only 384MB of RAM and can&amp;rsquo;t run Docker - still, a good choice for a single-purpose machine, e.g. to run monitoring scripts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mikrus 2.1&lt;/strong&gt; is a sweet spot for me: 1GB of RAM and 10GB of disk space is enough for a Linux server. I used to run commercial services on less than that.&lt;/li&gt;
&lt;li&gt;If you need more, there are higher plans available, up to &lt;strong&gt;Mikrus 4.2&lt;/strong&gt; with 16GB RAM and 160GB disk space.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Register, pay and in a moment you&amp;rsquo;ll have your machine up and running. You&amp;rsquo;ll get your initial password in the welcome mail and the admin panel will show the exact SSH command (you need to connect on a high port, not the usual 22).&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s next? It&amp;rsquo;s a Linux server, you can do anything with it. I&amp;rsquo;ll show some ideas in later posts. Remember to keep it updated and backed up.&lt;/p&gt;</description></item><item><title>Generating websites with Hugo</title><link>https://selfhosting.too-many-machines.com/internet/hugo/</link><pubDate>Sun, 05 Jul 2026 12:00:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/internet/hugo/</guid><description>&lt;h2 id="static-website-generator-vs-content-management-system"&gt;Static Website Generator vs. Content Management System
&lt;/h2&gt;&lt;p&gt;If you&amp;rsquo;re a dinosaur like me, you probably wrote your first websites manually, in HTML. In the 1990s and early 2000s, that was the default choice. Then came Content Management Systems, mostly based on PHP and MySQL: many competing systems first, until Wordpress dominated the market. Everyone shifted to them.&lt;/p&gt;
&lt;p&gt;I want to convince you to go back from server-generated pages. But it doesn&amp;rsquo;t mean you need to edit HTML manually like it&amp;rsquo;s 1998. Static website generators use a different concept:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;your content lives in plain text files (usually Markdown)&lt;/li&gt;
&lt;li&gt;after editing a post or adding a new one, you use the generator to &amp;ldquo;build&amp;rdquo; the site&lt;/li&gt;
&lt;li&gt;it will then combine your content with the theme (that decides how you site looks) and configuration&lt;/li&gt;
&lt;li&gt;and will output HTML, CSS and images, that you can upload to the server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The build and upload phase can be automated, if you prefer.&lt;/p&gt;
&lt;h3 id="who-is-this-guide-for"&gt;Who is this guide for
&lt;/h3&gt;&lt;p&gt;I&amp;rsquo;m assuming you&amp;rsquo;re familiar with CMSes. It&amp;rsquo;s not really necessary, but I&amp;rsquo;m going to compare the two approaches. I&amp;rsquo;m also assuming you&amp;rsquo;re not afraid to work with the terminal. But it doesn&amp;rsquo;t mean you need to be an IT professional (though if you are one, everything here will be easy, if you&amp;rsquo;re not, it might take some effort).&lt;/p&gt;
&lt;p&gt;Static websites often go together with git. If you&amp;rsquo;re familiar with it, I highly recommend to put your contents under version control. If you&amp;rsquo;re not, you can skip this part, it&amp;rsquo;s not required.&lt;/p&gt;
&lt;h3 id="or-let-the-ai-do-it"&gt;Or let the AI do it
&lt;/h3&gt;&lt;p&gt;The first time I used Hugo, I did all the steps below manually. This time I used Claude. It turned out it could competently handle most of the steps on its own: install the theme, change configs, rebuild, check the result, correct if needed. It worked especially well for migrating from Pelican to Hugo, since it required some boring steps: converting front matter and fixing image paths. I also used it to check for typos and other language errors (English is not my first language).&lt;/p&gt;
&lt;p&gt;That doesn&amp;rsquo;t mean you can skip reading this guide, though. Even if you don&amp;rsquo;t run the commands, you need to understand the concepts. AI agents make mistakes and need pointing in the right direction.&lt;/p&gt;
&lt;h3 id="advantages-over-cms"&gt;Advantages over CMS
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt; - Since there&amp;rsquo;s no server-side processing, there are far fewer ways to attack your website.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server availability and cost&lt;/strong&gt; - Again these come from no server-side processing: every web server can easily serve static pages. Which means you can use any cheap hosting provider and don&amp;rsquo;t even need to check what features are available.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed&lt;/strong&gt; - On a CMS, the page is generated the moment someone accesses it, which takes time - could be a few seconds or more for a complex page on a busy server. Serving static files skips this step entirely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintenance&lt;/strong&gt; - There&amp;rsquo;s next to none. If you ever used WordPress, you know you need to constantly update your plugins and themes and it always introduces incompatibilities. With a static website generator, you can just continue using your setup as-is. (Fine print: you probably want to update the theme and the generator itself occasionally to get new features, but it rarely requires any changes to your config or content.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ease-of-use"&gt;Ease of use?
&lt;/h3&gt;&lt;p&gt;CMS is easier to use, at least once someone set it up for you. Everything is from an admin panel on the web. Website generators have a steeper learning curve.&lt;/p&gt;
&lt;p&gt;But they&amp;rsquo;re only hard in the beginning. Once you&amp;rsquo;ve learned the concepts and configured it, adding more content is easy.&lt;/p&gt;
&lt;h3 id="pelican-vs-hugo"&gt;Pelican vs. Hugo
&lt;/h3&gt;&lt;p&gt;I had used another website generator in the past - Pelican. In 2023 I wanted to build a photo gallery and discovered its limitations. There are few people maintaining the app and creating themes, the themes themselves are hard to customise. I used Hugo for my gallery and it worked perfectly. Next time I needed to create a site, I used Hugo too. I&amp;rsquo;m now in the process of migrating my old websites.&lt;/p&gt;
&lt;h2 id="step-by-step-guide"&gt;Step by step guide
&lt;/h2&gt;&lt;h3 id="some-new-vocabulary"&gt;Some new vocabulary
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Markdown&lt;/strong&gt;: a simple, text-only file format that offers some formatting: headings, bold/italic, tables etc. Used by many static website generators (plus GitHub, Jupyter and many other places, if you&amp;rsquo;re in IT, you use it everyday).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Front matter&lt;/strong&gt;: a block of metadata at the top of a content file. Here you set title, date, tags, draft status etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Theme&lt;/strong&gt;: a separate package of layout and styling files that decides how your content looks, without touching the content itself. Swapping themes shouldn&amp;rsquo;t require rewriting your posts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Archetype&lt;/strong&gt;: a template used when you create a new page, so it starts with some sensible front matter already filled in.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shortcode&lt;/strong&gt;: a small reusable snippet you can call from inside a Markdown file for things plain Markdown can&amp;rsquo;t do on its own, such as embedding a video.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Page bundle&lt;/strong&gt;: probably the concept that trips up new Hugo users the most, so it gets its own section below.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="installing-hugo-and-creating-a-site"&gt;Installing Hugo and creating a site
&lt;/h3&gt;&lt;p&gt;I use Debian on my home laptop, so a simple &lt;code&gt;sudo apt install hugo&lt;/code&gt; is enough to install. Other platforms have their own packages. Or you can download from the project&amp;rsquo;s website - Hugo ships a single binary. Make sure you get the &amp;ldquo;extended&amp;rdquo; edition if your distribution offers a choice - some themes need it for SCSS support.&lt;/p&gt;
&lt;p&gt;Once installed, creating a new site is one command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hugo new site my-website
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd my-website
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This generates a skeleton directory: &lt;code&gt;content&lt;/code&gt; for your pages, &lt;code&gt;layouts&lt;/code&gt; for any template overrides, &lt;code&gt;static&lt;/code&gt; for files copied verbatim (favicons, robots.txt), &lt;code&gt;archetypes&lt;/code&gt; for new-page templates, and a configuration file (&lt;code&gt;hugo.toml&lt;/code&gt; by default, or &lt;code&gt;.yaml&lt;/code&gt;/&lt;code&gt;.json&lt;/code&gt; if you prefer).&lt;/p&gt;
&lt;h2 id="choosing-a-theme"&gt;Choosing a theme
&lt;/h2&gt;&lt;p&gt;Browse the theme gallery on the Hugo website and choose one. There are several ways to install the theme, the recommended one is to use git submodule:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git submodule add https://github.com/some-author/some-theme themes/some-theme
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then point your configuration file at it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;theme&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#39;some-theme&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Most themes ship an &lt;code&gt;exampleSite&lt;/code&gt; folder with a sample configuration - copying the relevant bits into your own &lt;code&gt;hugo.toml&lt;/code&gt; will save you a lot of guessing about which parameters the theme actually reads.&lt;/p&gt;
&lt;h2 id="configuring-the-site"&gt;Configuring the site
&lt;/h2&gt;&lt;p&gt;At a minimum, your &lt;code&gt;hugo.toml&lt;/code&gt; needs a &lt;code&gt;baseURL&lt;/code&gt; (the production URL of your site), a &lt;code&gt;title&lt;/code&gt;, and the &lt;code&gt;theme&lt;/code&gt; name. This is where you also configure what your sidebar/top menu will contain and hook up external systems (e.g. for comments). Here&amp;rsquo;s how hugo.toml for this website begins:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;baseURL&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#39;https://selfhosting.too-many-machines.com/&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;locale&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#39;en&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;title&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#39;Self Hosting&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;theme&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#39;hugo-theme-stack&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;summaryLength&lt;/span&gt; = &lt;span style="color:#ae81ff"&gt;40&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;copyright&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;© Igor Wawrzyniak&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;defaultContentLanguage&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;en&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;enableRobotsTXT&lt;/span&gt; = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;timeZone&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;Europe/Stockholm&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;disableKinds&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;section&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;taxonomy&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;term&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Most variables are theme-specific and live under &lt;code&gt;[params]&lt;/code&gt;. The first time I created a Hugo website, I spent some time reading the docs, next time I simply copied the file and changed what was relevant.&lt;/p&gt;
&lt;h2 id="page-bundles-keeping-content-and-images-together"&gt;Page bundles: keeping content and images together
&lt;/h2&gt;&lt;p&gt;In many older static site setups (Pelican included), your Markdown files live in one directory and your images live in another, and you glue them together with a path like &lt;code&gt;{static}/images/photo.png&lt;/code&gt;. In Hugo, another approach is possible: every page is a directory, containing an &lt;code&gt;index.md&lt;/code&gt; plus all images and other files that the page needs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;content/internet/hugo/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; index.md
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; screenshot.png
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To use the image in a page, use just the filename - &lt;code&gt;![Screenshot](screenshot.png)&lt;/code&gt; - no need to know the final URL. Hugo will take care of it. It will also automatically generate resized versions of your images.&lt;/p&gt;
&lt;p&gt;This kind of bundle (one page, no children) is called a &amp;ldquo;leaf bundle&amp;rdquo;. A &amp;ldquo;branch bundle&amp;rdquo; is the same idea but for a section front page - a directory with an &lt;code&gt;_index.md&lt;/code&gt; that can have further pages underneath it, such as a section listing page.&lt;/p&gt;
&lt;h2 id="adding-your-first-page"&gt;Adding your first page
&lt;/h2&gt;&lt;p&gt;You can create a new page bundle with the command: &lt;code&gt;hugo new content posts/my-first-post index.md&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This creates the file with front matter based on &lt;code&gt;archetypes/default.md&lt;/code&gt;. By default it has &lt;code&gt;draft: true&lt;/code&gt; setting in the front matter so it won&amp;rsquo;t accidentally get published. Open it, write your content in Markdown below the front matter, drop any images into the same directory, and change &lt;code&gt;draft&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; when you&amp;rsquo;re ready to publish.&lt;/p&gt;
&lt;p&gt;You don&amp;rsquo;t have to use this method though. You can just create a directory and write index.md by hand, or copy and modify an existing page, whatever is convenient for you.&lt;/p&gt;
&lt;h2 id="previewing-and-building"&gt;Previewing and building
&lt;/h2&gt;&lt;p&gt;Hugo has a built-in development server that runs on http://localhost:1313/. It rebuilds and refreshes your browser automatically when you save the markdown file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hugo server
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img alt="Screenshot" class="gallery-image" data-flex-basis="412px" data-flex-grow="171" height="835" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/internet/hugo/screenshot.png" srcset="https://selfhosting.too-many-machines.com/internet/hugo/screenshot_hu_eb1f6bf0a2f81b59.png 800w, https://selfhosting.too-many-machines.com/internet/hugo/screenshot.png 1434w" width="1434"&gt;&lt;/p&gt;
&lt;p&gt;When you&amp;rsquo;re done, generate the final static files with a simple &lt;code&gt;hugo&lt;/code&gt; or &lt;code&gt;hugo --minify&lt;/code&gt; to reduce size of CSS and scripts. This writes everything to the &lt;code&gt;public&lt;/code&gt; directory, ready to be uploaded to your web server. You can automate building and uploading the website e.g. using a Continuous Deployment system, but that&amp;rsquo;s a story for some other day.&lt;/p&gt;</description></item><item><title>Sidenote 2 - Ansible fact gathering</title><link>https://selfhosting.too-many-machines.com/general/ansible-facts/</link><pubDate>Fri, 22 Mar 2024 17:40:00 +0100</pubDate><guid>https://selfhosting.too-many-machines.com/general/ansible-facts/</guid><description>&lt;p&gt;If you look closely at the playbook&amp;rsquo;s output, you will see that it always begins with &amp;ldquo;Gathering facts&amp;rdquo;. It takes a few seconds, which annoys many users - especially when you&amp;rsquo;re developing and therefore running the same playbook many times.&lt;/p&gt;
&lt;h2 id="how-does-it-work"&gt;How does it work?
&lt;/h2&gt;&lt;p&gt;Ansible runs a special built-in task at the beginning of each playbook.
Facts are then available as variables. You can print them all with a simple playbook:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Print all available facts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;hosts&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;localhost&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;tasks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Print all facts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible.builtin.debug&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;var&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ansible_facts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or even simpler: &lt;code&gt;ansible &amp;lt;hostname&amp;gt; -m ansible.builtin.setup&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="why"&gt;Why?
&lt;/h2&gt;&lt;p&gt;Ansible can manage Linux, other Unix-like operating systems, Windows, network devices and a plethora of other things. Even if you stick with Linux, there&amp;rsquo;s a huge difference between RHEL 6.0 and Ubuntu 23.10.&lt;/p&gt;
&lt;p&gt;You can use facts like any other variable in your playbook or template. Some facts are more useful than others. For example I often use &lt;em&gt;ansible_distribution_release&lt;/em&gt; when adding apt sources.&lt;/p&gt;
&lt;p&gt;You can even gather and use facts about one system when configuring another system. For example, you can get the IP address from your DB server and use it when configuring a DB client.&lt;/p&gt;
&lt;h2 id="disabling-fact-gathering"&gt;Disabling fact gathering
&lt;/h2&gt;&lt;p&gt;Sometimes you don&amp;rsquo;t need to gather facts. Be careful though, even if you don&amp;rsquo;t use them directly, your Galaxy roles or built-in tasks might rely on them. But there are some cases when you can be 100% sure you won&amp;rsquo;t need the facts. For example, when working with clouds you then specify localhost as your playbook&amp;rsquo;s target, but it&amp;rsquo;s only the host that runs the cloud API client, you don&amp;rsquo;t really care about its configuration. You can then begin your playbook with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Do something on the cloud&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;hosts&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;localhost&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;become&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;gather_facts&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="caching-facts"&gt;Caching facts
&lt;/h2&gt;&lt;p&gt;If you want to make your playbooks run a few seconds faster, there&amp;rsquo;s a better way than disabling fact gathering: fact caching. For more complex setups you can use the DB, but a simple file is usually enough. Add this to your ansible.cfg:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[defaults]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;fact_caching&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;jsonfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;fact_caching_connection&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;$HOME/.ansible/fact-cache&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;fact_caching_timeout&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;3600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;How long should you cache facts? That depends on you and your infrastructure. If it&amp;rsquo;s highly dynamic, the machine can change at any time, but in many places servers stay the same for months. Personally, I use something between a few minutes and one hour.&lt;/p&gt;</description></item><item><title>Backup, part 1: IDrive, backup to the cloud</title><link>https://selfhosting.too-many-machines.com/home/idrive/</link><pubDate>Mon, 20 Nov 2023 17:40:00 +0100</pubDate><guid>https://selfhosting.too-many-machines.com/home/idrive/</guid><description>&lt;p&gt;That is against the spirit of self-hosting, but keeping data safe is more important than keeping purity. Your computers can back up to your NAS (I&amp;rsquo;ll cover that in the next step), that protects you from disk failure, ransomware, accidental deletion and losing your laptop on a train. But a fire or flood can destroy both at the same moment. A proper disaster recovery plan requires 3 copies of data: 1 working copy, 1 on-site backup for quick restore and 1 off-site backup if the previous backup fails. One option is to find another self-hoster and arrange for mutual backup to each other&amp;rsquo;s NAS, another is to use a cloud service.&lt;/p&gt;
&lt;h2 id="cloud-storage-choices"&gt;Cloud storage choices
&lt;/h2&gt;&lt;p&gt;Popular options include Google Drive, Microsoft OneDrive and Dropbox. They are convenient for sharing files, but you can also use them for backup. That is, if the space is large enough for you (you can buy more, but it will cost you) and you trust that your files will be private enough for your needs (best way - encrypt them before sending).&lt;/p&gt;
&lt;p&gt;Another way is to use an object storage such as Amazon S3, Google Cloud Storage, Azure Blob Storage or many S3-compatible offerings. Some providers have a special tier called cold storage. It&amp;rsquo;s much cheaper than the standard tier, but be careful: you also pay for data retrieval. For an archive or backup that you&amp;rsquo;ll probably never need, this might not be a big issue. A big advantage of such a service is that you can use any backup tool that works with cloud storage (Duplicati, Restic and many others).&lt;/p&gt;
&lt;h2 id="idrive"&gt;IDrive
&lt;/h2&gt;&lt;p&gt;Probably the cheapest way to store several TB of data at the time of writing this (November 2023) is IDrive. Their personal plan starts from $99 per year for 5TB, $149 for 10TB, but you can find some affiliate links with a special offer: $4 for the first year. I got mine at &lt;a class="link" href="https://www.tomsguide.com/reviews/idrive-cloud-storage-review" target="_blank" rel="noopener"
 &gt;https://www.tomsguide.com/reviews/idrive-cloud-storage-review&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Downside of IDrive: this service doesn&amp;rsquo;t provide S3-compatible API, REST interface or any other standard way that&amp;rsquo;s supported by various backup programs. You have to use their own client. In the case of Linux, it&amp;rsquo;s a set of scripts. They have limited functionality compared to a Windows version. There&amp;rsquo;s also no way of automating installation and configuration. Good sides? It&amp;rsquo;s cheap, it&amp;rsquo;s reasonably fast, it offers pretty good security (2FA, encrypted storage and transfer), and it works.&lt;/p&gt;
&lt;p&gt;Sending or receiving terabytes of data takes days, even on a fast broadband. IDrive gives an option of using USB drive for a restore or for initial backup. I haven&amp;rsquo;t used this option, so I can&amp;rsquo;t rate it.&lt;/p&gt;
&lt;h2 id="getting-started"&gt;Getting started
&lt;/h2&gt;&lt;p&gt;Sign up for an account. It&amp;rsquo;s also wise to turn on 2FA, preferably with a TOTP app. Now, the Windows and Mac clients are available for download, but for the Linux scripts you need to contact support. I&amp;rsquo;m not sure why, do they need to enable any special options for the account? Anyway, I asked on the live chat and got a download URL in 2 minutes. Beware of IDrive scripts you can find on the internet - I found some, they were so old they failed to connect - and even failed to auto-update.&lt;/p&gt;
&lt;p&gt;Following the documentation at &lt;a class="link" href="https://www.idrive.com/readme" target="_blank" rel="noopener"
 &gt;https://www.idrive.com/readme&lt;/a&gt; I unpacked the client to the directory of my choice. I used /opt and ran the client as root. If you don&amp;rsquo;t need to back up files belonging to different users, you can use a standard account. After unpacking you need to make the scripts executable: &lt;code&gt;chmod a+x *.pl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First script to run is &lt;code&gt;account_setting.pl&lt;/code&gt;. It detected some missing dependencies, tried to install them - some with apt, some using Perl CPAN - and failed. I ran the script again and chose not to install dependencies, but list them instead. I tried manual installation to see what was wrong. The first Perl module tried to create a directory in /usr/local/man, but on my system it wasn&amp;rsquo;t a directory, but a dead symlink. I fixed it, tried automatic installation again - it worked. I then logged in to my account and answered a few questions - default choice should work most of the time.&lt;/p&gt;
&lt;h2 id="first-backup"&gt;First backup
&lt;/h2&gt;&lt;p&gt;Next script to run is &lt;code&gt;edit_supported_files.pl&lt;/code&gt;. It is used for editing backup sets, exclude lists etc. I chose a reasonably sized directory (few GB) for a first test and added more directories later.&lt;/p&gt;
&lt;p&gt;Manual backup is started with &lt;code&gt;Backup_Script.pl&lt;/code&gt;. Remember to run it under cron or tmux if you expect your job to run longer than a few minutes. It shows progress, has an option to pause/resume backup and to set bandwidth limit.&lt;/p&gt;
&lt;p&gt;Make sure you can restore your data. You can use &lt;code&gt;Restore_Script.pl&lt;/code&gt; on your Linux machine or the web interface.&lt;/p&gt;
&lt;p&gt;Last thing to do is to run &lt;code&gt;scheduler.pl&lt;/code&gt; to schedule automatic backup. Choose your frequency (hourly/daily/weekly) and time to start. Once the scheduled job starts, you can watch its progress and pause/resume/throttle using &lt;code&gt;status_retrieval.pl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="IDrive web UI" class="gallery-image" data-flex-basis="509px" data-flex-grow="212" height="670" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/home/idrive/idrive.png" srcset="https://selfhosting.too-many-machines.com/home/idrive/idrive_hu_9ee9e16753c7c6da.png 800w, https://selfhosting.too-many-machines.com/home/idrive/idrive.png 1423w" width="1423"&gt;&lt;/p&gt;</description></item><item><title>Sidenote 1 - Using Ansible</title><link>https://selfhosting.too-many-machines.com/general/ansible/</link><pubDate>Sun, 19 Nov 2023 17:40:00 +0100</pubDate><guid>https://selfhosting.too-many-machines.com/general/ansible/</guid><description>&lt;p&gt;I use Ansible to configure my home computers. I started doing it to learn the tool, but is it really worth using it at home other than for education? Maybe.&lt;/p&gt;
&lt;p&gt;On the downside, it&amp;rsquo;s generally faster to configure a server manually. The syntax is quite awkward for
beginners. If you need to change a few lines in a config file, you can do it in 30 seconds with a text editor or spend an hour retrying the playbook. But it can save you time as well if you&amp;rsquo;re going to repeat the task.&lt;/p&gt;
&lt;p&gt;There are several ways to use Ansible. One is to automate ad-hoc tasks, such as patching, gathering some information, repairing common problems. It&amp;rsquo;s quite usual if you&amp;rsquo;re introducing Ansible into a pre-existing infrastructure.&lt;/p&gt;
&lt;p&gt;But Ansible really shines if you use the Infrastructure as a Code approach. That is, every server (or group of identical servers) has one playbook containing the whole configuration: software, user accounts etc. Parts of the playbook that are the same on different servers can be shared (see below about roles). And if you ever need to rebuild your server, you can do it with one command. It happened 3 times with my NAS (I experimented with different hardware). For more experimental devices or VMs, you can intentionally rebuild them often.&lt;/p&gt;
&lt;p&gt;Proper Ansible playbooks are idempotent, meaning they can be run multiple times. Ansible will check the state of the server and skip tasks that wouldn&amp;rsquo;t change anything.&lt;/p&gt;
&lt;h2 id="think-what-should-be-automated"&gt;Think what should be automated
&lt;/h2&gt;&lt;p&gt;Since it&amp;rsquo;s a home network, I didn&amp;rsquo;t do several things I would do in an enterprise setting. The most important omission is provisioning. I just started from the base system already installed. Provisioning can be automated, but I decided not to do it. I&amp;rsquo;ve got different types of machines: Raspberry Pis, PCs, VMs, VPS. Each needs to be provisioned in a different way: PXE, writing image to the card, cloning base image or using Terraform. That would mean lots of work for not much use (usually once per machine) and not much educational value (I know how to do it). My setup does 80% of the job with 20% of the effort.&lt;/p&gt;
&lt;p&gt;Sometimes one action you do with Ansible changes the way you interact with the server later. Example: I change SSH port on all my servers. That means I have to login with a default port on the first run, but on each subsequent run I need to use my custom port. Solution? I just manually change the port in the Ansible inventory. If I had a highly dynamic infrastructure with new machines coming every day, that wouldn&amp;rsquo;t do, but in my case it only happens once per machine.&lt;/p&gt;
&lt;h2 id="using-linter"&gt;Using linter
&lt;/h2&gt;&lt;p&gt;Playbook syntax can be hard in the beginning. A wrong indentation can turn it into an incorrect YAML. Even worse, there are some real gotchas, for example if you don&amp;rsquo;t put quotes around numeric file permissions, they might end up other than expected. A tool called ansible-lint checks all these and enforces some best practices.&lt;/p&gt;
&lt;p&gt;In fact, the linter is really picky and you might have to ignore some rules. I&amp;rsquo;ve got this in my .ansible-lint:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;skip_list&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;yaml[line-length]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;package-latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which means I allow long lines (linter thinks you should split them, I disagree) and installing latest version of software instead of a specific version.&lt;/p&gt;
&lt;p&gt;I started using Ansible several years ago. In the meantime, the idea of how an Ansible playbook should look changed a lot. Running ansible-lint revealed many problems with my code. For example, it is recommended to use fully-qualified collection names even for built-in commands (e.g. ansible.builtin.file instead of file).&lt;/p&gt;
&lt;h2 id="using-roles"&gt;Using roles
&lt;/h2&gt;&lt;p&gt;Roles are reusable pieces of configuration, which make my playbooks really short. There are some things I want to do with all Linux machines on my network:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;create a user account and install SSH key,&lt;/li&gt;
&lt;li&gt;configure SSH server the way I like it,&lt;/li&gt;
&lt;li&gt;upgrade packages&lt;/li&gt;
&lt;li&gt;install some packages that I use everywhere.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which is why I prepared a role &amp;ldquo;linux_common&amp;rdquo;. Then there&amp;rsquo;s role &amp;ldquo;linux_dev&amp;rdquo; that installs code editors, C++ compiler and some tools (I mostly code in Python, but it is already included in the base system). I want it on my laptop and on my desktop, but not on the server. Similar with the office and multimedia software included in the role &amp;ldquo;linux_desktop&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;My home server Firefly gets many roles. There&amp;rsquo;s one called firefly which contains settings specific to this machine, but there are also roles for Jellyfin and Prometheus. In future I might decide to move them elsewhere, so I&amp;rsquo;ll remove those roles from Firefly and include them in another machine&amp;rsquo;s playbook.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ansible role directory tree" class="gallery-image" data-flex-basis="166px" data-flex-grow="69" height="499" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/general/ansible/ansible-role.png" width="346"&gt;&lt;/p&gt;
&lt;p&gt;Ansible uses some conventions about directory structure and file naming. You can ignore them, but adhering to them will make life easier for everyone. This is an example for a moderately complex role.&lt;/p&gt;
&lt;p&gt;The most important directory is &amp;ldquo;tasks&amp;rdquo;, it stores YAML files with all task definitions. Handlers are special kind of tasks - they are run after all the standard tasks if a specific condition was met. For example, you can restart your service if its binary has changed or if the configuration file was modified. If both events happened, Ansible is smart enough to only run the handler once. In both of these directories, only file &amp;ldquo;main.yml&amp;rdquo; is read automatically. But if it gets too long, you can import other files from it.&lt;/p&gt;
&lt;p&gt;Directory &amp;ldquo;defaults&amp;rdquo; stores default values for variables, which are overwritten by custom values from &amp;ldquo;vars&amp;rdquo;. Last but not least, &amp;ldquo;files&amp;rdquo; contains all files that will be copied to the server without any change, those from &amp;ldquo;templates&amp;rdquo; are filled with some generated content first.&lt;/p&gt;
&lt;h2 id="using-galaxy"&gt;Using Galaxy
&lt;/h2&gt;&lt;p&gt;You can install roles prepared by others using Ansible Galaxy. They often save you hours of writing and testing playbooks. Some of them are so well-prepared that you can use them without changing the code, only modifying the configuration. Others though are of lesser quality, or your infrastructure is so specific that a generic role can&amp;rsquo;t solve all of your problems. In that case, you can just edit them like you would edit your own role.&lt;/p&gt;</description></item><item><title>Prometheus and Grafana, monitoring - part 2</title><link>https://selfhosting.too-many-machines.com/home/prometheus-2/</link><pubDate>Fri, 17 Nov 2023 17:40:00 +0100</pubDate><guid>https://selfhosting.too-many-machines.com/home/prometheus-2/</guid><description>&lt;p&gt;For the reference, here are the default ports of various components of Prometheus/Grafana stack:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3000 Grafana&lt;/li&gt;
&lt;li&gt;9090 Prometheus&lt;/li&gt;
&lt;li&gt;9100 Prometheus Node Exporter&lt;/li&gt;
&lt;li&gt;9116 Prometheus SNMP Exporter&lt;/li&gt;
&lt;li&gt;9093 Prometheus Alertmanager&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="generating-node-list-for-prometheus"&gt;Generating node list for Prometheus
&lt;/h2&gt;&lt;p&gt;In the default config file there are already two exporters configured: Prometheus for localhost and node exporter also for localhost. Yes, Prometheus server also exposes some metrics about itself and then consumes them. The first item is OK, I only have one Prometheus instance, but I want to monitor all Linux hosts on my home network. Let&amp;rsquo;s put the default config file in the role&amp;rsquo;s templates subdirectory and add a .j2 extension (so the full name is prometheus.yml.j2). Now just change a few lines at the end of the file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;job_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;node&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# If prometheus-node-exporter is installed, grab stats about the local&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# machine by default.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;static_configs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;targets&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#ae81ff"&gt;% for host in groups[&amp;#39;linux&amp;#39;] %}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#39;{{ host }}:9100&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#ae81ff"&gt;% endfor %}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;My Ansible inventory contains a group called linux, with all Ansible-managed Linux machines. Now, to generate the config file, change tasks/main.yml by adding these lines right after package installation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Generate config for Prometheus&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible.builtin.template&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;dest&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/etc/prometheus/prometheus.yml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;src&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;prometheus.yml.j2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;notify&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;Restart Prometheus&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;re now only missing the restart handler - a task that will run only when &amp;ldquo;Generate config for Prometheus&amp;rdquo; changes anything. Add handlers/main.yml:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Restart Prometheus&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible.builtin.service&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;prometheus&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;restarted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="configuring-grafana"&gt;Configuring Grafana
&lt;/h2&gt;&lt;p&gt;Grafana is a visual tool. You can programmatically generate the config (it&amp;rsquo;s all YAML) if you know what you want to achieve, but if you&amp;rsquo;re exploring, GUI is a better option. Login to Grafana and click on &amp;ldquo;Add data source&amp;rdquo;. Select &amp;ldquo;Prometheus&amp;rdquo; as the type, then enter the hostname (either host.docker.internal or a real external hostname, localhost won&amp;rsquo;t work) with the port (9090 is the default). Select &amp;ldquo;No authentication&amp;rdquo; as we didn&amp;rsquo;t configure any. Grafana will confirm it can read metrics from Prometheus.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s get the graphs already. There are two options. You can create your own dashboard by manually adding graphs of various types and entering PromQL queries. Or you can import an existing dashboard from Grafana. The first way is useful if you need a custom dashboard to quickly see selected metrics from different sources. If you want a complete view of each node, importing a high-quality existing dashboard will save you hours. I chose &amp;ldquo;Node exporter full&amp;rdquo;, a template so famous that many template designers compare their work to it, referring to it by id (&amp;ldquo;My template is similar to 1860, but&amp;hellip;&amp;rdquo;) and &amp;ldquo;Prometheus 2.0 Overview&amp;rdquo;, number 3662.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Grafana node dashboard" class="gallery-image" data-flex-basis="625px" data-flex-grow="260" height="729" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/home/prometheus-2/grafana-node.png" srcset="https://selfhosting.too-many-machines.com/home/prometheus-2/grafana-node_hu_f7131dc590c2b9b.png 800w, https://selfhosting.too-many-machines.com/home/prometheus-2/grafana-node_hu_7673ab6c65e6def9.png 1600w, https://selfhosting.too-many-machines.com/home/prometheus-2/grafana-node.png 1900w" width="1900"&gt;&lt;/p&gt;
&lt;h2 id="where-to-go-from-here"&gt;Where to go from here
&lt;/h2&gt;&lt;p&gt;We barely scratched the surface. Prometheus is widely used for gathering all sorts of metrics, from low-level system stats like we collect here, through HTTP/DB/other server stats, up to business metrics (e.g. click-through rate or order value from an e-commerce app) or industrial data. Grafana can plot all of these, plus it can use data sources other than Prometheus - next by popularity is probably Loki, a log aggregation system.&lt;/p&gt;
&lt;p&gt;Then there&amp;rsquo;s alerting. Is it useful for a home network? Definitely not as much as in the enterprise, some of your hosts may be down most of the time (e.g. a laptop), but notification about low disk space on your NAS or a failed backup might be nice.&lt;/p&gt;</description></item><item><title>Prometheus and Grafana, monitoring - part 1</title><link>https://selfhosting.too-many-machines.com/home/prometheus-1/</link><pubDate>Thu, 16 Nov 2023 17:40:00 +0100</pubDate><guid>https://selfhosting.too-many-machines.com/home/prometheus-1/</guid><description>&lt;p&gt;As the infrastructure grows, the need for monitoring becomes more apparent.
Servers fail, and when they do, you want to be notified and you want to have
information for debugging.&lt;/p&gt;
&lt;p&gt;Every decent monitoring system is modular. It can gather metrics from a wide variety of hardware and software, it can discover new services in any environment (e.g. LAN, Kubernetes, AWS), it can send alerts in many different ways, it can interface with a ticketing system. It can form a hierarchy, where low-level systems gather all data about their part of the infrastructure and send only summary statistics to the higher level. Most of these options are not needed at home, but it&amp;rsquo;s still worth choosing one of the popular and comprehensive systems. First, because there&amp;rsquo;s a high chance you&amp;rsquo;ll use it later for your work. Second, because a popular monitoring system will likely have plugins for everything you might imagine. Some systems, despite being very advanced, are not particularly overwhelming - you can just focus on the core system and easily ignore the modules you&amp;rsquo;re not using.&lt;/p&gt;
&lt;h2 id="choosing-a-monitoring-software"&gt;Choosing a monitoring software
&lt;/h2&gt;&lt;p&gt;Some of the popular monitoring systems are surprisingly old. Nagios, created in 2002, is still widely used - mostly because many admins find it already installed and working just fine. It has a huge library of community plugins, including very rare devices not available elsewhere, that&amp;rsquo;s why many later monitoring systems can use Nagios plugins (with a varying amount of success).&lt;/p&gt;
&lt;p&gt;Other somewhat popular and equally old (+/- 2 years) software includes Zabbix, OpenNMS and Cacti. They have some strong points too, e.g. Cacti makes it very easy to monitor network devices. But I wouldn&amp;rsquo;t recommend any of these for a new installation. Infrastructure today is very different than in the early
2000s and we need to monitor containers, web services, ML platforms and other things that weren&amp;rsquo;t even invented back then.&lt;/p&gt;
&lt;h2 id="why-prometheus"&gt;Why Prometheus
&lt;/h2&gt;&lt;p&gt;Prometheus was created in 2012, when cloud infrastructure was already similar to what we have today. It has all the standard features of a modern cloud-native software:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;all network communication is HTTP pull,&lt;/li&gt;
&lt;li&gt;written in Go,&lt;/li&gt;
&lt;li&gt;configuration in YAML,&lt;/li&gt;
&lt;li&gt;multiple independent instances can be used to reduce the risk of failure.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A data source for Prometheus is called an exporter. There&amp;rsquo;s a huge range of official exporters to choose from and even more written by the community, some popular ones include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;node exporter, gives standard hardware and system statistics (CPU, memory, disk, load avg etc.) about a Linux host,&lt;/li&gt;
&lt;li&gt;SNMP exporter for monitoring network devices,&lt;/li&gt;
&lt;li&gt;exporters for server hardware, sensors, GPUs,&lt;/li&gt;
&lt;li&gt;exporters for HTTP servers and proxies which provide statistics such as latency, number of 4xx/5xx errors and many others,&lt;/li&gt;
&lt;li&gt;exporters for databases,&lt;/li&gt;
&lt;li&gt;exporters for cloud services (AWS, Azure, DigitalOcean, GitHub&amp;hellip;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even better, Prometheus became so hugely popular that a lot of cloud infrastructure software (e.g. Docker and Kubernetes) can directly expose metrics in a Prometheus format.&lt;/p&gt;
&lt;p&gt;Prometheus collects metrics from the configured (or auto-discovered) exporters. Then it can use another module called AlertManager to notify about an urgent problem. An important feature is inhibition of alerts. Have you ever received hundreds of notifications when one network error caused many checks to fail? Prometheus can be configured to prevent that.&lt;/p&gt;
&lt;p&gt;Prometheus has a web interface (obviously). You can use it to write ad-hoc queries against the stored metrics using PromQL, a specially designed and easy to learn query language. You can also view graphs, but it&amp;rsquo;s not really recommended - Grafana, a visual dashboard which can get data from Prometheus, provides a much better experience.&lt;/p&gt;
&lt;h2 id="installing-prometheus-with-ansible"&gt;Installing Prometheus with Ansible
&lt;/h2&gt;&lt;p&gt;There are several ways to install Prometheus. You can use a package from your distribution, you can run it in a container or get the latest binary from the project&amp;rsquo;s website. Debian and Ubuntu, my distros of choice, have Prometheus packages available. Of course, I&amp;rsquo;ll use Ansible.&lt;/p&gt;
&lt;p&gt;But wait, there will be only one Prometheus/Grafana server, but every Linux system in my home network should run a node exporter. I already have a role &lt;strong&gt;linux_common&lt;/strong&gt; which, among other things, installs a few packages I like to have on all my machines. Let&amp;rsquo;s add one thing to the list: &lt;code&gt;sys_packages: [ 'curl', 'vim', 'git', 'screen', 'mc', 'lsof', 'aptitude', 'rsync', 'gpg', 'prometheus-node-exporter' ]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to run Prometheus on Firefly, my NAS. Not much choice, I only have two always-on systems: one for DNS/DHCP and one for everything else. But it might change in future, so I&amp;rsquo;ll prepare a role &amp;ldquo;prometheus_server&amp;rdquo; and firefly will include this role.&lt;/p&gt;
&lt;p&gt;First I&amp;rsquo;m installing packages: Prometheus core, alert manager, and SNMP exporter for monitoring network hardware.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Install prometheus packages&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible.builtin.apt&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;name=[&amp;#39;prometheus&amp;#39;, &amp;#39;prometheus-snmp-exporter&amp;#39;, &amp;#39;prometheus-alertmanager&amp;#39;] state=latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s point the browser to &lt;a class="link" href="http://firefly.home.arpa:9090" target="_blank" rel="noopener"
 &gt;http://firefly.home.arpa:9090&lt;/a&gt; - it works!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Prometheus GUI right after the installation" class="gallery-image" data-flex-basis="207px" data-flex-grow="86" height="763" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/home/prometheus-1/empty-prometheus.png" width="660"&gt;&lt;/p&gt;
&lt;h2 id="installing-grafana-with-ansible"&gt;Installing Grafana with Ansible
&lt;/h2&gt;&lt;p&gt;Grafana is a bit more complex, as there&amp;rsquo;s no official Debian package. I decided to use a Docker container. Here&amp;rsquo;s a Docker Compose file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;3.5&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;grafana&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;grafana/grafana-oss:latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;grafana&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;TZ=Europe/London&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#39;3000:3000&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;extra_hosts&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;host.docker.internal:host-gateway&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;always&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/data/noshare/grafana:/var/lib/grafana&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is fairly standard, except for extra_hosts directive. Grafana in a container wouldn&amp;rsquo;t connect to Prometheus on the host. That is, it would connect with a real (not 127.0.0.1) IP, provided that Prometheus listens on something else than a loopback interface, treating it like any other external server. Usually, the whole point of using Docker is to have some isolation. But it&amp;rsquo;s a home server, I&amp;rsquo;m OK with mixing container-based and host-based workloads. After adding those two lines we can connect from the container to services running on the host using a special name &amp;ldquo;host.docker.internal&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;If we don&amp;rsquo;t configure any volume, Grafana will store data inside the container - meaning it will lose it when the container is stopped. We can either create a Docker-managed volume, or bind-mount a specific directory. I chose the second option since I want to keep the data on a specific disk. Before we start the container, we need to make sure the directory exists and has proper access rights - Grafana runs as a user with UID 472. Such a user doesn&amp;rsquo;t exist on my system, but that&amp;rsquo;s not a problem. Here&amp;rsquo;s how the complete Prometheus/Grafana role looks:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Install prometheus packages&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible.builtin.apt&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#39;prometheus&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;prometheus-snmp-exporter&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;prometheus-alertmanager&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;present&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Chown directory for Grafana data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible.builtin.file&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;path&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/data/noshare/grafana&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;owner&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;472&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;recurse&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;copy Grafana docker-compose.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible.builtin.copy&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;src&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;files/docker-compose-grafana.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;dest&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/home/{{ create_user }}/compose/docker-compose-grafana.yml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;owner&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;{{ create_user }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;start containers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;community.docker.docker_compose&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;project_src&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/home/{{ create_user }}/compose/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;present&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;files&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;docker-compose-grafana.yml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;register&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;print compose output&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible.builtin.debug&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;var&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;verbosity&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Run the playbook: &lt;code&gt;ansible-playbook firefly.yml --start-at-task &amp;quot;Install prometheus packages&amp;quot;&lt;/code&gt; and check if the container is running: &lt;code&gt;docker ps&lt;/code&gt;. Then, point the browser to &lt;a class="link" href="http://firefly.home.arpa:3000" target="_blank" rel="noopener"
 &gt;http://firefly.home.arpa:3000&lt;/a&gt; - if you don&amp;rsquo;t see anything, wait a moment, Grafana needs to initialise its database on the first start.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Grafana GUI right after the installation" class="gallery-image" data-flex-basis="494px" data-flex-grow="206" height="670" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/home/prometheus-1/empty-grafana.png" srcset="https://selfhosting.too-many-machines.com/home/prometheus-1/empty-grafana_hu_b1363530ca2d13d5.png 800w, https://selfhosting.too-many-machines.com/home/prometheus-1/empty-grafana.png 1381w" width="1381"&gt;&lt;/p&gt;</description></item><item><title>Syncthing, synchronising files between the devices</title><link>https://selfhosting.too-many-machines.com/home/syncthing/</link><pubDate>Wed, 10 May 2023 17:40:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/home/syncthing/</guid><description>&lt;p&gt;Sharing files between your devices using a cloud service such as Google Drive or Dropbox is incredibly convenient.
Wouldn&amp;rsquo;t it be nice to do the same locally, with complete privacy and without using your internet connection? Sure it would.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Syncthing&lt;/strong&gt; works on Linux, Windows, macOS, FreeBSD and Android. There are also packages for commercial NASes from Synology,
QNAP, WD and others. It doesn&amp;rsquo;t require a server - you can directly sync two computers, or a computer with a phone,
or 3 devices can all sync with each other. That&amp;rsquo;s what I did before I got the NAS. But if you have a home server which is
always on, it makes sense to treat it as a central point and have all other devices synchronise with it.&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation
&lt;/h2&gt;&lt;p&gt;On my Linux laptops and desktop, I just installed the package and set syncthing to autostart using my
user account (no need to run it as root and a dedicated account wouldn&amp;rsquo;t have access to my files):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;apt install syncthing
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl enable syncthing@igor
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl start syncthing@igor
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On the NAS, it was similar, but I used Ansible. This is the task file I added to the &lt;em&gt;firefly&lt;/em&gt; role:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Install syncthing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;apt&lt;/span&gt;: 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#39;syncthing&amp;#39;&lt;/span&gt;] 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;present&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Create directory for syncthing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible.builtin.file&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;path&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/data/noshare/syncthing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;owner&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;igor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Restart syncthing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;service&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;syncthing@igor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;restarted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I could run syncthing in a Docker container, but I don&amp;rsquo;t see much value in it. It&amp;rsquo;s just one package,
data is all contained in one place and an up-to-date package is widely available for Linux distros.&lt;/p&gt;
&lt;p&gt;On Windows I installed the SyncTrayzor package, which includes Syncthing and a system tray widget. Finally,
on Android it&amp;rsquo;s just installing the package from either Play Store or F-Droid.&lt;/p&gt;
&lt;h2 id="configuration"&gt;Configuration
&lt;/h2&gt;&lt;p&gt;Syncthing isn&amp;rsquo;t really well suited for automated configuration. It stores its settings in ~/.config/syncthing/config.xml.
You could generate this file if you really wanted to, but a more sane option is to configure using the web GUI and then
back up this file so you can recreate the setup if needed. For configuring your computer, just point your browser to
&lt;a class="link" href="http://localhost:8384" target="_blank" rel="noopener"
 &gt;http://localhost:8384&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a bit more tricky with the NAS. By default, syncthing only listens on the local interface. You can change this
option in your config file or, since it&amp;rsquo;s only needed for a moment and the default is better for security, you can
set up an SSH tunnel. On my laptop, I typed &lt;code&gt;ssh -L 8385:localhost:8384 firefly&lt;/code&gt; and pointed the browser to &lt;a class="link" href="http://localhost:8385" target="_blank" rel="noopener"
 &gt;http://localhost:8385&lt;/a&gt;.
How does the tunnel work? It means that ssh will forward local port 8385 through firefly to localhost:8384 - but that&amp;rsquo;s localhost
from the firefly&amp;rsquo;s point of view. Note that I can open the tunnel &lt;em&gt;through&lt;/em&gt; firefly &lt;em&gt;to&lt;/em&gt; another server, sysadmins often use this
technique to connect to servers using IPs through one jumphost available on the internet.&lt;/p&gt;
&lt;p&gt;Back to the point. In one of your browser tabs, click &amp;ldquo;Add Folder&amp;rdquo; and choose what you want to share. In my case, I&amp;rsquo;m sharing
~/CloudStation between all my computers. The folder name is due to historical reasons, a few years ago I used Synology CloudStation
for the same purpose. Don&amp;rsquo;t share your whole home directory as some config files are system-specific.&lt;/p&gt;
&lt;p&gt;Next, click &amp;ldquo;Add Remote Device&amp;rdquo;. You can then paste the ID of another device (or scan the QR code) or very likely, you&amp;rsquo;ll
see a list of other devices on the network. Remember to type a name that will be displayed in the GUI instead of the ID. Then,
on your other machines, confirm you want to pair with this device. A moment later you&amp;rsquo;ll see a message: this device wants to
share a folder. Click to confirm. Note that the path of the shared folder doesn&amp;rsquo;t need to be the same, on my computers I use
~/CloudStation, but on the NAS I chose /data/noshare/syncthing/CloudStation.&lt;/p&gt;
&lt;h2 id="syncing-the-phone"&gt;Syncing the phone
&lt;/h2&gt;&lt;p&gt;There are some things I like to sync between my computers - code snippets, financial stuff, home related documents - that I
would never access on the phone. I decided to share only a subset of files, inside my ~/CloudStation folder, I have
~/CloudStation/na-telefon (which translates as &amp;ldquo;to the phone&amp;rdquo;). Syncthing warns that one shared folder is contained
within the other, but allows such setup.&lt;/p&gt;
&lt;p&gt;One other use for syncthing is to &lt;strong&gt;auto upload my photos&lt;/strong&gt; from my phone to Elsa, my graphics workstation. On the
phone, I chose to sync the camera folder with Elsa, but there&amp;rsquo;s one twist: the synchronisation is only in one
direction. The phone will send files, I can view them on the computer and delete those I don&amp;rsquo;t need, the changes won&amp;rsquo;t be
synced back.&lt;/p&gt;
&lt;h2 id="dealing-with-conflicts"&gt;Dealing with conflicts
&lt;/h2&gt;&lt;p&gt;Most of the time, nothing will stop you from modifying the same file on multiple devices at once. Some applications
create lock files, those warn about opening the same file more than once and often suggest opening it read-only.
Depending on your workflow, this might be a huge problem (&amp;ldquo;I&amp;rsquo;m getting conflicts all the time&amp;rdquo;), none at all
(&amp;ldquo;I&amp;rsquo;m always editing this file on this computer anyway, others only read it&amp;rdquo;) or something in between.&lt;/p&gt;
&lt;p&gt;When you do get a conflict, syncthing will keep both versions of the file, adding &amp;ldquo;syncconflict&amp;rdquo; plus timestamp
and device ID. It is then up to you - and your applications - to resolve it. For example, KeePassXC will merge
two DBs without any issues, text files (source code etc.) can be compared with diff, vimdiff or similar tools.&lt;/p&gt;</description></item><item><title>Jellyfin, a streaming server for home</title><link>https://selfhosting.too-many-machines.com/home/jellyfin/</link><pubDate>Fri, 14 Apr 2023 17:40:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/home/jellyfin/</guid><description>&lt;p&gt;We made a lot of hard work in the previous steps, time for relax. My NAS holds, in addition to other things, a small collection
of videos. Mostly some old movies and TV shows not available in streaming services or my favourites that I want to keep no
matter what. The PCs can easily play video files from an SMB share using VLC or another player, but mobile devices, smart TVs
etc. need streaming. Plus, commercial streaming services made us used to features like easy searching, categories, recommendations,
resuming on another device. Fortunately it&amp;rsquo;s easy to run your own streaming server.&lt;/p&gt;
&lt;p&gt;There are two main contenders in this field in addition to a plethora of less popular solutions. &lt;strong&gt;Plex&lt;/strong&gt; is a well-established
one with 15 years of history. Recently many users are annoyed by the GUI changes, mostly done to promote &lt;em&gt;Plex Pass&lt;/em&gt;, an optional
subscription service extending features of Plex. &lt;strong&gt;Jellyfin&lt;/strong&gt; is a fully open-source alternative that doesn&amp;rsquo;t include any
subscriptions or outside components, it&amp;rsquo;s fully self-contained. It has fewer plugins and clients than its main competitor, but
it&amp;rsquo;s growing rapidly and already supports most platforms.&lt;/p&gt;
&lt;p&gt;Jellyfin has a client-server architecture, meaning you install server software that reads your media files and streams them to
the clients, and client software on the devices you want to use for viewing. There are clients for Android, iOS, popular smart TVs
and digital media players, there&amp;rsquo;s also a web client. Installing a client is a completely no-frills experience, just install it
from an app store, open it and it will automatically detect the server on the LAN.&lt;/p&gt;
&lt;h2 id="installing-the-server"&gt;Installing the server
&lt;/h2&gt;&lt;p&gt;There are several ways to install Jellyfin server, I chose the simplest for my setup: run it in a Docker container on my NAS.
That way, Jellyfin doesn&amp;rsquo;t need to access media files using the network, I can bind mount media directories inside the container.
I used an image from &lt;a class="link" href="https://docs.linuxserver.io/images/docker-jellyfin" target="_blank" rel="noopener"
 &gt;https://docs.linuxserver.io/images/docker-jellyfin&lt;/a&gt; and a very simple Ansible role.&lt;/p&gt;
&lt;p&gt;Here is the task:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Create directory for docker-compose.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible.builtin.file&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;path&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/home/{{ create_user }}/compose&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;owner&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;{{ create_user }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;copy docker-compose.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible.builtin.copy&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;src&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;files/docker-compose.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;dest&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/home/{{ create_user }}/compose/docker-compose.yml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;owner&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;{{ create_user }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;start containers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;community.docker.docker_compose&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;project_src&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/home/{{ create_user }}/compose/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;present&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;register&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;print compose output&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible.builtin.debug&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;var&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;verbosity&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the main part is a Docker Compose file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;3.5&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;jellyfin&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;lscr.io/linuxserver/jellyfin:latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jellyfin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	 - &lt;span style="color:#ae81ff"&gt;PUID=1001&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	 - &lt;span style="color:#ae81ff"&gt;PGID=1002&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	 - &lt;span style="color:#ae81ff"&gt;TZ=Europe/London&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#f92672"&gt;network_mode&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;host&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	 - &lt;span style="color:#ae81ff"&gt;/data/noshare/jellyfin/config:/config&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	 - &lt;span style="color:#ae81ff"&gt;/data/noshare/jellyfin/cache:/cache&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	 - &lt;span style="color:#ae81ff"&gt;/data/filmy:/media1:ro&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	 - &lt;span style="color:#ae81ff"&gt;/data/video2:/media2:ro&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	 - &lt;span style="color:#ae81ff"&gt;/data/seriale1:/media3:ro&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	 - &lt;span style="color:#ae81ff"&gt;/data/seriale2:/media4:ro&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;unless-stopped&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And that&amp;rsquo;s it. The things that need customisation are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PUID and PGID - the user and group that owns the media files,&lt;/li&gt;
&lt;li&gt;TZ is my timezone&lt;/li&gt;
&lt;li&gt;Volumes: first two are for config and cache. I have a hard drive mounted at /data/noshare which, as the name suggests, is not shared by the NAS. The rest are media directories, the same ones I also share using SMB. Naming here can be anything you want, in hindsight I should have used something more descriptive than media1, media2 etc. but it&amp;rsquo;s not a big deal.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="configuring-jellyfin-server"&gt;Configuring Jellyfin server
&lt;/h2&gt;&lt;p&gt;Point your browser to port 8096 of your server, in my case it&amp;rsquo;s &lt;a class="link" href="http://firefly.home.arpa:8096" target="_blank" rel="noopener"
 &gt;http://firefly.home.arpa:8096&lt;/a&gt; and go through the settings. First you need to create an admin account. Next important thing is to add media sources, it&amp;rsquo;s not enough to make the volumes available to the container. It is recommended that a media source contains movies, TV shows, music etc. but not a mixture of different media types. Note that media sources in the Jellyfin GUI don&amp;rsquo;t need to be the same as volumes in the container:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can have one volume split into multiple data sources, e.g. a volume called /videos containing directories /videos/movies and /videos/shows&lt;/li&gt;
&lt;li&gt;or the other way around, you can have volumes /movies1 and /movies2 combined into one data source.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are some options specifying how should Jellyfin check for changes, where to get metadata etc. - usually you can leave the defaults. Scanning the data source will take between a few minutes and a few hours but the media will start appearing immediately.&lt;/p&gt;
&lt;p&gt;In the beginning I wrote that Jellyfin doesn&amp;rsquo;t need online services. Well, it was a small lie. When it detects new contents, it will download metadata such as cover image, movie description, actor list etc. It&amp;rsquo;s optional, but on by default and uses free data sources.&lt;/p&gt;
&lt;h2 id="more-configuration"&gt;More configuration
&lt;/h2&gt;&lt;p&gt;You can create multiple user accounts. They can have limited access to the data sources, e.g. child account might only have access to child-friendly shows (or vice versa, adult account without access to cartoons).&lt;/p&gt;
&lt;p&gt;Transcoding options have a huge impact on CPU usage. First, use hardware acceleration if possible. This setting is available on the server. See Jellyfin manual for details, unfortunately my hardware doesn&amp;rsquo;t support acceleration so I can&amp;rsquo;t speak from experience. I also have a rather weak CPU. More than adequate for all other purposes, in fact it rarely goes above 1% usage, but it&amp;rsquo;s sometimes too slow for transcoding. If you find yourself in my place, you need to tune the transcoding settings on each client (not the server):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;on Android, choose &amp;ldquo;integrated player&amp;rdquo; instead of &amp;ldquo;web player&amp;rdquo; in &amp;ldquo;client settings&amp;rdquo; - it supports more codecs, meaning it will do more processing on the client instead of the server&lt;/li&gt;
&lt;li&gt;in &amp;ldquo;Playback&amp;rdquo;, choose option to prefer fMP4 Media Container - again, it means more direct playback, less transcoding.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Storage server, part 4: putting it all together</title><link>https://selfhosting.too-many-machines.com/home/nas-4/</link><pubDate>Fri, 14 Apr 2023 17:38:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/home/nas-4/</guid><description>&lt;p&gt;(partly based on old post from 2023.04.14, rewritten on 2026.07.09)&lt;/p&gt;
&lt;h2 id="choosing-the-os"&gt;Choosing the OS
&lt;/h2&gt;&lt;p&gt;There are some distributions of Linux or FreeBSD specially designed for NAS use. Or you can install ordinary Linux (or Windows, I won&amp;rsquo;t judge). NAS distribution simplifies management, you get something similar to a Synology or QNAP device. But you need to play by their rules, while ordinary Linux can be customised any way you like.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t have a problem configuring Linux servers without a GUI and I wanted a place for experiments in addition to plain NAS. Plus, I already used a vanilla system (Debian, stable release) on my previous attempts to run a NAS. I configured it with Ansible. I was able to reuse the playbook for my 3rd NAS. That&amp;rsquo;s the nice thing about Ansible: configuring my NAS for the first time took me more time than doing the same thing by hand, but the next attempts were almost fully automated.&lt;/p&gt;
&lt;h3 id="not-for-everyone"&gt;Not for everyone
&lt;/h3&gt;&lt;p&gt;I don&amp;rsquo;t necessarily recommend everyone to follow these steps. I&amp;rsquo;m OK if my NAS is a bit difficult to configure, if it means I&amp;rsquo;m honing skills I need for my job. I don&amp;rsquo;t go as far as having a dedicated homelab, I just mix the two uses for home servers.&lt;/p&gt;
&lt;p&gt;In particular, Ansible doesn&amp;rsquo;t make sense for a server that you&amp;rsquo;re not going to rebuild. Unless you want to write a playbook because you haven&amp;rsquo;t done it at your day job recently and you don&amp;rsquo;t want your skills to deteriorate. Despite my enthusiasm for Ansible, I advise everyone to skip it, unless they have a good reason.&lt;/p&gt;
&lt;h3 id="base-installation"&gt;Base installation
&lt;/h3&gt;&lt;p&gt;In my case, that was just a simple Debian install. Write the ISO to a thumbdrive, plug it into the NAS, connect screen and keyboard for the initial setup. Make sure the installer writes to the system drive only and doesn&amp;rsquo;t touch the data HDDs at all (you can disconnect them to be sure).&lt;/p&gt;
&lt;p&gt;Installing the base system and configuring the hard drives was the manual part. It could be automated as well, but it doesn&amp;rsquo;t make much sense to automate a one-time task.&lt;/p&gt;
&lt;h2 id="formatting-the-drives"&gt;Formatting the drives
&lt;/h2&gt;&lt;h3 id="main-data-drives"&gt;Main data drives
&lt;/h3&gt;&lt;p&gt;Btrfs RAID1 on top of two LUKS-encrypted HDDs. I created 2 subvolumes, they got mounted at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;/data/noshare - stuff that won&amp;rsquo;t be shared by SMB or NFS, mostly space for services running on the NAS (Syncthing, Jellyfin, Prometheus, photo archive)&lt;/li&gt;
&lt;li&gt;/data/other - stuff (other than videos) that&amp;rsquo;s shared over LAN - my music and audiobook collection, old projects, files copied from old computers that I&amp;rsquo;m going to sort in mythical free time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I haven&amp;rsquo;t used any extra options for btrfs and didn&amp;rsquo;t do any performance tuning. So far, the defaults work well.&lt;/p&gt;
&lt;h3 id="video-drives"&gt;Video drives
&lt;/h3&gt;&lt;p&gt;As I wrote before, I keep video files on several drives protected by SnapRAID. It does checksum-based bitrot protection and I see no reason to snapshot the filesystem, so there was no need for btrfs. I used ext4, but this time with some extra options when creating the filesystems: &lt;code&gt;mkfs.ext4 -m 0 -T largefile4 -L 'video1' /dev/mapper/luks-video1&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-m 0&lt;/code&gt; disables reserving space for root - by default 5% is reserved, that&amp;rsquo;s only possibly useful on a system disk.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-T largefile4&lt;/code&gt; changes the inode size to 4MB from the default of 16KB. An inode is an allocation unit. 4MB is a good value for video files which are hundreds of MB to several GB in size. Fewer inodes use less disk space for metadata, larger inodes increase performance for large files. But there&amp;rsquo;s a tradeoff: the size taken by each file is rounded up to the inode size, and if you run out of inodes you can&amp;rsquo;t write anything to the disk even if it has free space. For filesystems containing text documents, photos, applications, etc. - pretty much everything other than videos or filesystem images - you should stick to the default.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-L video1&lt;/code&gt; adds a filesystem label. A convenience on a machine with multiple drives, one more way to select the right filesystem.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-to-share-files"&gt;How to share files
&lt;/h2&gt;&lt;p&gt;The two main contenders are Samba and NFS. NFS allows using Linux-specific attributes such as file ownership and ACLs while Samba does the same for Windows. You can run both services on the same machine and even share the same files, if you need both Windows and Linux file permissions then go for it. If you have only one OS on your network, then the choice is obvious too.&lt;/p&gt;
&lt;p&gt;The less common choice is iSCSI, which exports a block device instead of a filesystem. From the point of view of the connected client, it will look a lot like a local drive which can be formatted and used in any way. Very useful for many professional settings, for example for live migrating VMs between hosts. Not so much for the home, unless you mostly want to hone your sysadmin skills.&lt;/p&gt;
&lt;p&gt;For now, I went with Samba only. I don&amp;rsquo;t really need any permissions, I&amp;rsquo;m OK with making all files on the share accessible to all systems and users on my LAN. If I need it later, I can get NFS running in a few minutes.&lt;/p&gt;
&lt;h2 id="configuration-with-ansible"&gt;Configuration with Ansible
&lt;/h2&gt;&lt;p&gt;I installed the base system, set the static lease on the DHCP server and launched Ansible. Main playbook firefly.yml (that&amp;rsquo;s how I called my NAS) is very simple, because the real deal is in the roles. Most are shared with my other computers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;linux_common is the basic configuration for all Linux systems, be it physical or VMs,&lt;/li&gt;
&lt;li&gt;docker_host and qemu_host are for running containers and VMs&lt;/li&gt;
&lt;li&gt;firefly is the main thing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ll describe the shared roles separately, now let&amp;rsquo;s focus on the nas role. I based the files on several configurations I found in Ansible Galaxy. It does quite a lot, so I split it for readability. The main file just imports the other four:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;import_tasks&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;pre-tasks.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;import_tasks&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mountpoints.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;import_tasks&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;samba.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;import_tasks&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;post-tasks.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;File tasks/samba.yml is mostly taken from &lt;a class="link" href="https://github.com/bertvv/ansible-role-samba" target="_blank" rel="noopener"
 &gt;https://github.com/bertvv/ansible-role-samba&lt;/a&gt; with only minor modifications. I added three files with tasks required before and after configuring Samba.&lt;/p&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;create a system user for samba, it will own the shared files&lt;/li&gt;
&lt;li&gt;create mount points: make directories where the new filesystems will be mounted, modify fstab, mount filesystems - change ownership of files&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;install and configure wsdd, or Web Services Dynamic Discovery, software that allows Windows to find Samba shares,&lt;/li&gt;
&lt;li&gt;install and configure hd-idle, software that turns off inactive USB drives (video disks are going to be unused most of the time)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="historical-notes"&gt;Historical notes
&lt;/h2&gt;&lt;h3 id="usb-drives"&gt;USB drives
&lt;/h3&gt;&lt;p&gt;My first attempt ran on Odroid and used only USB drives. It didn&amp;rsquo;t work well. Later when I moved to a PC case, I still used several USB drives. When they failed or ran out of space, I replaced them with internal drives. I don&amp;rsquo;t have any USB drives connected to the NAS anymore.&lt;/p&gt;
&lt;p&gt;The only reason I used USB drives is that I already had a large number of them.&lt;/p&gt;
&lt;h3 id="june-2023-update-snapraid-recovery"&gt;June 2023 update: SnapRAID recovery
&lt;/h3&gt;&lt;p&gt;It was inevitable that a disk would fail. In fact, two disks failed at once (at that time, I had more USB drives and didn&amp;rsquo;t trust them, so I used two parity drives). How I dealt with it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I unplugged the failed disks (they were both USB drives).&lt;/li&gt;
&lt;li&gt;Plugged new disks of the same size (could be bigger, could probably be smaller if they weren&amp;rsquo;t filled to capacity, but I had the same size disks).&lt;/li&gt;
&lt;li&gt;Formatted them the same way (again, not necessary, but since the setup worked before, I didn&amp;rsquo;t change anything), mounted in the same place.&lt;/li&gt;
&lt;li&gt;Checked the disk number in /etc/snapraid.conf, in my case these were d3 and d4.&lt;/li&gt;
&lt;li&gt;Ran the command to recover files: &lt;code&gt;snapraid -d d3 -l fix.log fix&lt;/code&gt; for one disk and &lt;code&gt;snapraid -d d4 -l fix2.log fix&lt;/code&gt; (I could have skipped &amp;lsquo;-d DISKNAME&amp;rsquo; to fix everything at once). Note: you should run it under screen or tmux as it will take a very long time - hours, maybe days.&lt;/li&gt;
&lt;li&gt;Ran the command to check the recovered files: &lt;code&gt;snapraid check&lt;/code&gt; (again, many hours).&lt;/li&gt;
&lt;li&gt;Finally, re-synchronised the array just to be sure: &lt;code&gt;snapraid sync&lt;/code&gt; (that was, as expected, quick).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="alternative-to-snapraid"&gt;Alternative to SnapRAID
&lt;/h3&gt;&lt;p&gt;Before I discovered SnapRAID, when I tried to create a NAS only with USB drives, I used a different solution to keep my data safe from drive failure. I added the drives in pairs, e.g. if a main drive was called &amp;ldquo;video1&amp;rdquo; there was also &amp;ldquo;video1-backup&amp;rdquo;. And I generated a script to copy all data to the backup disk. That&amp;rsquo;s where Ansible really shone. All I needed was a few lines in the playbook:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Generate backup script&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;template&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#f92672"&gt;dest&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/etc/cron.daily/rsync-backup.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#f92672"&gt;src&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;rsync-backup.sh.j2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And a very short template:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# rsync backup file -- Managed by Ansible, please don&amp;#39;t edit manually&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# {{ ansible_managed }}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;% &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; share in samba_shares %&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mount /data/&lt;span style="color:#f92672"&gt;{{&lt;/span&gt; share.name &lt;span style="color:#f92672"&gt;}}&lt;/span&gt;-backup &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; rsync -va /data/&lt;span style="color:#f92672"&gt;{{&lt;/span&gt; share.name &lt;span style="color:#f92672"&gt;}}&lt;/span&gt;/ /data/&lt;span style="color:#f92672"&gt;{{&lt;/span&gt; share.name &lt;span style="color:#f92672"&gt;}}&lt;/span&gt;-backup/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;umount /data/&lt;span style="color:#f92672"&gt;{{&lt;/span&gt; share.name &lt;span style="color:#f92672"&gt;}}&lt;/span&gt;-backup
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;% endfor %&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Where &amp;ldquo;samba_shares&amp;rdquo; contained the list of filesystems. When I added another disk pair, I only needed to edit &amp;ldquo;samba_shares&amp;rdquo;. Ansible modified fstab, smb.conf and the backup script.&lt;/p&gt;</description></item><item><title>Storage server, part 2: disk drives, RAID and SnapRAID, encryption</title><link>https://selfhosting.too-many-machines.com/home/nas-2/</link><pubDate>Fri, 14 Apr 2023 17:36:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/home/nas-2/</guid><description>&lt;p&gt;(first published on 2023.03.14, rewritten on 2026.07.08)&lt;/p&gt;
&lt;h2 id="what-i-have-now"&gt;What I have now
&lt;/h2&gt;&lt;p&gt;Again, before going into details, this is my current setup.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;System drive: 256GB SSD (no redundancy)&lt;/li&gt;
&lt;li&gt;Array for miscellaneous data: software RAID1 (btrfs) on 2x3TB drives&lt;/li&gt;
&lt;li&gt;Array for video files: SnapRAID on 4 HDDs, mixed sizes (1TB, 3x4TB)&lt;/li&gt;
&lt;li&gt;Data drives encrypted with LUKS, using method 2: providing the password later&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Hard drives" class="gallery-image" data-flex-basis="105px" data-flex-grow="44" height="600" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/home/nas-2/hdd.jpg" width="264"&gt;&lt;/p&gt;
&lt;h2 id="do-you-need-raid"&gt;Do you need RAID?
&lt;/h2&gt;&lt;p&gt;RAID stands for Redundant Array of (Inexpensive|Independent) Disks. Which means the data is stored on more than one disk. If one HDD fails, the array will continue to operate, your data will be still available. When you replace the failed drive, the array will rebuild.&lt;/p&gt;
&lt;h3 id="raid-vs-backup"&gt;RAID vs backup
&lt;/h3&gt;&lt;p&gt;RAID protects you from drive failure.&lt;/p&gt;
&lt;p&gt;Backup protects you from drive failure, controller failure, accidentally deleting a file, ransomware encrypting all your drives, fire, theft, flood and pretty much any other reason of data loss.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re going to choose between the two, choose backup. One good reason to have RAID in addition to backup is recovery time. Restoring terabytes of data from backup takes hours or days, RAID just continues to work. Which is why RAID is necessary for professional servers, to keep the business running, for home use it&amp;rsquo;s optional.&lt;/p&gt;
&lt;h2 id="which-raid-is-right"&gt;Which RAID is right?
&lt;/h2&gt;&lt;h3 id="hardware-vs-software"&gt;Hardware vs software
&lt;/h3&gt;&lt;p&gt;If you read very old articles, they might argue that software RAID is slow. That is not the case anymore. Modern CPUs (as in: made in the last 15 years or so) are so fast you won&amp;rsquo;t notice any performance impact under normal conditions. Maybe during the array rebuild, but even then HDD speed will be the bottleneck.&lt;/p&gt;
&lt;p&gt;Proper RAID controllers, e.g. MegaRAID or Dell PERC, offload everything from the CPU. I worked with them on real servers (in those unusual cases when you still use a physical server), but I&amp;rsquo;m not buying one for home. But if you happen to have one, you can consider using it.&lt;/p&gt;
&lt;p&gt;Some home motherboards offer hardware RAID, but do it so poorly that it&amp;rsquo;s actually slower than software RAID. And I wouldn&amp;rsquo;t trust them with my data.&lt;/p&gt;
&lt;p&gt;Hardware RAID gives an abstraction layer between your drives and your OS. You configure the array in BIOS setup tool or with an additional utility (e.g. megacli), the OS sees a single SCSI drive. It simplifies the view - either you work with physical drives or with filesystems, never mixing the two - but it also means you can&amp;rsquo;t use standard Linux tools to test and monitor the hard drives.&lt;/p&gt;
&lt;p&gt;For most people, the choice is between:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;standard Linux software RAID (built into kernel, configured with mdadm)&lt;/li&gt;
&lt;li&gt;modern filesystems (ZFS, btrfs) that combine filesystem, RAID and volume management&lt;/li&gt;
&lt;li&gt;RAID-like solutions such as SnapRAID&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are, as usual, tradeoffs, more on that later.&lt;/p&gt;
&lt;h3 id="raid-levels"&gt;RAID levels
&lt;/h3&gt;&lt;h3 id="raid0---striping"&gt;RAID0 - striping
&lt;/h3&gt;&lt;p&gt;R in RAID stands for Redundant. RAID0 isn&amp;rsquo;t - it spreads the writes across all drives to increase performance. If you lose just one drive, you lose the whole array.&lt;/p&gt;
&lt;h3 id="raid1---mirroring"&gt;RAID1 - mirroring
&lt;/h3&gt;&lt;p&gt;The most basic form of RAID. The same information is written to each drive in a pair. If one fails, the other continues to function as if nothing happened. When both work, read throughput is increased compared to a single drive (data can be read from both simultaneously), write throughput is similar to a single drive, maybe just a bit slower (need to write on both and it&amp;rsquo;s mostly done simultaneously). Rebuilding the array
after replacing the drive is a simple process of copying data. It&amp;rsquo;s a very safe level, but wastes half of the disk space.&lt;/p&gt;
&lt;p&gt;The nice thing about Linux software RAID is you can take out one disk from a pair, install it in a machine without RAID support and just use it like a regular drive, all the files would be accessible.&lt;/p&gt;
&lt;h3 id="raid5---parity"&gt;RAID5 - parity
&lt;/h3&gt;&lt;p&gt;RAID5 stores parity information. Think of it like this. Disk 1: data set A, disk 2: data set B, disk 3: A+B. If drive 2 fails, it can be recreated by calculating &amp;ldquo;disk 3 - disk 1&amp;rdquo;. Except the data and parity are distributed between the disks, not written on a dedicated one (that was RAID 3 and 4, but nobody uses them anymore). And you can have more
disks than 3. And the operation is not the sum. But you get the idea.&lt;/p&gt;
&lt;p&gt;RAID5 can survive a failure of one drive in the array, if the other one fails before the rebuild is done, all your data is lost.&lt;/p&gt;
&lt;p&gt;A huge advantage of RAID5 is the efficient use of storage. RAID1 always has a 50% capacity of disks. RAID5 on 3 drives - 66%, on 4 drives - 75%, etc. But there are also disadvantages. Performance is not as good as in RAID1. Read throughput is about the same as from a single disk, write throughput is worse and modifying small files is the biggest problem: the controller (or OS) needs to read the original sector, recalculate parity, write new data and new parity.&lt;/p&gt;
&lt;p&gt;But the worst thing is what happens when a drive fails. Every read requires calculating the data from parity, hurting performance. Rebuilding requires recalculating the whole data contained on the failed disk from the information stored on all other disks. This means the process is long and requires a lot of operations on the remaining disks, increasing the chance that one of them fails as well.&lt;/p&gt;
&lt;p&gt;For this reason, RAID5 is not recommended for professional use. For home uses, the opinions vary. I think that for small arrays (3-5 disks) the efficiency might be worth the risk. And you have a backup anyway, right? If you want to be extra safe, choose RAID1.&lt;/p&gt;
&lt;h3 id="raid6---parity-but-more-of-it"&gt;RAID6 - parity, but more of it
&lt;/h3&gt;&lt;p&gt;RAID6 is like RAID5, but it stores each parity block twice, on different drives. That means the array survives the failure of 2 hard drives.&lt;/p&gt;
&lt;p&gt;Four disks are the minimum number of hard drives for RAID6 and would give you the same storage efficiency as RAID1, but with more complexity. It&amp;rsquo;s a level of choice for large arrays in professional settings (that usually also have hot spares and replace disks every few years even if they don&amp;rsquo;t show any problems, all to minimise the risk of a second drive failure before the rebuild is done). But if you&amp;rsquo;re managing one, you shouldn&amp;rsquo;t be getting your knowledge from some random guy&amp;rsquo;s blog.&lt;/p&gt;
&lt;h3 id="raid10---10"&gt;RAID10 - 1+0
&lt;/h3&gt;&lt;p&gt;Combines mirroring and striping to give you both redundancy and performance. Sometimes used in professional settings, if throughput is more important than storage efficiency. Not much use at home.&lt;/p&gt;
&lt;h3 id="raid-levels-at-a-glance"&gt;RAID levels at a glance
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;RAID&lt;/th&gt;
 &lt;th&gt;Min. disks&lt;/th&gt;
 &lt;th&gt;Usable capacity&lt;/th&gt;
 &lt;th&gt;Fault tolerance&lt;/th&gt;
 &lt;th&gt;Read perf&lt;/th&gt;
 &lt;th&gt;Write perf&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;RAID 0&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;100% (all disks)&lt;/td&gt;
 &lt;td&gt;None - any disk failure loses everything&lt;/td&gt;
 &lt;td&gt;High&lt;/td&gt;
 &lt;td&gt;High&lt;/td&gt;
 &lt;td&gt;Striping only, no redundancy&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;RAID 1&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;50% (1 disk&amp;rsquo;s worth)&lt;/td&gt;
 &lt;td&gt;1 disk (per mirror pair)&lt;/td&gt;
 &lt;td&gt;High&lt;/td&gt;
 &lt;td&gt;Normal&lt;/td&gt;
 &lt;td&gt;Simple mirroring&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;RAID 5&lt;/td&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;(n-1)/n&lt;/td&gt;
 &lt;td&gt;1 disk&lt;/td&gt;
 &lt;td&gt;High&lt;/td&gt;
 &lt;td&gt;Reduced (parity calc)&lt;/td&gt;
 &lt;td&gt;Write hole risk on unclean shutdown&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;RAID 6&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;(n-2)/n&lt;/td&gt;
 &lt;td&gt;2 disks&lt;/td&gt;
 &lt;td&gt;High&lt;/td&gt;
 &lt;td&gt;Lower (double parity calc)&lt;/td&gt;
 &lt;td&gt;Safer than RAID5 for large/many disks&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;RAID 10&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;50%&lt;/td&gt;
 &lt;td&gt;1 per mirror pair (up to half the disks, if spread right)&lt;/td&gt;
 &lt;td&gt;High&lt;/td&gt;
 &lt;td&gt;High&lt;/td&gt;
 &lt;td&gt;Mirror + stripe, best perf/redundancy balance, costly on capacity&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;RAID-Z1 (ZFS)&lt;/td&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;(n-1)/n&lt;/td&gt;
 &lt;td&gt;1 disk&lt;/td&gt;
 &lt;td&gt;High&lt;/td&gt;
 &lt;td&gt;Reduced&lt;/td&gt;
 &lt;td&gt;Like RAID5, no write hole (COW)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;RAID-Z2 (ZFS)&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;(n-2)/n&lt;/td&gt;
 &lt;td&gt;2 disks&lt;/td&gt;
 &lt;td&gt;High&lt;/td&gt;
 &lt;td&gt;Lower&lt;/td&gt;
 &lt;td&gt;Like RAID6, no write hole&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="flexibility-of-linux-raid"&gt;Flexibility of Linux RAID
&lt;/h2&gt;&lt;p&gt;One good feature of mdadm - if you&amp;rsquo;re into DIY solutions and are not afraid of running strange configurations for some time - is the flexibility. If you plan to use RAID1 in future, but only have one drive now, you can create an array and declare one drive missing. It will show as degraded - obviously - but you can add the drive later and &amp;ldquo;rebuild&amp;rdquo;. It works the other way too - you can remove the drive if you temporarily need it for something else. Even better, put it into another computer with no RAID configured and it will still show all the data.&lt;/p&gt;
&lt;p&gt;If you need more space and decide to change RAID1 to RAID5, that&amp;rsquo;s just two commands: one to add a 3rd drive and one to change RAID level. Similarly, you can convert RAID5 to RAID6.&lt;/p&gt;
&lt;p&gt;Note that reshaping the array takes hours or days and your data might be at risk if the process is interrupted or if a drive fails mid-process (depending on the operation, many are safe). Always check mdadm documentation, consider testing on VMs first and make sure you have a tested backup.&lt;/p&gt;
&lt;p&gt;But still, such operations are occasionally useful for home scenarios when you need to manage limited disk space. And no other tool offers that kind of flexibility.&lt;/p&gt;
&lt;h2 id="modern-filesystems"&gt;Modern filesystems
&lt;/h2&gt;&lt;p&gt;Some modern filesystems - in particular, ZFS and btrfs - combine RAID, volume management, filesystem and other disk-related tools in one package. This has both advantages and disadvantages compared to the traditional way. The answer is not simple and will take a separate post. Before creating RAID with mdadm, read &lt;a class="link" href="https://selfhosting.too-many-machines.com/home/nas-3/" &gt;part 3&lt;/a&gt; - you might want something else.&lt;/p&gt;
&lt;h2 id="snapraid"&gt;SnapRAID
&lt;/h2&gt;&lt;p&gt;A relatively new option for home NAS is SnapRAID. It&amp;rsquo;s an interesting software that combines the advantages of RAID5/6 with the advantages of no RAID at all. How is that possible? The name SnapRAID comes from Snapshot (as in: snapshot backups) and RAID, because it works like something in between the two:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you have any number of data disks, they can have any filesystem such as ext4, XFS, JFS; could be different filesystems, different sizes,&lt;/li&gt;
&lt;li&gt;you have between 1 and 6 parity disks, they need to be as large as the largest data disk.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;During normal operation, you only use the data disks, parity drives are not touched until you run the &lt;code&gt;snapraid sync&lt;/code&gt; command (which is run either by cron, e.g. at night, or manually after you changed something on the data drives, depending on the use case). That works best for large files that are written once and never change - such as video files. Compared to proper RAID, there are some interesting advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can add and remove disks whenever you like and combine sizes,&lt;/li&gt;
&lt;li&gt;the drives can already contain the data when added, they would still contain the data when removed,&lt;/li&gt;
&lt;li&gt;you can also use it to restore files that were accidentally deleted (if you&amp;rsquo;re fast),&lt;/li&gt;
&lt;li&gt;there&amp;rsquo;s very little disk space overhead (e.g. if you use 1 parity disk for 4 data disks, that&amp;rsquo;s only 20%),&lt;/li&gt;
&lt;li&gt;it protects against bitrot, while RAID doesn&amp;rsquo;t (see &lt;a class="link" href="https://selfhosting.too-many-machines.com/home/nas-3/" &gt;part 3&lt;/a&gt; about bitrot),&lt;/li&gt;
&lt;li&gt;there&amp;rsquo;s no slowdown, no CPU usage and no extra I/O during normal operation, only during sync,&lt;/li&gt;
&lt;li&gt;you can set up your disks to power down and only the one that&amp;rsquo;s currently accessed will spin up (RAID5 or 6 would spin up all disks),&lt;/li&gt;
&lt;li&gt;if multiple disks break, you can still access the data on the other drives - unlike RAID5 and 6 when they would all be useless,&lt;/li&gt;
&lt;li&gt;while it is not recommended to use SnapRAID on USB drives, it is possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The downsides: your files are not protected from drive failure until you run &amp;ldquo;snapraid sync&amp;rdquo; and the technology is not as well tested as Linux RAID.&lt;/p&gt;
&lt;h2 id="testing-the-drives"&gt;Testing the drives
&lt;/h2&gt;&lt;p&gt;Some of the hard drives I use are old, maybe even 10 years old. But even with the new drives it&amp;rsquo;s a good idea to test them. Especially if you can replace them under warranty. I had some old USB drives too and did the following to quickly check them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;connected them to my laptop one by one,&lt;/li&gt;
&lt;li&gt;ran the short self-test: &lt;code&gt;smartctl -t short /dev/sdb&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;if there was already some data, checked the filesystem: &lt;code&gt;fsck -f /dev/sdb1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;short self-test takes about 2 minutes, after that time I would see the results plus all other SMART data: &lt;code&gt;smartctl -a /dev/sdb&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two drives were showing quite alarming results (failed self-test, high values in Reallocated_Sector_Ct). One of them had some old backups on it, it was also showing filesystem corruption and some files could not be read (nothing important though).
I copied the data, wiped the drives and threw them away.&lt;/p&gt;
&lt;p&gt;Should you immediately throw away the drive showing any errors in SMART data? It&amp;rsquo;s up to you, if you trust your RAID and backup, you might take your chances. Remember that a drive can fail without a SMART warning anyway. I&amp;rsquo;d say that one-digit numbers in &lt;em&gt;Reallocated_Sector_Ct&lt;/em&gt; or &lt;em&gt;Current_Pending_Sector&lt;/em&gt; are no cause for alarm. Some drives develop a few bad sectors early and then continue to work fine for years. But if you see the numbers rising or SMART overall health self-assessment goes bad, you better get a new drive soon.&lt;/p&gt;
&lt;p&gt;In fact, two other drives were showing some &amp;ldquo;not great, not terrible&amp;rdquo; SMART attributes. I marked them as suspicious and made sure they are backed up. It&amp;rsquo;s handy to have a label writer when you&amp;rsquo;re dealing with 20 USB drives.&lt;/p&gt;
&lt;p&gt;Then after connecting the drives to the target system it was time for a proper checkup. First, a long SMART self-test which takes several hours: &lt;code&gt;smartctl -t long /dev/sdX&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Another thing I did was a badblocks test overwriting the whole disk: &lt;code&gt;badblocks -b 4096 -wsv /dev/sdX&lt;/code&gt;. To reiterate, &lt;strong&gt;it overwrites everything on the disk!&lt;/strong&gt; Only use it with an empty drive. And it takes days on a large and slow drive. It was quite a pain, especially since some drives already had data, so I had to copy it around. But once it was done, I could be reasonably sure that every sector of the disk works properly. And if any errors were discovered and corrected by the drive firmware, they should show up in the SMART data.&lt;/p&gt;
&lt;h2 id="disk-encryption"&gt;Disk encryption
&lt;/h2&gt;&lt;p&gt;I wouldn&amp;rsquo;t use a laptop without an encrypted hard drive. Laptops often get lost or stolen. If you&amp;rsquo;re lucky, a thief will just reformat the laptop for sale. If you&amp;rsquo;re not, your data will be leaked, which means potential embarrassment or identity theft. Companies can even be fined for neglect. Both Windows (with BitLocker) and Linux (with LUKS) make it easy and almost transparent to encrypt a personal device. With Windows, you can even switch back and forth, with Linux, it&amp;rsquo;s better to choose during the installation whether you want encryption, changing your decision later means a manual process.&lt;/p&gt;
&lt;p&gt;The balance isn&amp;rsquo;t the same for home servers. In many places, home burglaries are unlikely, even if they happen, a bulky PC is not a first choice for thieves. And servers usually run headless (without screen and keyboard). Which means the usual way of unlocking encrypted hard drives - typing a password during boot - is not possible.&lt;/p&gt;
&lt;p&gt;Three ways to deal with it:&lt;/p&gt;
&lt;h3 id="method-1-no-encryption"&gt;Method 1: no encryption
&lt;/h3&gt;&lt;p&gt;A valid choice for some. If your risk vs inconvenience assessment says so, I&amp;rsquo;m not judging.&lt;/p&gt;
&lt;h3 id="method-2-providing-the-password-later"&gt;Method 2: providing the password later
&lt;/h3&gt;&lt;p&gt;You set your system NOT to mount the encrypted filesystems on boot, and NOT start any daemons that depend on them. Instead, you create a script that will prompt for a password, unlock the devices, mount the filesystems and start the services. After the server boots, log in with SSH and run the script.&lt;/p&gt;
&lt;p&gt;Upsides: easy to implement, no complex setups with many potential failure points, decent security.&lt;/p&gt;
&lt;p&gt;Downsides: still requires manual intervention, system drive can&amp;rsquo;t be encrypted this way.&lt;/p&gt;
&lt;p&gt;Overall, it can be a good choice for a home server that&amp;rsquo;s rarely booted. Not encrypting the system drive means some information is at risk (configuration in /etc, logs in /var, list of installed software) but for most home users it&amp;rsquo;s a reasonable tradeoff.&lt;/p&gt;
&lt;h3 id="method-3-passwordless-luks"&gt;Method 3: passwordless LUKS
&lt;/h3&gt;&lt;p&gt;Password is the most common type of LUKS key, but there are other possibilities.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LUKS key on a thumbdrive.&lt;/strong&gt; You can use a file as the LUKS key. Store it on a thumb drive, make sure it&amp;rsquo;s present during boot, then remove it and keep it somewhere safe, away from the server. Least secure of all options here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;systemd-cryptenroll with YubiKey or smartcard.&lt;/strong&gt; Similar to the previous version, but slightly more secure: a thumbdrive can be copied, those devices can&amp;rsquo;t. Requires additional hardware that most people don&amp;rsquo;t have and a more complex setup. Useless if you leave your device in or next to the server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LUKS key in initramfs + Secure Boot.&lt;/strong&gt; LUKS key is present in the initial ramdisk, so during normal boot, the disk is automatically unlocked. If you enable Secure Boot, password-protect the bootloader, UEFI setup and prevent selecting an alternative boot device, you&amp;rsquo;ll also protect the system during not-so-normal boot. Allows unattended boot and encryption of the system disk (but not /boot), but has one weak point: an attacker can move the hard drives to another machine, running their own OS, thereby bypassing your passwords. They can then extract the key from initramfs (which needs to be unencrypted in that setup), unlock and mount the drives. Probably above the pay grade of a common thief, but not beyond the skills of an experienced Linux user, not to mention a security professional.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LUKS key in the TPM.&lt;/strong&gt; TPM will only release the key if certain conditions are met. You can also configure it to always release the key, which works similar to the previous point, but addresses the weakness: if you move the drives to another machine, the TPM won&amp;rsquo;t contain the key anymore. Downsides: it&amp;rsquo;s the most complex option.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authorization server on the network.&lt;/strong&gt; You&amp;rsquo;ll be surprised how much you can pack into initramfs to manage the way the system boots. One way is to contact Tang server to authorize unlocking the disk. If the server is moved away from the LAN, it won&amp;rsquo;t reach it. Tang server (and related Clevis client) are free and open source, in theory you could run it at home, but I&amp;rsquo;ve never heard of anyone doing it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Are these methods practical? First two rely on removing your key after the system boots, leave it with the servers and you completely defeated the encryption. The others are much more complex. There&amp;rsquo;s a real risk of making your system unbootable due to configuration error or a failed update - you should have an additional LUKS key (e.g. a complex password you keep in your password manager) for recovery. There&amp;rsquo;s also a risk of leaving a way in if you miss one step (and the 3rd method already leaves a huge known vulnerability).&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t recommend passwordless LUKS unless you&amp;rsquo;re a seasoned sysadmin. Actually, I am a seasoned sysadmin and decided it&amp;rsquo;s not worth it for home use.&lt;/p&gt;</description></item><item><title>Storage server, part 1: hardware</title><link>https://selfhosting.too-many-machines.com/home/nas-1/</link><pubDate>Fri, 14 Apr 2023 17:35:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/home/nas-1/</guid><description>&lt;img src="https://selfhosting.too-many-machines.com/home/nas-1/firefly.jpg" alt="Featured image of post Storage server, part 1: hardware" /&gt;&lt;p&gt;(first published on 2023.03.14, rewritten on 2026.07.07)&lt;/p&gt;
&lt;p&gt;A house is not a home without a NAS. OK, maybe I&amp;rsquo;m not really that emotionally attached to my file server, but it&amp;rsquo;s one of the first things you need if you want to rely less on the cloud. What can you use it for?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sharing files between the devices,&lt;/li&gt;
&lt;li&gt;storing large data you create, such as photos and videos - laptops and even desktops tend to have smallish SSD drives these days, not really suitable for media content,&lt;/li&gt;
&lt;li&gt;backups,&lt;/li&gt;
&lt;li&gt;storing music, movies and TV shows.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also use the same machine as an all-purpose server. With virtualization and containers, it&amp;rsquo;s easy to separate different workloads (you could run everything on the same OS, but the system might get messy).&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m trying to keep my IT green (see &lt;a class="link" href="https://selfhosting.too-many-machines.com/general/green-it/" &gt;separate post&lt;/a&gt; for details), which in short means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the fewer boxes running 24/7, the better,&lt;/li&gt;
&lt;li&gt;prefer old hardware to new one.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-i-have-now"&gt;What I have now
&lt;/h2&gt;&lt;p&gt;Before going into details and considerations, this is my current setup&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Part&lt;/th&gt;
 &lt;th&gt;Model&lt;/th&gt;
 &lt;th&gt;source&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Case&lt;/td&gt;
 &lt;td&gt;Corsair, unknown (huge) model&lt;/td&gt;
 &lt;td&gt;used&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Motherboard&lt;/td&gt;
 &lt;td&gt;Asus H87I-PLUS, Mini-ITX&lt;/td&gt;
 &lt;td&gt;used&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CPU&lt;/td&gt;
 &lt;td&gt;Intel Pentium G3220T&lt;/td&gt;
 &lt;td&gt;(came with MB)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;RAM&lt;/td&gt;
 &lt;td&gt;8GB&lt;/td&gt;
 &lt;td&gt;(came with MB)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;HDD&lt;/td&gt;
 &lt;td&gt;6 different ones&lt;/td&gt;
 &lt;td&gt;new and already owned&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SSD&lt;/td&gt;
 &lt;td&gt;Plextor PX-256M6S&lt;/td&gt;
 &lt;td&gt;already owned&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PSU&lt;/td&gt;
 &lt;td&gt;Thermaltake Smart BM2 450W&lt;/td&gt;
 &lt;td&gt;new&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;other&lt;/td&gt;
 &lt;td&gt;extra SATA card&lt;/td&gt;
 &lt;td&gt;new&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="my-nas-history"&gt;My NAS history
&lt;/h2&gt;&lt;h3 id="commercial-vs-diy"&gt;Commercial vs DIY
&lt;/h3&gt;&lt;p&gt;The easiest option is to buy an off-the-shelf NAS such as Synology or QNAP. I had one in the past (&lt;strong&gt;Synology DS216se&lt;/strong&gt;). The good thing about them is you can get them up and running in less than an hour and without much knowledge. Just install disks, plug it into the network and answer a few questions in the web UI. It&amp;rsquo;s also more compact than even a smallest PC.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Synology DS216se" class="gallery-image" data-flex-basis="379px" data-flex-grow="158" height="759" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/home/nas-1/synology.jpg" srcset="https://selfhosting.too-many-machines.com/home/nas-1/synology_hu_6e87e378ca93ead6.jpg 800w, https://selfhosting.too-many-machines.com/home/nas-1/synology.jpg 1200w" width="1200"&gt;&lt;/p&gt;
&lt;p&gt;But the cheap models only have 2 disk bays, very little RAM and a pathetic CPU. They work fine as basic file servers, but doing anything in the GUI is an exercise in frustration. Click, wait for 10 seconds until it responds, click, wait again. You&amp;rsquo;ll quickly miss the command line. If you want to run any extra services, you&amp;rsquo;re limited by both hardware resources and software available. I used RAID1 for storage so when I ran out of space, the only option would be to replace both disks with the bigger ones, old disks would be useless (or add a USB drive, but that means no RAID). Enough, back to the DIY.&lt;/p&gt;
&lt;h3 id="to-usb-or-not-to-usb"&gt;To USB or not to USB?
&lt;/h3&gt;&lt;p&gt;I have a drawer full of USB HDDs. A few were mine, most I literally inherited. Many of them are small and not really worth bothering, but about a dozen were 1-2TB. I experimented with a NAS running on a single-board system (&lt;strong&gt;Odroid C1+&lt;/strong&gt;) and a small-form PC (&lt;strong&gt;Dell Wyse&lt;/strong&gt;) and decided I REALLY don&amp;rsquo;t want a NAS on USB drives. You can run one or two semi-reliably, but the more you have, the probability of problems approaches 100%. Sometimes the plug moves in the socket and you end up with a corrupt filesystem. Or the cable cannot handle the power. Or the disks run fine when you initially connect them one by one, but during the next reboot they will start all at once drawing too much current (yes, I tried a powered USB hub) and only half of them will show up. Of course, you can&amp;rsquo;t have RAID on USB (technically, it is possible, the system wouldn&amp;rsquo;t stop you, but it&amp;rsquo;s a terrible idea if your drives can disappear at any moment).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Odroid C1+" class="gallery-image" data-flex-basis="152px" data-flex-grow="63" height="1200" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/home/nas-1/odroid.jpg" width="762"&gt;&lt;/p&gt;
&lt;h3 id="searching-for-a-case"&gt;Searching for a case
&lt;/h3&gt;&lt;p&gt;For a NAS, you want a case that can hold many drives: preferably, more than you intend to install at the moment, to have space for extension. Such cases are relatively easy to buy in the USA, but not in Europe. It&amp;rsquo;s quite a niche product after all, very few PCs use more than 1 or 2 drives. Those that can be found are twice as expensive as in the States, buying straight from the US vendor means paying for shipping and long waiting.&lt;/p&gt;
&lt;p&gt;But I found one interesting option: &lt;strong&gt;Kolink Satellite Cube&lt;/strong&gt;. According to the specs, it had 4x 2.5&amp;quot; and 3x 3.5&amp;quot; bays. And it held a mini-ITX motherboard - not perfect, but OK. It was cheap and quite small. I ordered the case and searched for the MB. Mini-ITX are hard to find, but I found a great second-hand option, with 8GB RAM, a Pentium CPU that&amp;rsquo;s really a 4th generation Intel Core and best of all, 6 SATA ports!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Kolink Satellite Cube" class="gallery-image" data-flex-basis="210px" data-flex-grow="87" height="1200" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/home/nas-1/kolink.jpg" srcset="https://selfhosting.too-many-machines.com/home/nas-1/kolink_hu_5fc09b6faa058222.jpg 800w, https://selfhosting.too-many-machines.com/home/nas-1/kolink.jpg 1051w" width="1051"&gt;&lt;/p&gt;
&lt;p&gt;When I got the case, I discovered the specs were wrong in two ways. One, it could hold a mini-ITX or a micro-ATX motherboard, the latter is slightly larger and much more popular. Fine, if I knew that maybe I would have found the motherboard quicker, but I&amp;rsquo;m satisfied with the one I got. But the other thing was worse. Turned out I can have 4x 2.5&amp;quot; OR 3x 3.5&amp;quot;, but not both.&lt;/p&gt;
&lt;p&gt;I lived with this limited space until I got tired of it and searched eBay. I found a person nearby selling &lt;strong&gt;Corsair gaming case&lt;/strong&gt; and the specs specifically mentioned multiple drive bays. I don&amp;rsquo;t even know how many hard drives it can take, but I put 6 and there&amp;rsquo;s some space left. Plus, the 5.25&amp;quot; drive bays can be reused for HDDs if I need even more.&lt;/p&gt;
&lt;p&gt;That also means I can switch to a full-size ATX motherboard in future. Currently, the mini-ITX one I have is enough and can be installed in a huge case, even if it looks funny sitting in one corner of it.&lt;/p&gt;
&lt;h2 id="hardware-considerations---mine-and-yours"&gt;Hardware considerations - mine and yours
&lt;/h2&gt;&lt;h3 id="cpu"&gt;CPU
&lt;/h3&gt;&lt;p&gt;If you want only a storage server, or just a few small services on top of it, you don&amp;rsquo;t need much processing power. Any reasonably modern (say, less than 15 years old) mainline CPU from Intel or AMD is already way more powerful than the puny processor in a commercial NAS. Rule of thumb for Intel CPUs: if it&amp;rsquo;s called &amp;ldquo;Core i-some number&amp;rdquo;, it should be OK.&lt;/p&gt;
&lt;p&gt;If you want more processing power (e.g. for video transcoding), look for something slightly newer - not necessarily new and top of the line. Still more? If you have funds, you can pack an unreasonable amount of CPU power in one box. I used to work with servers that had 2x128 cores, they were used for physics simulations. But I don&amp;rsquo;t have enough imagination to think why anyone would need that at home.&lt;/p&gt;
&lt;p&gt;On the other end of the spectrum, there are Intel Atom (for low power usage devices), Pentium and Celeron (budget versions of mainline CPUs). I really wish Intel used a more consistent naming convention. These brands have been in use for decades and without checking the exact model, you won&amp;rsquo;t know if it&amp;rsquo;s just slightly slower than a modern laptop, or a completely unusable 30-year-old junk.&lt;/p&gt;
&lt;p&gt;My CPU is Intel Pentium G3220T, which is a slightly slower (no HT, no AVX) version of a 4th generation Core i3. Slow by today&amp;rsquo;s standards, yet most of the time it&amp;rsquo;s only used at 1%.&lt;/p&gt;
&lt;p&gt;Two things worth checking when buying used hardware:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does the CPU have &lt;strong&gt;virtualization extensions&lt;/strong&gt;? You need them to run VMs at reasonable speeds. All mainline CPUs since roughly 2015 have them, if it&amp;rsquo;s older or unusual - check.&lt;/li&gt;
&lt;li&gt;Intel Xeon and AMD EPYC are server CPUs. Some are power hogs, others scale down just as well as home models. If you find a good offer on them, there&amp;rsquo;s a chance to buy some real processing power cheaply, but check idle power usage. Note that server motherboards might not fit standard PC cases.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also don&amp;rsquo;t recommend buying CPU and motherboard separately, unless you know what you&amp;rsquo;re doing and how to check the compatibility.&lt;/p&gt;
&lt;h3 id="ram"&gt;RAM
&lt;/h3&gt;&lt;p&gt;For a storage-only server, even small RAM like 1 GB is OK for the start, Linux would only need a few dozen MB anyway and use the rest for disk cache. If you want to run extra services, the answer can vary from &amp;ldquo;1GB is still enough&amp;rdquo; to &amp;ldquo;I have 128G and ran out of memory anyway&amp;rdquo;. But realistically, 4GB should suffice if you don&amp;rsquo;t run VMs. I have 8GB and use less than 2 most of the time.&lt;/p&gt;
&lt;p&gt;The situation changes if you want to use ZFS - a modern filesystem with some interesting features, popular among self hosters. 4GB is a minimum, 8GB is better. Personally, I don&amp;rsquo;t like ZFS, more on that in later posts.&lt;/p&gt;
&lt;h3 id="network"&gt;Network
&lt;/h3&gt;&lt;p&gt;You should connect the NAS using Ethernet, not Wi-Fi. 1Gbit is a reasonable choice, 100Mbit is too slow these days, higher speeds are not necessary for most home users (if you&amp;rsquo;re the one that needs it, you know).&lt;/p&gt;
&lt;p&gt;If you really insist on 10Gbit, you should use fibre. While the standard Ethernet cables (CAT6 and higher) theoretically support 10Gbit, the transceivers/NICs often overheat under high load, causing unstable connections. I&amp;rsquo;m speaking from experience of using 10Gbit at work.&lt;/p&gt;
&lt;h3 id="sata-ports"&gt;SATA ports
&lt;/h3&gt;&lt;p&gt;You&amp;rsquo;ll need a lot of them. Most motherboards have 2 or 4, if you&amp;rsquo;re lucky you might get 6. It&amp;rsquo;s not a big issue: you can install additional controllers. Just make sure you have enough empty PCI Express slots. This is only a concern for small cases and mini-ITX/micro-ATX motherboards, full-size PCs can easily have an unreasonable number of SATA ports.&lt;/p&gt;
&lt;h3 id="hard-drives"&gt;Hard drives
&lt;/h3&gt;&lt;p&gt;3.5&amp;quot; are cheaper, faster, available in larger capacities and are considered more reliable (though it&amp;rsquo;s mostly anecdotal evidence).&lt;/p&gt;
&lt;p&gt;2.5&amp;quot; are smaller (obviously). In some PC cases, you can fit more of them. In others, you just have to install a caddy around the 2.5&amp;quot; drive and you still use the same 3.5&amp;quot; bay.&lt;/p&gt;
&lt;h4 id="smr-controversy"&gt;SMR controversy
&lt;/h4&gt;&lt;p&gt;Traditional hard drives use &lt;strong&gt;CMR&lt;/strong&gt; (Conventional Magnetic Recording), where each data track is written independently with a small gap between tracks. &lt;strong&gt;SMR&lt;/strong&gt; (Shingled Magnetic Recording) drives instead overlap tracks like roof shingles. This allows packing the data more densely, and therefore lower the price of high capacity disks.&lt;/p&gt;
&lt;p&gt;But there&amp;rsquo;s a tradeoff. Writes to SMR drives are 3-10x slower than to CMR. Hard drives use write caching to offset this. In normal use, you might not notice the difference. But if you try to write gigabytes at a time, you&amp;rsquo;ll notice write speed begins at reasonable levels such as 250MB/s, but in a few seconds, once the cache is full, slows down to 40MB/s. It&amp;rsquo;s not only an issue for copying large files, but also for RAID rebuilds.&lt;/p&gt;
&lt;p&gt;Most budget large-capacity HDDs these days are SMR. Whether you want to pay more for CMR, depends on your use case.&lt;/p&gt;
&lt;p&gt;There are several types of SMR, the most popular in consumer hardware is called Drive Managed (DM-SMR). It is invisible to the host, there&amp;rsquo;s no attribute that flags it. Some drives might have SMR in their identification that you can read with &lt;code&gt;dmesg | grep sda&lt;/code&gt; or &lt;code&gt;smartctl -i /dev/sda&lt;/code&gt;, but most don&amp;rsquo;t. The only semi-reliable way to check your HDDs is to read the model number (from smartctl data) and look it up on the internet. Semi-reliable, because several manufacturers were caught quietly swapping technology in previously-CMR product lines.&lt;/p&gt;
&lt;p&gt;Personally, I have a mixture. Some drives are CMR, because they predate SMR technology. New ones are a mix of both SMR and CMR, when I bought them, I didn&amp;rsquo;t know the difference.&lt;/p&gt;
&lt;h4 id="brands-and-reliability"&gt;Brands and reliability
&lt;/h4&gt;&lt;p&gt;You&amp;rsquo;ve probably met someone who says never to buy Seagate and get Western Digital instead. Or the other way around. The truth, if you look at reliability data (e.g. &lt;a class="link" href="https://www.backblaze.com/cloud-storage/resources/hard-drive-test-data" target="_blank" rel="noopener"
 &gt;famous Backblaze stats&lt;/a&gt;), is that all manufacturers had some models that failed early and some that exceeded expectations. If you want to shop for reliability, look for specific model, not just the brand. Even that doesn&amp;rsquo;t guarantee that 2026 WD Red 4TB is the same thing as 2024 WD Red 4TB.&lt;/p&gt;
&lt;p&gt;Personally, I don&amp;rsquo;t care much. I&amp;rsquo;m just prepared for any drive to fail at any moment. Instead, if I buy two HDDs at the same time - especially if I plan to use them in RAID1 - I choose two different manufacturers. It&amp;rsquo;s not optimal for array performance (ideally, you would have 2 drives with the same specs), but I&amp;rsquo;m reducing the chance of both drives failing at the same time.&lt;/p&gt;
&lt;h4 id="usb-drives-and-shucking"&gt;USB drives and shucking
&lt;/h4&gt;&lt;p&gt;External USB disks are often cheaper than internal 2.5&amp;quot; drives, despite the extra cost of enclosure and electronics. Some can be removed and used as regular drives. The process is called shucking and it&amp;rsquo;s quite popular among self hosters. But there&amp;rsquo;s a reason for lower price: they are built with lower quality components (or they failed QA tests), they might live for years if they are only used occasionally, but won&amp;rsquo;t last long with more intensive use.&lt;/p&gt;
&lt;p&gt;Since I had many USB drives, I shucked a few. I used others as external drives (they couldn&amp;rsquo;t be shucked, they are soldered directly to the USB electronics and have no SATA port). About half of them failed in a few months, the other half worked just as reliable as regular HDDs for a few years. I don&amp;rsquo;t use them with the NAS now, though some are still usable. I don&amp;rsquo;t recommend buying USB drives for a NAS, but if you already have them, you can reuse them, just be prepared for failure.&lt;/p&gt;
&lt;h4 id="the-system-drive"&gt;The system drive
&lt;/h4&gt;&lt;p&gt;Should you place your OS with the data or use a separate disk? Both ways have some pros and cons.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you use an old motherboard, it might not support booting from large disks - but once the operating system loads, it will access the drives just fine.&lt;/li&gt;
&lt;li&gt;Most, if not all, NAS-specific distros require a separate system disk.&lt;/li&gt;
&lt;li&gt;Decoupling data from OS will generally make your life easier in the long run: you can replace data disks if they are too small while keeping the system. Or you can move all your data drives to another machine.&lt;/li&gt;
&lt;li&gt;You can also run the system from an SSD which is much faster - it doesn&amp;rsquo;t make much difference if you use it only for NAS, but it could be noticeable if you also want to host VMs or containers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On the other hand, keeping OS with the data means you don&amp;rsquo;t waste precious drive bay in the case. And if your data is on RAID, the system is also protected from the drive failure.&lt;/p&gt;
&lt;p&gt;All things considered, I&amp;rsquo;d recommend a separate drive.&lt;/p&gt;
&lt;p&gt;Tip: if you want to use a separate drive, but don&amp;rsquo;t want to waste drive bay/SATA port/money on the extra disk, you can install Linux on a USB stick. Just remember they are way slower even than HDDs and wear out quickly. You should try to use them read-only (possible but tricky, requires combining read-only fs with a writable ramdisk on top of it, e.g. aufs) or almost read-only (e.g. disable swap, redirect logs to an external system). You can even install the stick internally - motherboards have USB connectors for use with the case&amp;rsquo;s front ports.&lt;/p&gt;
&lt;h3 id="psu-power-supply"&gt;PSU (power supply)
&lt;/h3&gt;&lt;p&gt;Buy the &lt;strong&gt;lowest&lt;/strong&gt; power PSU you can find. It won&amp;rsquo;t be easy. These days few people build PCs, it seems that these are mostly gamers and crypto miners, both use power-hungry GPUs. A NAS, even with multiple hard drives, will likely not exceed 100W at full load, much less at idle. Old PSUs were very inefficient at very low load, modern ones are better, but still waste a considerable amount of power.&lt;/p&gt;
&lt;p&gt;There are certifications aimed exactly at this problem: &lt;strong&gt;80+&lt;/strong&gt; means the PSU is at least 80% efficient at any load higher than 20%. The higher the level - they go from White (or just 80+ without a modifier) through Bronze, Silver, Gold, Platinum to Titanium - the better, but more expensive.&lt;/p&gt;
&lt;p&gt;Other things worth paying for are reliability and low noise level. Good news: all three factors often go together. Bad news: they also go together with higher price.&lt;/p&gt;
&lt;p&gt;Brands recommended in self hosting circles: Seasonic, Corsair, be quiet!, FSP. The one I have (Thermaltake) is considered &amp;ldquo;not great, not terrible&amp;rdquo;.&lt;/p&gt;</description></item><item><title>Pi-hole - DHCP and DNS</title><link>https://selfhosting.too-many-machines.com/home/pi-hole/</link><pubDate>Fri, 14 Apr 2023 17:30:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/home/pi-hole/</guid><description>&lt;p&gt;DHCP is a necessary component of the home network. You probably don&amp;rsquo;t want to manually assign IPs. It would be quite inconvenient on the laptop
that travels between several networks, even more on the smartphone. On IoT devices lacking proper screens and keyboards, it&amp;rsquo;s plain impossible.&lt;/p&gt;
&lt;p&gt;If all you have are client devices, you don&amp;rsquo;t need to know what IPs are assigned. Perhaps the address of the router to occasionally log in to its web
interface, but that&amp;rsquo;s it. Most people don&amp;rsquo;t know the IP of their computer or tablet and rightly so.&lt;/p&gt;
&lt;p&gt;It all changes when you want to run servers. There are ways to find services on the local network, such as mDNS and WS-Discovery, but let&amp;rsquo;s face
it: at some point, you&amp;rsquo;ll need to point your browser or SSH client at a specific IP. So it&amp;rsquo;s important that it doesn&amp;rsquo;t change.&lt;/p&gt;
&lt;p&gt;Of course, every home router contains a DHCP server which allows setting some addresses as static. It&amp;rsquo;s often a good choice. There&amp;rsquo;s
one important advantage of keeping your DHCP server on the router: fewer points of failure. DHCP is needed for the network to
function and so is the router, if they&amp;rsquo;re the same device, that&amp;rsquo;s only one device that really needs to work all the time. But a good reason to move
DHCP out of the router is the possibility to back up configuration. Are you going to keep using the same router? I certainly don&amp;rsquo;t, as the
one I got from my internet provider is terrible and I&amp;rsquo;m going to replace it soon. I don&amp;rsquo;t want to configure all the addresses manually again
and home routers don&amp;rsquo;t allow you to export/import DHCP configuration, certainly not between different brands.&lt;/p&gt;
&lt;p&gt;So I decided to run my own DHCP server as a first step before I start running servers. But why would I type IPs, when I could use local DNS?
And if I have a DNS for the local network, it can also cache internet addresses. A typical website fetches content from 10 or more different
servers, so there&amp;rsquo;s a noticeable difference when the DNS replies in 2 milliseconds as opposed to 200 milliseconds. I could use bind and isc-dhcpd
like I did in the past when configuring &amp;ldquo;real&amp;rdquo; networks, but there are servers more tailored to the needs of home users.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pi-hole GUI" class="gallery-image" data-flex-basis="394px" data-flex-grow="164" height="767" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/home/pi-hole/pihole.png" srcset="https://selfhosting.too-many-machines.com/home/pi-hole/pihole_hu_bc618183999b1140.png 800w, https://selfhosting.too-many-machines.com/home/pi-hole/pihole.png 1262w" width="1262"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pi-hole&lt;/strong&gt; was designed as a DNS sinkhole - a server that can block some content by replying &amp;ldquo;address unknown&amp;rdquo;. Typical use is blocking ads and
tracking: on websites, but also in mobile applications and IoT devices that can&amp;rsquo;t use traditional ad-blocking software. It can also be used to
increase security by blocking some malware that relies on DNS, blocking fraudulent websites, etc. - it all depends on the blocklists loaded and there
are many to choose from. Non-blocked queries are forwarded to external DNS servers and then cached. There&amp;rsquo;s also a possibility to add
local addresses and an optional DHCP server. So it&amp;rsquo;s the extra functionality that first caught my attention and the core functionality is for me
a nice addition.&lt;/p&gt;
&lt;h2 id="choosing-hardware"&gt;Choosing hardware
&lt;/h2&gt;&lt;p&gt;Pi in Pi-hole comes from Raspberry Pi, but the software can run pretty much everywhere where you can run Linux. I did my first experiment using
Docker on my homelab server, but I quickly thought that Raspberry Pi is indeed a better option. Not because there was anything wrong with the
Pi-hole itself, but DHCP and DNS are such important pieces of infrastructure they really should run on a separate device. A homelab server is, by
definition, something for experiments, you often need to reboot it and sometimes feel a need to rebuild it from scratch. Plus, if Pi-hole crashes
(it never happened so far and I&amp;rsquo;ve been running it for months) when I&amp;rsquo;m away, it can be rebooted by pulling a plug.&lt;/p&gt;
&lt;p&gt;So, Pi it is. They are currently hard to get or outrageously priced, but luckily I had one unused Pi 2 in the drawer. It&amp;rsquo;s 7 years old, with a performance
way below current models, but more than enough for the job.&lt;/p&gt;
&lt;h2 id="installing-os"&gt;Installing OS
&lt;/h2&gt;&lt;p&gt;The official operating system is &lt;strong&gt;Raspberry Pi OS&lt;/strong&gt;, formerly called Raspbian. It&amp;rsquo;s a port of Debian, my favourite distro. Perfect. I chose a 32-bit version
(64-bit requires Pi 3 or 4), Lite edition, meaning no GUI. Unlike the PCs, on machines like this, you don&amp;rsquo;t use the installer. Instead, you write the image
to the micro-SD card using another computer. There is a special tool for that called Raspberry Pi Imager. It simplifies downloading and writing and
even allows you to do some customisation. I chose the old school way - downloaded the image myself, wrote it with dd and customised the system later,
but that&amp;rsquo;s because I like the old way.&lt;/p&gt;
&lt;h2 id="initial-configuration"&gt;Initial configuration
&lt;/h2&gt;&lt;p&gt;My system is going to run headless, but I have to do the initial configuration. There are two options: you can temporarily connect a keyboard and monitor, during the first boot you&amp;rsquo;ll answer a few questions. Or you can mount the micro-SD card on the PC and modify a few config files. There&amp;rsquo;s a third option
of using Raspberry Pi Imager, but I already skipped this. Since I like editing config files and I&amp;rsquo;m too lazy to go to another room and fetch an HDMI cable,
I chose the second option. Again, feel free to use the other two if you don&amp;rsquo;t feel like manually editing configs.&lt;/p&gt;
&lt;p&gt;There are two partitions on the micro-SD card, bootfs and rootfs. All I had to do was these 3 steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;on bootfs, create a file called ssh or ssh.txt, it can be empty or have any content, just the presence of this file informs the system to start the SSH server,&lt;/li&gt;
&lt;li&gt;also on bootfs, create a file called userconf.txt containing a username and an encrypted password separated by a colon, to encrypt the password you use
the command: openssl passwd -6&lt;/li&gt;
&lt;li&gt;on rootfs (not bootfs this time!), edit the file etc/dhcpcd.conf to provide network information (since the Pi is going to run the DHCP server, it obviously
cannot get the configuration from DHCP)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd /media/igor/bootfs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;touch ssh
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl passwd -6
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vim userconf.txt &lt;span style="color:#75715e"&gt;# add the line below&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;igor:I&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;mNotShowingMyPasswordHereEvenIfItIsEncrypted/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd /media/igor/rootfs/etc
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vim dhcpcd.conf
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# add lines&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;interface eth0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;static ip_address&lt;span style="color:#f92672"&gt;=&lt;/span&gt;192.168.1.3/24
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;static routers&lt;span style="color:#f92672"&gt;=&lt;/span&gt;192.168.1.1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;static domain_name_servers&lt;span style="color:#f92672"&gt;=&lt;/span&gt;192.168.1.1 1.1.1.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that I&amp;rsquo;m not using localhost as the DNS server - not yet!&lt;/p&gt;
&lt;p&gt;I replaced the micro-SD card, connected Pi to LAN and powered it up. A few seconds later it&amp;rsquo;s available on the network. I could log in with SSH
and update packages. It takes much longer than on the PC (SD cards are slow).&lt;/p&gt;
&lt;h2 id="installing-pi-hole"&gt;Installing Pi-hole
&lt;/h2&gt;&lt;p&gt;The recommended method is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -sSL https://install.pi-hole.net | bash
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;but I prefer to see the script before running it, so I chose alternative method 2:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;wget -O basic-install.sh https://install.pi-hole.net
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;less basic-install.sh
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo bash basic-install.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The installation script supports several operating systems and uses the distro&amp;rsquo;s package manager to install most of
the software: lighttpd and a few PHP packages for the web interface, git to download the core part of Pi-hole and some
supporting tools. We need to confirm the system uses static IP and answer some very important questions (but don&amp;rsquo;t
worry, you can change all the settings later):&lt;/p&gt;
&lt;h2 id="privacy-versus-security"&gt;Privacy versus security
&lt;/h2&gt;&lt;p&gt;Do you want to keep logs of the DNS queries? It can be useful to see the activity on the network, e.g. to debug something
or to find malware. But it also means you&amp;rsquo;re logging all websites visited by you and other people in your home. Do you
want it? I don&amp;rsquo;t. Another problem with query logging is the size of logs - you&amp;rsquo;re going to run out of disk space soon and
you&amp;rsquo;ll quickly wear out the SD card.&lt;/p&gt;
&lt;p&gt;My recommendation: answer &amp;ldquo;yes&amp;rdquo; during installation, but only keep this option on for a few minutes, to see that
everything is running fine. Then, log in to the web interface, choose Settings/System, &amp;ldquo;Disable query logging&amp;rdquo;, then
&amp;ldquo;Flush logs&amp;rdquo; to delete those already written. Pi-hole will only keep completely anonymous statistics, e.g. how many
clients are connected and how many DNS queries were sent.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Disabling query log" class="gallery-image" data-flex-basis="455px" data-flex-grow="189" height="327" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/home/pi-hole/pihole-querylog.png" width="621"&gt;&lt;/p&gt;
&lt;p&gt;Warning, there&amp;rsquo;s another option in Settings/Privacy to disable &lt;em&gt;displaying&lt;/em&gt; some private information, but if you don&amp;rsquo;t
disable logging, it will still be written.&lt;/p&gt;
&lt;h2 id="choosing-upstream-dns"&gt;Choosing upstream DNS
&lt;/h2&gt;&lt;p&gt;Another important option during installation is the upstream DNS or the server to which the Pi-hole will forward the queries.
Again you should consider privacy. A popular option is Google, the server is fast and its IP is easy to remember (8.8.8.8), but
don&amp;rsquo;t you think Google already knows too much about you?&lt;/p&gt;
&lt;p&gt;I think there are two good options. One, choosing &amp;ldquo;custom&amp;rdquo; and entering the servers of your internet provider. After all, the
provider already knows where you connect and the servers are close to you (I&amp;rsquo;m talking about network distance here, how
many devices are between you and the server), so quite fast. However, in many countries providers are required by law to
filter some domains and you might not like it. Some providers even use DNS for more controversial purposes - if you type
a non-existent domain, instead of returning an error, they will redirect you to a website showing ads. It&amp;rsquo;s annoying in the
browser and could completely mess up other software.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s why the other option is even better: Cloudflare and the famous &amp;ldquo;1.1.1.1&amp;rdquo; server. It&amp;rsquo;s blazingly fast, faster than
Google and likely faster than your provider, despite being further from you. What&amp;rsquo;s even more important, Cloudflare
doesn&amp;rsquo;t log DNS queries. They have regular external audits to prove it.&lt;/p&gt;
&lt;h2 id="configuring-dhcp"&gt;Configuring DHCP
&lt;/h2&gt;&lt;p&gt;There needs to be exactly one DHCP server on the home network. Less than that and the devices won&amp;rsquo;t get an IP address
and won&amp;rsquo;t connect. More than that, they will get confusing replies and likely also won&amp;rsquo;t connect. Luckily, once the device gets the network configuration, it will keep it until it expires, so changing the DHCP server is simple and safe, provided
that you do it in the correct order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;log in to the router and Pi-hole web UI,&lt;/li&gt;
&lt;li&gt;check the DHCP settings on the router, but don&amp;rsquo;t disable it yet,&lt;/li&gt;
&lt;li&gt;enter the same settings on the Pi-hole (except for DNS, which should be the address of the Pi-hole), but don&amp;rsquo;t enable DHCP yet,&lt;/li&gt;
&lt;li&gt;now, disable DHCP on the router and quickly, before someone complains that the network doesn&amp;rsquo;t work, enable it on the Pi.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Enabling DHCP" class="gallery-image" data-flex-basis="466px" data-flex-grow="194" height="394" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/home/pi-hole/pihole-dhcp.png" width="766"&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a good idea to test. Get another device (not the one connected to the router and Pi) and force it to renew the IP, for example
by enabling and disabling airplane mode. You should see a client appearing on the Pi-hole DHCP server and DNS queries showing
up on the dashboard.&lt;/p&gt;
&lt;p&gt;Some time later, you will see the &amp;ldquo;Currently active DHCP leases&amp;rdquo; list getting longer. On the right of every entry, you can see two icons.
One to remove the device and the other to set a static lease (permanent address). It&amp;rsquo;s a bit unintuitive: clicking this will move the entry
to the list below (Static DHCP leases configuration), but you need to click the + button next.&lt;/p&gt;
&lt;h2 id="filtering"&gt;Filtering
&lt;/h2&gt;&lt;p&gt;Filters are configured in the Adlists tab. The default list is quite lax. I think that is right. If you use a browser extension such as
uBlock Origin, you can be aggressive in your filtering: if it causes a website to stop functioning properly, it&amp;rsquo;s easy to unblock.
In Pi-hole that&amp;rsquo;s more hassle, plus it&amp;rsquo;s filtering for the whole network which is probably used by less-technical users as well.&lt;/p&gt;
&lt;p&gt;What can we do if Pi-hole breaks the website (or we suspect that it does)? There are two options. Quick one: click &amp;ldquo;disable blocking&amp;rdquo;.
Precise one, see option &amp;ldquo;domains&amp;rdquo; in the left menu. You can use it to add a domain to a whitelist or blacklist, using ordinary text or
regular expressions.&lt;/p&gt;
&lt;p&gt;Filtering with DNS is not as effective as using a browser extension. You can&amp;rsquo;t block &lt;a class="link" href="https://example.com/ads/" target="_blank" rel="noopener"
 &gt;https://example.com/ads/&lt;/a&gt; while leaving the rest of &lt;a class="link" href="https://example.com/" target="_blank" rel="noopener"
 &gt;https://example.com/&lt;/a&gt; .
Of course, you can use both. Pi-hole can also increase security. Some malware connects to a specific domain. There are also lists of
domains used by scammers - fake lotteries, SMS subscriptions, etc. Since I also visit Polish websites, I use a list that also includes
Polish domains: &lt;a class="link" href="https://raw.githubusercontent.com/MajkiIT/polish-ads-filter/master/polish-pihole-filters/KADhosts.txt" target="_blank" rel="noopener"
 &gt;https://raw.githubusercontent.com/MajkiIT/polish-ads-filter/master/polish-pihole-filters/KADhosts.txt&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Configuring blocklists" class="gallery-image" data-flex-basis="389px" data-flex-grow="162" height="648" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/home/pi-hole/pihole-listy.png" srcset="https://selfhosting.too-many-machines.com/home/pi-hole/pihole-listy_hu_fea38f66826a067.png 800w, https://selfhosting.too-many-machines.com/home/pi-hole/pihole-listy.png 1052w" width="1052"&gt;&lt;/p&gt;
&lt;h2 id="local-dns"&gt;Local DNS
&lt;/h2&gt;&lt;p&gt;Recommended domain for local use is &lt;strong&gt;home.arpa&lt;/strong&gt; - not localdomain or example.com that many people use. To be fair, in most cases nothing bad would happen, but let&amp;rsquo;s do it by the book. Simply add your devices in Local DNS -&amp;gt; DNS Records. Pity there&amp;rsquo;s no option to automatically populate local DNS with DHCP entries.&lt;/p&gt;
&lt;h2 id="backup"&gt;Backup
&lt;/h2&gt;&lt;p&gt;I usually use Ansible to configure services, but this time I decided it was not worth it. You only configure Pi-hole once, no configuration can be
shared with other servers. It&amp;rsquo;s much faster to use a web UI.&lt;/p&gt;
&lt;p&gt;But it&amp;rsquo;s always a good idea to have backups. There are two ways to do it, for slightly different purposes. One is the option Teleporter in Settings.
It allows you to save or revert configuration. It generates a small file containing things like DHCP and DNS settings, blocklists URLs (but not the blocklists), manually added entries. It&amp;rsquo;s useful if you mess up the configuration or need to move the config to another device.&lt;/p&gt;
&lt;p&gt;The second option is to make an image of the micro-SD card. Take it out of Raspberry Pi, insert it into the reader, check (e.g. with lsblk) which device name
it got (I&amp;rsquo;ll use sde in the example):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dd &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/dev/sde bs&lt;span style="color:#f92672"&gt;=&lt;/span&gt;10M status&lt;span style="color:#f92672"&gt;=&lt;/span&gt;progress conv&lt;span style="color:#f92672"&gt;=&lt;/span&gt;fsync | gzip &amp;gt; raspbian-pihole.img.gz
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It writes a backup of the whole operating system. Useful if you break something on this level or your micro-SD card stops working. You can restore it with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;zcat raspbian-pihole.img.gz | dd of&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/dev/sde bs&lt;span style="color:#f92672"&gt;=&lt;/span&gt;10M status&lt;span style="color:#f92672"&gt;=&lt;/span&gt;progress conv&lt;span style="color:#f92672"&gt;=&lt;/span&gt;fsync
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you don&amp;rsquo;t know dd, the options are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if is the input file, stdin by default&lt;/li&gt;
&lt;li&gt;of is the output file, stdout by default&lt;/li&gt;
&lt;li&gt;bs stands for block size, set it to something large like 1M or 10M (probably no difference between the two, but the default is only 0.5KB) to increase throughput&lt;/li&gt;
&lt;li&gt;conv=fsync ensures that data and metadata are really written before the program exits,&lt;/li&gt;
&lt;li&gt;status=progress shows progress.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s enough to write it once, maybe repeat it occasionally (e.g. once a year or before/after an OS upgrade).&lt;/p&gt;</description></item><item><title>Why Self hosting?</title><link>https://selfhosting.too-many-machines.com/general/why-self-hosting/</link><pubDate>Fri, 14 Apr 2023 17:30:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/general/why-self-hosting/</guid><description>&lt;p&gt;I think we&amp;rsquo;re on the verge of a &amp;ldquo;back to the past&amp;rdquo; moment in IT. For the last 15 years or so, we heard that on-premises infrastructure is dead,
desktop software is not needed, everything should run on the cloud. Preferably as a collection of microservices, utilising NoSQL databases
and message queues.&lt;/p&gt;
&lt;p&gt;Recently it changed. I keep seeing articles about the advantages of monolithic architecture, relational DBs, applications that don&amp;rsquo;t embed the
browser, etc. Last but not least, more and more companies realised that the cloud is expensive, on-prem servers are back.
There is also a growing trend among hobbyists to run self-hosted services at home.&lt;/p&gt;
&lt;p&gt;Now, don&amp;rsquo;t get me wrong. All of these technologies have good uses, but they are not a solution for everything.&lt;/p&gt;
&lt;h2 id="good-and-bad-reasons"&gt;Good and bad reasons
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Education&lt;/strong&gt; is one good reason. Many IT professionals maintain a &amp;ldquo;home lab&amp;rdquo; to experiment with technology. Isn&amp;rsquo;t it convenient when the lab
can also do something useful?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Privacy&lt;/strong&gt; is another good reason. I see some people declaring that privacy is lost anyway and they might as well share all their personal information.
I see others that meticulously control all their data to make sure it never leaves a system they control. I&amp;rsquo;m neither. I don&amp;rsquo;t believe fully reclaiming
your data is even possible and the more you try to control it, the more you hit the law of diminishing returns. But some things can and should be
done.&lt;/p&gt;
&lt;p&gt;How about &lt;strong&gt;owning your data&lt;/strong&gt;? Cloud service can be discontinued at any moment. Small companies go out of business, corporations
decide to cut the margins. Especially Google is famous for killing many of their projects, even if they achieved a moderate success.
It is also a known problem with IoT devices - many people discovered their smart bulb or socket is no longer smart (or doesn&amp;rsquo;t work at all)
when it can&amp;rsquo;t connect to the manufacturer&amp;rsquo;s servers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Less reliance on the internet&lt;/strong&gt;. OK, I&amp;rsquo;m not making any plans for living offline, that would be way too uncomfortable. But if I have most
of my data and software locally, I can live with slow or unreliable connection. And yes, it is an issue even in the most developed regions
of the world. Perhaps you would like to live in a village or spend some time in an RV or a boat one day? Outside the towns, mobile
internet is often the only option. Just a few kilometres from the cell tower and you don&amp;rsquo;t even get that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cutting cost is not a good reason&lt;/strong&gt;. You may get some savings, but it&amp;rsquo;s not guaranteed. You might cut down on cloud subscriptions,
but add the cost of hardware and electricity. Not to mention the time spent on configuring all this.&lt;/p&gt;
&lt;h2 id="where-am-i-now"&gt;Where am I now?
&lt;/h2&gt;&lt;p&gt;I plan to write this as a step-by-step guide of my journey to self-hosting, like I&amp;rsquo;m starting from scratch. That is not strictly true.
I&amp;rsquo;m already running some of the services or I did that in the past. I&amp;rsquo;m going to start with the basic infrastructure first and then
add new services or better, tidier versions of those I&amp;rsquo;m already running.&lt;/p&gt;</description></item><item><title>(OUTDATED) Generating websites with Pelican</title><link>https://selfhosting.too-many-machines.com/internet/pelican/</link><pubDate>Mon, 27 Mar 2023 17:30:00 +0200</pubDate><guid>https://selfhosting.too-many-machines.com/internet/pelican/</guid><description>&lt;h2 id="july-2026-update"&gt;July 2026 update
&lt;/h2&gt;&lt;p&gt;I no longer use Pelican for my websites. I use another static generator: Hugo. It&amp;rsquo;s much faster and actively maintained. The results look much better too, especially for pages with many or large images. For text-heavy sites like this one, there&amp;rsquo;s not much difference.&lt;/p&gt;
&lt;p&gt;I decided to keep the old contents. All the generic information about static generator vs. CMS are still (or even more) valid for Hugo. And maybe someone who needs to use Pelican stumbles upon this site.&lt;/p&gt;
&lt;h2 id="old-contents-from-here"&gt;Old contents from here
&lt;/h2&gt;&lt;p&gt;I use static website generator called Pelican to build all my websites, unless I have a good reason not to. Why? Since laziness is one of the virtues of a good programmer, I asked ChatGPT why a static website generator is better then a CMS. Here&amp;rsquo;s a slightly edited answer:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Speed and Performance:&lt;/strong&gt; Static website generators offer incredible speed and performance compared to CMS. With a static website, there is no server-side processing required to generate pages, resulting in faster load times&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security:&lt;/strong&gt; Static websites are more secure than CMS because they have a smaller attack surface. Since there is no server-side processing involved in generating static sites, there are fewer points of entry for potential hackers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cost:&lt;/strong&gt; Static websites are generally cheaper to host and maintain than CMS. With CMS, there are ongoing costs associated with hosting, database maintenance, and plugin/theme updates. In contrast, static websites can be hosted anywhere.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version Control:&lt;/strong&gt; Static website generators are typically built with version control in mind, which allows developers to track changes, revert to previous versions, and collaborate with others on the same project.&lt;/p&gt;
&lt;p&gt;So there you have it, ChatGPT got right. Out of many static website generators, I chose Pelican because it&amp;rsquo;s easy to use and written in Python. To be frank, I never even looked at the source code because I never had to modify or extend anything, but it&amp;rsquo;s good to know that I can.&lt;/p&gt;
&lt;h2 id="but-i-dont-like-the-look-of-this-website"&gt;But I don&amp;rsquo;t like the look of this website
&lt;/h2&gt;&lt;p&gt;Could be, but it&amp;rsquo;s not the problem of Pelican. That&amp;rsquo;s because I like minimalistic designs and I&amp;rsquo;m not very good at them. You can have as much eye candy as you like.&lt;/p&gt;
&lt;h2 id="start-simple"&gt;Start simple
&lt;/h2&gt;&lt;p&gt;Static site generators can seem quite intimidating. You&amp;rsquo;re faced with thousands of configuration options, plugins and themes. But you don&amp;rsquo;t have to use all of these, definitely not from the start. My suggestion is to do some minimal configuration, add a bit of content, generate the website, repeat. For the developers: think of it as a one-person Scrum with 30 minutes sprint time.&lt;/p&gt;
&lt;h2 id="installing-pelican-and-initializing-the-website"&gt;Installing Pelican and initializing the website
&lt;/h2&gt;&lt;p&gt;I assume you already have Python installed. On Linux, it&amp;rsquo;s almost always true, every general purpose distribution comes with Python preinstalled. On Windows, you can get Python from python.org or Windows Store. Or you can use Anaconda on any platform if you prefer.&lt;/p&gt;
&lt;p&gt;A recommended way to install Pelican is using venv and pip. However, I had good results on Debian and Ubuntu with an ordinary system package - a simple &amp;ldquo;sudo apt install pelican&amp;rdquo; gives me a working setup in seconds.&lt;/p&gt;
&lt;p&gt;Optional step: create a GitHub repository for your website. Login to GitHub, click the &amp;ldquo;New repository&amp;rdquo; button, then clone it on your computer, either with your favourite IDE or &amp;ldquo;git clone&amp;rdquo; plus the URL pasted from GitHub. Remember to add &lt;em&gt;output/&lt;/em&gt; to .gitignore.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t want to use GitHub, simply make a directory and enter it. You can alway create a repo later if you change your mind.&lt;/p&gt;
&lt;p&gt;In the new directory, run &amp;ldquo;pelican-quickstart&amp;rdquo; and answer a few questions. Make sure to change the title, author, URL prefix, and timezone. I chose not to automatically upload the site, I wanted to test it locally first.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pelican init" class="gallery-image" data-flex-basis="439px" data-flex-grow="183" height="489" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/internet/pelican/pelican-init.png" srcset="https://selfhosting.too-many-machines.com/internet/pelican/pelican-init_hu_a11dbb04bd4a9875.png 800w, https://selfhosting.too-many-machines.com/internet/pelican/pelican-init.png 895w" width="895"&gt;&lt;/p&gt;
&lt;h2 id="choosing-a-theme"&gt;Choosing a theme
&lt;/h2&gt;&lt;p&gt;Have a look at &lt;a class="link" href="https://github.com/getpelican/pelican-themes" target="_blank" rel="noopener"
 &gt;https://github.com/getpelican/pelican-themes&lt;/a&gt;. At first I chose Bootstrap2, a simple theme with no dependencies. Soon I discovered I should switch to similar looking, but much more powerful Pelican-Bootrstrap3 which requires several plugins. Fine, I&amp;rsquo;ll need plugins anyway. Changing a theme is simple, so don&amp;rsquo;t worry that you choose wrong.&lt;/p&gt;
&lt;p&gt;Recommended way is to use git submodule to install the theme and plugins, but again I did it my way. Here&amp;rsquo;s my setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I keep all my websites in ~/Documents/web, eg. this one is ~/Documents/web/web-random&lt;/li&gt;
&lt;li&gt;In the same directory, I keep themes and plugins, in ~/Documents/web/pelican-themes and ~/Documents/web/pelican-plugins&lt;/li&gt;
&lt;li&gt;In each website&amp;rsquo;s directory, I created symlinks to themes and plugins&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd ~/Documents/web
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git clone --recursive https://github.com/getpelican/pelican-themes ./pelican-themes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git clone --recursive https://github.com/getpelican/pelican-plugins ./pelican-plugins
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd web-random
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ln -s ../pelican-themes .
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ln -s ../pelican-plugins .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I just added a few lines in configuration file pelicanconf.py:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PLUGIN_PATHS &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#39;pelican-plugins&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;THEME &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;pelican-themes/pelican-bootstrap3&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;BOOTSTRAP_THEME &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;flatly&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;JINJA_ENVIRONMENT &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {&lt;span style="color:#e6db74"&gt;&amp;#39;extensions&amp;#39;&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#39;jinja2.ext.i18n&amp;#39;&lt;/span&gt;]}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PLUGINS &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#39;i18n_subsites&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;I18N_TEMPLATES_LANG &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;en&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="adding-content"&gt;Adding content
&lt;/h2&gt;&lt;p&gt;Every file with .rst (ReStructuredText) or .md (Markdown) extension in the directory &lt;em&gt;content&lt;/em&gt; will be parsed by Pelican. I prefer Markdown since it&amp;rsquo;s also used on Github, Jupyter and many other places. Pelican considers every file an &lt;em&gt;article&lt;/em&gt; - something that is indexed by a publication date, has category, tags etc. Unless the file is in the &lt;em&gt;content/pages&lt;/em&gt; subdirectory - this one is for non-chronological content. So, if your website resembles a blog (like this one), you would have no or maybe several pages (eg. About) and many articles. If you don&amp;rsquo;t want to see newest content first, you would put everything in pages.&lt;/p&gt;
&lt;p&gt;Every article or page starts with a short metadata section. Here is an example taken from this post:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Title&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;How I build this site&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Date&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;2023-03-27&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Status&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;published&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Category&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Tags&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;it, python, website&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Slug&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;pelican&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Most of this is self explanatory. &lt;em&gt;Slug&lt;/em&gt; is the output filename if you want to use a non-default.&lt;/p&gt;
&lt;p&gt;For the non-blog site, you generally skip things like tags and category. You also have to choose one page as the default. Example from my personal site:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Title&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Too Many Machines&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Date&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;2023-03-27&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Status&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;published&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Summary&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Personal website of Igor Wawrzyniak&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;URL&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;save_as&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;index.html&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, after at least one empty line, follows the contents. You can use all of the standard Markdown syntax plus many extensions eg. from Github, such as syntax highlighting.&lt;/p&gt;
&lt;h2 id="previewing-the-site"&gt;Previewing the site
&lt;/h2&gt;&lt;p&gt;After adding some test content, I run two simple commands: make html &amp;amp;&amp;amp; make serve&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;igor@testvm:~/Dokumenty/web/web-random$ make html &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make serve
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;pelican&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/home/igor/Dokumenty/web/web-random/content&amp;#34;&lt;/span&gt; -o &lt;span style="color:#e6db74"&gt;&amp;#34;/home/igor/Dokumenty/web/web-random/output&amp;#34;&lt;/span&gt; -s &lt;span style="color:#e6db74"&gt;&amp;#34;/home/igor/Dokumenty/web/web-random/pelicanconf.py&amp;#34;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;15:37:32&lt;span style="color:#f92672"&gt;]&lt;/span&gt; WARNING Watched path does not exist: /home/igor/Dokumenty/web/web-random/content/images log.py:91
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Done: Processed &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; article, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; drafts, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; hidden articles, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; pages, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; hidden pages and &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; draft pages in 0.56 seconds.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;pelican&amp;#34;&lt;/span&gt; -l &lt;span style="color:#e6db74"&gt;&amp;#34;/home/igor/Dokumenty/web/web-random/content&amp;#34;&lt;/span&gt; -o &lt;span style="color:#e6db74"&gt;&amp;#34;/home/igor/Dokumenty/web/web-random/output&amp;#34;&lt;/span&gt; -s &lt;span style="color:#e6db74"&gt;&amp;#34;/home/igor/Dokumenty/web/web-random/pelicanconf.py&amp;#34;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Serving site at: http://127.0.0.1:8000 - Tap CTRL-C to stop
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So far so good. I&amp;rsquo;ve got the working website and it doesn&amp;rsquo;t look very ugly. Articles are indexed by tags, category and date.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pelican site preview" class="gallery-image" data-flex-basis="341px" data-flex-grow="142" height="847" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://selfhosting.too-many-machines.com/internet/pelican/pelican-screen.png" srcset="https://selfhosting.too-many-machines.com/internet/pelican/pelican-screen_hu_5bdc4d4774f49b15.png 800w, https://selfhosting.too-many-machines.com/internet/pelican/pelican-screen.png 1204w" width="1204"&gt;&lt;/p&gt;
&lt;h2 id="linking"&gt;Linking
&lt;/h2&gt;&lt;p&gt;There is already an image on this page and I didn&amp;rsquo;t explain how to add them. Pelican has a concept of static files - they are copied to output directory without any change. You can configure different types of static files, images are already configured by default. Simply copy the files you need into content/images and use them like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;![&lt;span style="color:#f92672"&gt;Pelican init&lt;/span&gt;](&lt;span style="color:#a6e22e"&gt;{static}/images/pelican-init.png&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item></channel></rss>