<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Storage-and-Backup | AkitaOnRails.com</title><link>https://akitaonrails.github.io/en/tags/storage-and-backup/</link><description>Fabio Akita's blog — tech, career, and assorted geek topics. English edition.</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 19 Oct 2026 23:20:52 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/en/tags/storage-and-backup/index.xml" rel="self" type="application/rss+xml"/><item><title>Linux users: always choose BTRFS, not EXT4</title><link>https://akitaonrails.github.io/en/2026/10/12/linux-users-always-choose-btrfs-not-ext4/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/10/12/linux-users-always-choose-btrfs-not-ext4/</guid><pubDate>Mon, 12 Oct 2026 23:00:00 -0300</pubDate><description>&lt;p&gt;This post is a direct sequel to the &lt;a href="https://akitaonrails.github.io/en/2026/10/06/disaster-on-my-nas-how-i-almost-lost-90-tb-of-data/"&gt;disaster on my NAS&lt;/a&gt;. Summary for newcomers: my Synology, with eight drives in SHR (single parity), lost two drives in one strike during an upgrade. I survived thanks to an SHR implementation detail, bought a QNAP in a hurry, spent a week copying and verifying 91 TB, and lost not a single file by accident. The new QNAP runs ZFS in RAID-6; the rebuilt Synology now runs SHR-2.&lt;/p&gt;</description><content:encoded><![CDATA[<p>This post is a direct sequel to the <a href="/en/2026/10/06/disaster-on-my-nas-how-i-almost-lost-90-tb-of-data/">disaster on my NAS</a>. Summary for newcomers: my Synology, with eight drives in SHR (single parity), lost two drives in one strike during an upgrade. I survived thanks to an SHR implementation detail, bought a QNAP in a hurry, spent a week copying and verifying 91 TB, and lost not a single file by accident. The new QNAP runs ZFS in RAID-6; the rebuilt Synology now runs SHR-2.</p>
<p>Underneath this whole story there&rsquo;s a decision almost nobody makes consciously: <strong>the filesystem</strong>. The Synology used btrfs all along. The QNAP uses ZFS. And your Linux machine, most likely, came from the factory with ext4 without anyone asking you anything. That&rsquo;s what this is about.</p>
<h2>Where the two came from<span class="hx:absolute hx:-mt-20" id="where-the-two-came-from"></span>
    <a href="#where-the-two-came-from" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>ext4 is the end of a dynasty of over three decades. ext (extended filesystem) was born with Linux itself, ext2 became the standard in the 90s, ext3 added a journal in 2001 so you wouldn&rsquo;t need an epic fsck after every power loss, and ext4 arrived in 2008 with extents, delayed allocation and volumes up to 1 exbibyte. The design goal was always the same: be the default filesystem, fast, simple and predictable. It&rsquo;s excellent at that. And it stopped there.</p>
<p>btrfs was born in 2007, at Oracle, with Chris Mason, with a completely different goal: be Linux&rsquo;s answer to ZFS. Instead of writing data over data, it&rsquo;s copy-on-write: every write goes to a new block and the pointers are updated cascading up the tree. That single decision is the mother of everything that matters in it.</p>
<p>What each one delivers, in a list:</p>
<p><strong>ext4:</strong></p>
<ul>
<li>journaling (consistent metadata after a crash)</li>
<li>extents and delayed allocation (good performance)</li>
<li>mature, stable, everywhere, every tool works</li>
</ul>
<p><strong>btrfs:</strong></p>
<ul>
<li>copy-on-write on everything</li>
<li>data and metadata checksums on every block</li>
<li>instant, cheap snapshots</li>
<li>independent subvolumes inside the same volume</li>
<li>transparent compression (zstd, lzo, zlib)</li>
<li>native RAID and scrub (verification sweep)</li>
<li>snapshot send/receive for incremental backup</li>
<li>reflinks (copies that don&rsquo;t duplicate blocks)</li>
</ul>
<p>The list already answers the title&rsquo;s question. Let&rsquo;s go piece by piece.</p>
<h2>Even NTFS and APFS have passed ext4<span class="hx:absolute hx:-mt-20" id="even-ntfs-and-apfs-have-passed-ext4"></span>
    <a href="#even-ntfs-and-apfs-have-passed-ext4" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>And if the comparison with btrfs seems unfair, look sideways. NTFS was born in 1993, with Windows NT, already with metadata journaling and ACLs, and back in the 2000s it gained a log of every change on the volume plus shadow copies (Windows&rsquo; &ldquo;Previous Versions&rdquo;). APFS, which Apple launched in 2017 made for flash, is copy-on-write like btrfs: native snapshots (it&rsquo;s what Time Machine uses for local restores), instant file clones that share blocks, volumes sharing the space of the same container, and per-file encryption.</p>
<p>Read the ext4 list again and compare: no snapshots, no data checksums, no CoW, no compression. Windows&rsquo; and Mac&rsquo;s filesystems, which nobody considers examples of sophistication, protect your data better than Linux&rsquo;s default, whose last big feature came out in 2008. btrfs is what puts Linux in the same generation as the other two, instead of a decade behind.</p>
<h2>Why btrfs is far superior<span class="hx:absolute hx:-mt-20" id="why-btrfs-is-far-superior"></span>
    <a href="#why-btrfs-is-far-superior" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>What copy-on-write is, for those who never stopped to think about it<span class="hx:absolute hx:-mt-20" id="what-copy-on-write-is-for-those-who-never-stopped-to-think-about-it"></span>
    <a href="#what-copy-on-write-is-for-those-who-never-stopped-to-think-about-it" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>In classic filesystems, writing is destructive by definition: updating a block means writing the new content over the old one, in the same place on disk. If the machine dies mid-write, the file ends up half old, half new. The ext3/ext4 journal covers that for <strong>metadata</strong> (the directory structure doesn&rsquo;t corrupt), but your data itself is left to luck.</p>
<p>Copy-on-write inverts the order: <strong>nothing is ever overwritten</strong>. Writing to a file allocates a new block, writes the new content there, and only when everything is safely on disk does the filesystem flip the pointers to the new version, atomically. At any instant, you have either the whole old version or the whole new version. Never a mix of the two.</p>
<p>What that buys in practice:</p>
<ul>
<li><strong>Nearly free snapshots.</strong> The old blocks already persist by CoW&rsquo;s nature; a snapshot is just the filesystem holding the root pointer of that instant, pure accounting.</li>
<li><strong>Power loss doesn&rsquo;t corrupt.</strong> An interrupted write leaves the previous version intact, because it was never touched. For anyone who has lost a VM image or a database to a mid-write crash, this is worth the whole filesystem.</li>
<li><strong>Verification becomes possible.</strong> Since each block is written once and never touched again, keeping its checksum forever makes sense. It&rsquo;s the base of the corruption protection I detail further down.</li>
</ul>
<p>The cost exists and I pay it knowingly: random-write-heavy files (VM disks, databases) fragment and duplicate blocks, because each rewrite becomes a new block. For those cases you disable CoW per directory with <code>chattr +C</code>, which must be set <strong>before</strong> the file is created. In my layout, the VM image made with the right <code>+C</code> stayed healthy; the one made without became the textbook fragmentation case.</p>
<h3>Snapshots are the feature that changes your life<span class="hx:absolute hx:-mt-20" id="snapshots-are-the-feature-that-changes-your-life"></span>
    <a href="#snapshots-are-the-feature-that-changes-your-life" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A btrfs snapshot is an instant photo of a subvolume, and it costs almost nothing, because CoW already keeps the old blocks while someone references them. Creating a snapshot is a seconds operation, not hours. In practice: before every upgrade, the system takes a photo. If the upgrade breaks, you don&rsquo;t &ldquo;uninstall packages&rdquo;: you boot back into the previous photo and you&rsquo;re done. The boot recovery section, further down, shows this happening for real.</p>
<h3>Subvolumes are the right way to organize the disk<span class="hx:absolute hx:-mt-20" id="subvolumes-are-the-right-way-to-organize-the-disk"></span>
    <a href="#subvolumes-are-the-right-way-to-organize-the-disk" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A subvolume is an independent namespace inside the same filesystem, with its own snapshot policy. And it has a killer property: <strong>snapshots are not recursive</strong>. A child subvolume inside another is automatically left out of the parent&rsquo;s snapshot.</p>
<p>I use this for everything. On my desktop, the cache directories that can be re-downloaded (mise, rustup, npm, opencode, AI caches) are becoming nested subvolumes inside <code>@home</code>: they stay in the same place for any program, but out of every snapshot. That separation alone takes 165 GiB out of snapshot scope, and the big caches (Hugging Face, yay, uv, pip) already live outside the system volume, with symlinks pointing to them: minus 148 GiB of snapshot, part already migrated and part in progress. The snapshot stays small, the restore stays fast, and whatever can be re-downloaded from the internet never enters the photo.</p>
<p>The real cost, measured on the new QNAP (ZFS, but the CoW math is the same): 27 hourly snapshots of a folder with almost 80 TB, and nearly all of them consume megabytes, because between one hour and the next almost nothing changed. Cheap snapshots are what let this routine exist without guilt.</p>
<p><img src="/2026/10/12/usuarios-linux-sempre-escolham-btrfs-nao-ext4/qnap-snapshot-manager.png" alt="QNAP Snapshot Manager with 27 hourly snapshots of the CHAD folder, most consuming between 3 and 300 MB"  loading="lazy" /></p>
<h3>Reflink deduplication<span class="hx:absolute hx:-mt-20" id="reflink-deduplication"></span>
    <a href="#reflink-deduplication" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>First, the caveat that matters: btrfs has no native online deduplication like ZFS (which keeps a dedup table in RAM and charges dearly for it). What it has are two practical things: <strong>reflink</strong> and offline dedup. Reflink is a copy that shares blocks: <code>cp --reflink=auto</code> creates a new file that points to the same blocks as the original, instant and free, and each one starts diverging only where written. In the era of dozens of checkouts, worktrees and similar builds, that saves a mountain of blocks. For retroactive dedup there are userspace tools like <a href="https://github.com/markfasheh/duperemove"target="_blank" rel="noopener">duperemove</a> and <a href="https://github.com/Zygo/bees"target="_blank" rel="noopener">bees</a>.</p>
<h3>Real corruption protection<span class="hx:absolute hx:-mt-20" id="real-corruption-protection"></span>
    <a href="#real-corruption-protection" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Every btrfs block, data or metadata, carries a checksum. When the filesystem reads and the checksum doesn&rsquo;t match, it knows the block rotted, and if there&rsquo;s a second copy (RAID or metadata DUP) it self-heals. ext4 checks no data at all: bit rot passes silently until you open the file years later.</p>
<p>Two pieces of my configuration show what that buys in practice, and what ext4 simply doesn&rsquo;t offer. The first is metadata in DUP mode. On ext4, each structure on the volume exists only once: a well-placed bad sector corrupts the directory table and the whole drive stops mounting. On btrfs, even with a single drive, every metadata block exists duplicated. The bad sector keeps existing, but the checksum flags the rotten block on read and the second copy answers in its place, without you noticing. It&rsquo;s the difference between losing the volume and noting a warning in the log.</p>
<p>The second is the monthly scrub. ext4 never re-reads anything to check health: if a block rots, you find out the day someone tries to open that specific file, maybe years later, when there&rsquo;s no fixing it anymore. The scrub is a scheduled sweep that re-reads the whole volume validating checksums, and finds the rot while it&rsquo;s still small and recoverable. When the <code>btrfs device stats</code> counter leaves zero, it&rsquo;s the siren to replace the drive before the disaster, not after.</p>
<h3>Transparent compression, measured<span class="hx:absolute hx:-mt-20" id="transparent-compression-measured"></span>
    <a href="#transparent-compression-measured" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Every mount on my desktop uses <code>compress=zstd:1</code>. Level 1 costs a few percentage points of one CPU core, and btrfs detects what doesn&rsquo;t compress (model blob, video) and writes it raw, at no cost. The result, measured this week on my two new drives:</p>
<p><img src="/2026/10/12/usuarios-linux-sempre-escolham-btrfs-nao-ext4/btrfs-compressao-economia.png" alt="Compression savings table: 2.2 TB of data taking 1.6 TB on disk, with 35% savings on the system, 39% on Docker layers and zero on AI model weights"  loading="lazy" /></p>
<p>That&rsquo;s <strong>630 GB of free space</strong> on 2.2 TB: 15% more drive, paying nothing. On the initial migration the gain was even bigger, 45% on the system volume, because there was more text and less binary. Notice the table&rsquo;s details: programs and libraries compress well, Docker layers compress even better (39%), text logs compress 4.5x, and AI model weights compress nothing, which is expected and costs nothing. Source code, which is what we produce the most in the AI era, compresses beautifully.</p>
<h2>Synology uses btrfs, QNAP uses ZFS<span class="hx:absolute hx:-mt-20" id="synology-uses-btrfs-qnap-uses-zfs"></span>
    <a href="#synology-uses-btrfs-qnap-uses-zfs" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Being fair to ZFS: for a big NAS it&rsquo;s superior, and it&rsquo;s what I chose for the QNAP. Native checksums and self-healing, mature RAIDZ, excellent send/receive. The price is complexity and resources: ZFS wants RAM for the ARC cache (my QNAP has 7 GB for a 174 TB pool, and I feel the lack), its online deduplication would need about 190 GB of RAM just for the table on my volume, and its provisioning model gave me a free scare when the snapshot was refused with 20 TB free (the thick vs thin story is in the NAS article).</p>
<p>btrfs is the right balance for a consumer machine. Light, native in the kernel, does almost everything ZFS does without demanding a storage administrator. Desktop, laptop, homelab, small or medium NAS: btrfs. Synology proved it in practice, twice: the btrfs volume survived readable with two drives out, and its snapshots saved 11 albums I deleted by mistake in the middle of the cleanup, byte by byte.</p>
<blockquote>
  <p>Big, serious NAS, with RAM to spare: ZFS. Everything else: btrfs. And for a personal Linux machine, it&rsquo;s not even a discussion.</p>

</blockquote>
<h2>Boot recovery via snapshot<span class="hx:absolute hx:-mt-20" id="boot-recovery-via-snapshot"></span>
    <a href="#boot-recovery-via-snapshot" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The feature that has saved me for real, told on both machines.</p>
<p>On the desktop, <a href="https://omarchy.org"target="_blank" rel="noopener">Omarchy</a> configures <a href="https://snapper.io/"target="_blank" rel="noopener">snapper</a> to take a snapshot before every update and <code>limine-snapper-sync</code> publishes each snapshot as a boot entry in <a href="https://github.com/limine-bootloader/limine"target="_blank" rel="noopener">Limine</a>. New kernel won&rsquo;t come up? In the boot menu you pick yesterday&rsquo;s snapshot and the machine comes back as if nothing happened. On my machine the scheduled routine runs via <a href="https://github.com/teejee2008/timeshift"target="_blank" rel="noopener">Timeshift</a> on cron (every boot and hourly), with snapper covering the update points: the way back is the same mechanism. (The gotcha: each snapshot entry carries the kernel image along, so a small ESP fills up fast. My 1 GB one already refused a kernel update for lack of space; the system stayed bootable on the old kernel, which is the right behavior.)</p>
<p>On the server, which runs <a href="https://get.opensuse.org/microos/"target="_blank" rel="noopener">openSUSE MicroOS</a>, it&rsquo;s even stronger: the whole system is transactional btrfs, and every update is a new snapshot. Last week, kernel 7.2.9 took down the NVMe controller silently, twice, in the middle of the nightly backup. The fix was a <code>transactional-update rollback</code> and a reboot back to 7.2.6, which ran the whole load (754 GiB of backup reads with concurrent download) without a hiccup. A kernel regression became a scheduled reboot, not a recovery all-nighter. One documented caveat: <code>/etc</code> lives inside the snapshot, so the rollback undoes configuration changes along with it. After any rollback, check your units again.</p>
<h2>Maintenance is not optional (and the deleted file that won&rsquo;t go away)<span class="hx:absolute hx:-mt-20" id="maintenance-is-not-optional-and-the-deleted-file-that-wont-go-away"></span>
    <a href="#maintenance-is-not-optional-and-the-deleted-file-that-wont-go-away" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A modern CoW filesystem delivers a lot, but it charges two routines ext4 never asked for.</p>
<p>The first is the scrub, which already appeared here: re-read everything and check checksums, monthly, on all machines. It&rsquo;s bit-rot detection in action, and without it the checksum only warns you about the rot when you try to read the file. The second point is subtler: <strong>a snapshot pins the blocks of deleted files</strong>. When you delete a file inside a snapshotted subvolume, the space doesn&rsquo;t come back, because the block stays referenced by the photo. That&rsquo;s exactly what happened on my NAS: the 2.14 TB cleanup I ran on the Synology only freed space for real after the old snapshots were removed; until then, the blocks stayed pinned.</p>
<p>The treatment is simple routine. Retention has to be automatic, with snapper or Timeshift deleting the old snapshots per policy (on the QNAP, the Snapshot Manager&rsquo;s retention policy plays the same role). Space that doesn&rsquo;t come back after deleting old snapshots is usually over-allocated chunks: a <code>btrfs balance start -dusage=50</code> returns the little-used pieces to the free pool (in my house I don&rsquo;t blindly schedule balance, I monitor the unallocated space and act when it gets tight). And on the restic side, <code>forget</code> alone only removes the snapshot&rsquo;s reference: the repository space is only reclaimed with <code>prune</code> along, <code>forget --prune</code>.</p>
<h2>House cleaning in the AI coding era<span class="hx:absolute hx:-mt-20" id="house-cleaning-in-the-ai-coding-era"></span>
    <a href="#house-cleaning-in-the-ai-coding-era" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Now the part almost nobody connects with filesystems: the agent era produces garbage at industrial speed. I know because I measured.</p>
<ul>
<li><strong>Rust is the biggest disk polluter in the dev world.</strong> Every build leaves behind 100 to 180 MB test binaries, and they accumulate with each edit. On my desktop, the <code>target/</code> directories added up to <strong>920 GiB</strong>. ai-memory alone accounted for 704 GiB. The hygiene tool I was using is <a href="https://github.com/holmgr/cargo-sweep"target="_blank" rel="noopener">cargo-sweep</a>, weekly. A reader pointed out that the project declares itself unmaintained, and its own README points to <a href="https://github.com/dnlmlr/cargo-clean-all"target="_blank" rel="noopener">cargo-clean-all</a>, which works differently: instead of pruning old artifacts inside one <code>target/</code>, it finds every Rust project under a directory and deletes the whole <code>target/</code> of the ones you filter, for example <code>cargo clean-all --keep-days 7 ~</code> to clean only what hasn&rsquo;t been compiled in a week.</li>
<li><strong>Docker is right behind.</strong> On my machine I found 1.26 TB of build cache plus 938 GB of images. A single cleanup recovered about 600 GB. On the server, the weekly cleanup collects 0.6 to 4.5 GB every Sunday.</li>
<li><strong>AI and package caches bloat quietly.</strong> The Hugging Face cache had 86 GB until I deleted three dead models and it dropped to 21. Pacman had 22 GB of outdated packages. yay, uv and pip, another 125 GB.</li>
<li><strong>And agents create temporary directories without asking permission.</strong> A complex project with several branches means worktrees, and each release worktree has its own <code>CARGO_TARGET_DIR</code> full of test binaries. On ai-memory that has already blown up the disk, and it became a mandatory rule in the project&rsquo;s AGENTS.md: after every release, <code>cargo clean</code> in each created worktree and removal of finished worktrees, no exceptions.</li>
</ul>
<p>The solution is to automate the sweeping. My minimal example, a user timer that runs on the 1st and 15th at dawn, with Nice and idle I/O:</p>
<ul>
<li>deletes <code>target/</code> directories of projects idle for more than 14 days</li>
<li>cleans the yay cache</li>
<li>prunes only Docker build cache older than 30 days, never images or volumes</li>
</ul>
<p>And the part that became standard in my house: <strong>instruct the agents to clean up what they create</strong>. Three real rules from my projects, for you to copy:</p>
<blockquote>
  <p><strong>In the desktop&rsquo;s AGENTS.md:</strong> &ldquo;Never run broad cleanups like <code>docker system prune</code> without explicit user authorization after a read-only inventory. Cleanup uses an exact list of IDs; idle, old or unreferenced does not mean disposable.&rdquo;</p>

</blockquote>
<blockquote>
  <p><strong>In the homelab&rsquo;s CLAUDE.md:</strong> &ldquo;Before finishing any task, remove the temporary things the task created, and only those. Never delete what you didn&rsquo;t create or can&rsquo;t attribute to the task. When in doubt, list it for the owner.&rdquo;</p>

</blockquote>
<blockquote>
  <p><strong>In ai-memory&rsquo;s AGENTS.md:</strong> &ldquo;<code>target/</code> grows without limit: every edit to a shared crate leaves behind a copy of each 100-180 MB test binary (seen at 157 GiB). Weekly <code>cargo sweep --time 7</code>, and mandatory <code>cargo clean</code> after every release.&rdquo;</p>

</blockquote>
<h2>And the real backup: restic<span class="hx:absolute hx:-mt-20" id="and-the-real-backup-restic"></span>
    <a href="#and-the-real-backup-restic" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A snapshot lives on the same drive, so a snapshot is not a backup: if the drive dies, the snapshot dies with it. The last item in the kit is <a href="https://restic.net/"target="_blank" rel="noopener">restic</a>, which for me is the best backup system on Linux today.</p>
<p>restic is a single open source binary, written in Go, that does deduplicated, encrypted and versioned backup to practically any destination: another directory, another NAS via SFTP or REST, S3, Glacier. The central trick is content-defined chunking: it slices each file into variable-size blocks defined by the content, hashes each block and only writes to the repository the blocks that don&rsquo;t exist there yet. That&rsquo;s why the delta is cheap: today&rsquo;s backup carries the blocks that changed since yesterday, not the files that changed. Editing a 100 GB database in the middle costs a few megabytes of backup, not 100 GB again. Each run becomes a repository snapshot, a restore point you browse with <code>restic mount</code> as if it were a filesystem, and <code>restic check</code> audits the whole repository&rsquo;s integrity whenever you ask.</p>
<p>Configuration matters more than the tool, so here&rsquo;s ours, mistakes included. On the desktop, the repository lives on the NAS and retention is <code>keep-daily 7, keep-weekly 4, keep-monthly 6, keep-yearly 1</code>. On the server, backup runs daily at dawn to the repository on the QNAP, covering <code>/etc</code>, <code>/usr/local/bin</code>, <code>/home</code> and the container volumes, with the password off the machine and <code>pg_dump</code> before the databases, because a filesystem snapshot doesn&rsquo;t guarantee consistency for a database that&rsquo;s writing. The first full load was 729 GiB in under an hour, and restic crossed unharmed, on retries, the window in which the NFS mount went read-only by mistake during the migration.</p>
<p>The most important part of the configuration is what does <strong>not</strong> go into the backup. Our exclusions, all with a reason:</p>
<ul>
<li>AI model caches (Immich, Hugging Face, EasyOCR): if lost, download again;</li>
<li>Prometheus metrics: history that regenerates itself;</li>
<li>the Fulcrum database: reindexes from bitcoind;</li>
<li>the decision classifiers&rsquo; weights: re-downloadable by revision hash (and this exclusion&rsquo;s glob goes inside quotes inside the bash array, because unquoted it becomes a backup source instead of an exclusion);</li>
<li><code>node_modules</code>, <code>target/</code>, cargo registry, rustup toolchains: pure build artifacts.</li>
</ul>
<p>And one retention lesson to close. When we shortened the server policy to &ldquo;yesterday, 2 dailies, 2 weeklies and 1 monthly&rdquo;, the correct translation to flags is <code>--keep-daily 3 --keep-weekly 3 --keep-monthly 2</code>, not 2/2/1: restic&rsquo;s levels overlap, and yesterday&rsquo;s snapshot is also the newest of this week and this month, so each number needs to be one more than the older points you want to keep. The detail caught us off guard: the script had a literal 2/2/1, and with it the monthly level would never hold anything older than last night. In practice, the intention was to keep 5 restore points, and 2/2/1 delivers only 3: yesterday, plus one daily, plus one weekly. We erred, measured, corrected.</p>
<p>A humility note from the disaster: the old repositories were left on the Synology by my decision (almost 2 TB of abandoned history, because the content was already re-covered on the new QNAP). A backup repository ages too, and revisiting its scope is part of the hygiene.</p>
<h2>Conclusion: in the AI era, disk is not a detail<span class="hx:absolute hx:-mt-20" id="conclusion-in-the-ai-era-disk-is-not-a-detail"></span>
    <a href="#conclusion-in-the-ai-era-disk-is-not-a-detail" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agents coding all day change the storage math at both ends. On the volume end: builds, caches, worktrees and models fill the disk much faster than any human habit ever did, and without a cleanup routine you find out when it blows up. On the safety end: the more generated code, the more you need a cheap way back, because not every agent change is a good one.</p>
<p>btrfs covers both. Transparent compression gives back 15% of disk for free, and source code is exactly the kind of data that compresses the most. Snapshots and subvolumes give you instant rollback on the system and isolation for what doesn&rsquo;t need protection. Checksums and scrub watch the health of blocks that ext4 simply ignores. ext4 had its 15 years of well-deserved reign. In 2026, choosing it on a new Linux is giving up protection you&rsquo;ll need, sooner or later. Choose btrfs and set up the cleanup today.</p>
]]></content:encoded><category>linux</category><category>storage-and-backup</category><category>homelab</category><category>omarchy</category></item><item><title>Disaster on My NAS! How I Almost Lost 90 TB of Data</title><link>https://akitaonrails.github.io/en/2026/10/06/disaster-on-my-nas-how-i-almost-lost-90-tb-of-data/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/10/06/disaster-on-my-nas-how-i-almost-lost-90-tb-of-data/</guid><pubDate>Tue, 06 Oct 2026 12:00:00 -0300</pubDate><description>&lt;p&gt;Yesterday, Monday, I was woken up at 8 a.m. by a loud, insistent beep. I knew it was the NAS before I opened my eyes. I ran to check, and nearly lost it: two drives marked as critical, in an array that can never have more than one.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-2-discos-criticos.png" alt="Synology panel showing Storage Pool 1 in critical state, with drives 1 and 8 marked Critical" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;At that moment I knew I was a hiccup away from losing everything. This post is the post-mortem of what happened, and the first part was written in the heat of the disaster, with the rescue copy still running.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Yesterday, Monday, I was woken up at 8 a.m. by a loud, insistent beep. I knew it was the NAS before I opened my eyes. I ran to check, and nearly lost it: two drives marked as critical, in an array that can never have more than one.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-2-discos-criticos.png" alt="Synology panel showing Storage Pool 1 in critical state, with drives 1 and 8 marked Critical"  loading="lazy" /></p>
<p>At that moment I knew I was a hiccup away from losing everything. This post is the post-mortem of what happened, and the first part was written in the heat of the disaster, with the rescue copy still running.</p>
<blockquote>
  <p><strong>Update of October 11:</strong> the copy finished and the ending was a happy one. No file was lost by accident, both NASes are healthy, each with its own RAID-6. The full recovery is in the <a href="#six-days-later-the-end-of-the-story">second half of this post</a>. The first part stays as it was published, untouched.</p>

</blockquote>
<h2>I&rsquo;ve been using a NAS for a long time<span class="hx:absolute hx:-mt-20" id="ive-been-using-a-nas-for-a-long-time"></span>
    <a href="#ive-been-using-a-nas-for-a-long-time" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I&rsquo;m not new to this. I made an <a href="https://www.youtube.com/playlist?list=PLdsnXVqbHDUcM0LTAxqrVrTy6Q7jQprjt"target="_blank" rel="noopener">entire video mini-series on storage and filesystems</a> (in Portuguese), and I recommend watching it before asking me &ldquo;what&rsquo;s a good beginner NAS?&rdquo;. There is no such thing.</p>
<p>I&rsquo;ve used external storage since the second half of the 2000s, when I was a Mac user and Drobo existed. The company is gone, but I had three of their DAS units before migrating to Synology, first on an entry-level model and then on the mid-tier DS1821+, which I&rsquo;ve been using for more than five years. I&rsquo;ve written about it here, <a href="/en/2025/04/17/configuring-my-synology-nas-with-nfs-on-linux/">setting up NFS on Linux</a> and <a href="/en/2025/04/24/accessing-your-nas-using-iscsi-instead-of-smb/">accessing it over iSCSI</a>.</p>
<p>It&rsquo;s been very valuable to me, and you don&rsquo;t get to question why I need that much space. It doesn&rsquo;t matter: everyone decides what they keep on theirs.</p>
<p>When I was producing YouTube videos, I edited 4K footage straight from the NAS, thanks to the NVMe cache and 10 GbE networking. I got used to big storage at 10 gigabits and I can&rsquo;t go back.</p>
<p>The array was organized in SHR, Synology&rsquo;s hybrid RAID, which in practice behaves like RAID-5 (more on that later): it survives the loss of one drive without losing any data. I always thought that was enough.</p>
<h2>Hard drives die. Always<span class="hx:absolute hx:-mt-20" id="hard-drives-die-always"></span>
    <a href="#hard-drives-die-always" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Make no mistake: hard drives go bad. Never, ever trust a lone drive forgotten in a closet. It will fail, and you will lose everything on it.</p>
<p>The best public data on this comes from Backblaze, which runs hundreds of thousands of drives and publishes failure rates every quarter. In the <a href="https://www.backblaze.com/blog/backblaze-drive-stats-for-q2-2026/"target="_blank" rel="noopener">Q2 2026 report</a>, with 354,415 drives monitored, the annualized rate was 1.73%, and the lifetime rate for the whole fleet stands at 1.41% per year.</p>
<p>That sounds small. Translated to human scale:</p>
<ul>
<li><strong>One drive on its own, for 5 years:</strong> the chance of it dying in that period is close to 7%. That&rsquo;s roughly the chance of rolling one specific number on a 14-sided die. Would you bet every family photo on that?</li>
<li><strong>One drive on its own, for 10 years:</strong> about 13%, one in eight.</li>
<li><strong>Eight drives, like my NAS, for one year:</strong> the chance of at least one failing is about 11%. In five years, more than 40%.</li>
</ul>
<p>And those numbers are from a datacenter, with controlled temperature, clean power and drives spinning all the time. A drawer drive that sits idle for years and gets knocked around in a move doesn&rsquo;t have statistics that pretty.</p>
<p>Memory cards are worse, and there isn&rsquo;t even an equivalent statistic to consult. What exists is torture testing: the <a href="https://www.bahjeez.com/the-great-microsd-card-survey-one-year-later/"target="_blank" rel="noopener">Great MicroSD Card Survey</a> tested 216 cards over a year of continuous writing, and 52 of them, one in four, had already died.</p>
<p>Genuine cards started throwing errors, on average, after about 2,500 rewrite cycles; counterfeit cards, after 700. A memory card is transport media. Keeping the only copy of anything on a microSD is asking to lose it.</p>
<p>The practical conclusion is the same as in the mini-series: important data needs redundancy. A parity array exists precisely so that the death of one drive, which is a matter of time, doesn&rsquo;t take anything with it.</p>
<h2>What happened<span class="hx:absolute hx:-mt-20" id="what-happened"></span>
    <a href="#what-happened" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>With all that, I should have been at ease with my setup. I wasn&rsquo;t.</p>
<p>It started the day before yesterday, Sunday. For a while I&rsquo;d been watching the volume approach the ceiling.</p>
<p>At first it was 8 drives of 10 TB. Over the years I swapped them, one by one, for 20 TB drives (DSM shows binary units; on the label they&rsquo;re 12 and 22 TB). It&rsquo;s a slow process: each swap takes days, because the array has to rebuild and redistribute the data.</p>
<p>I decided to swap one more, the seventh. Synology supports hot-swap, so it&rsquo;s simple: pull the old drive, slot in the new one, add it to the array and let DSM handle the rebuild.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-pool-reparando.png" alt="Synology Storage Pool 1 repairing, 0.01% complete, with 108 TB allocated out of 121.8 TB"  loading="lazy" /></p>
<p>On Sunday night it looked like this: rebuild starting, all eight drives healthy, 108 TB allocated out of 121.8 TB.</p>
<p>On Monday, at 8 a.m., the beep. Drive 1, the new one, with 13 hours of use, marked critical:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-drive1-critico.png" alt="Synology drive 1 in critical state, with an I/O error and 13 hours powered on"  loading="lazy" /></p>
<p>And drive 8, one of the old ones, with 22,095 hours powered on (two and a half years), also critical, with a read error at 08:05:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-drive8-critico.png" alt="Synology drive 8 in critical state, with a read error and the message that the number of failed drives exceeds the RAID’s fault tolerance"  loading="lazy" /></p>
<p>The pool&rsquo;s message left no room: &ldquo;Unrecoverable errors have occurred because drive errors occurred after storage pool degradation. Please back up your data immediately.&rdquo;</p>
<h3>Why two drives is the end<span class="hx:absolute hx:-mt-20" id="why-two-drives-is-the-end"></span>
    <a href="#why-two-drives-is-the-end" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>In RAID-5, if two drives go down, redundancy is zero, and in theory the whole array goes with them. It&rsquo;s an unrecoverable situation.</p>
<p>Anyone who watched my playlist knows why. An array like this doesn&rsquo;t store &ldquo;files,&rdquo; one per drive. A file is a collection of blocks, and the blocks are spread across all the drives, along with the parity. Losing a drive is losing a piece of every file. It doesn&rsquo;t matter that the other seven are healthy, because they only have parts.</p>
<p>Parity is there to recompute the missing piece when one drive disappears. With two missing, the math doesn&rsquo;t close.</p>
<p>And there&rsquo;s a cruel detail in the rebuild: to recreate the new drive, the array has to read every other drive end to end. That&rsquo;s more than 100 TB of continuous reading. If some old drive had a bad sector hiding in a corner nobody had read in a long time, that&rsquo;s when it shows up. That&rsquo;s what happened with drive 8.</p>
<h2>Where do you copy 90 TB to?<span class="hx:absolute hx:-mt-20" id="where-do-you-copy-90-tb-to"></span>
    <a href="#where-do-you-copy-90-tb-to" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The only way out, at that point, was to thank the heavens that the volumes were still accessible, with the files showing up, and use the window to copy everything out immediately. I couldn&rsquo;t touch anything else.</p>
<p>The problem: copy about 90 TB to where? Nobody has an empty 100 TB NAS lying around at home.</p>
<p>Normally I&rsquo;d buy this kind of hardware in the United States, on a tourist trip, and bring it back with me. It&rsquo;s much cheaper. Buying expensive electronics in Brazil isn&rsquo;t worth it: the country has charged import taxes worse than Trump&rsquo;s tariffs for decades, and the final price usually goes past double the American list price.</p>
<p>Except I had to choose. What is it worth to lose 90 TB of data I spent years collecting and that is nearly impossible to recover from anywhere else? It&rsquo;s one of those situations where the answer is &ldquo;it&rsquo;s priceless,&rdquo; and so, whatever the price, I pay.</p>
<p>I found a storage reseller close to home, <a href="https://www.controle.net"target="_blank" rel="noopener">Controle Net</a>. Good people, very quick to respond, and they sent me a proposal for equipment with same-day delivery.</p>
<p>Their first suggestion was a QNAP TS-832PX. I researched it before accepting. It has the same 8 bays and ships with 10 GbE, but the processor is a 1.7 GHz ARM Cortex-A57 (Annapurna Labs AL324) with 4 GB of RAM, half the 8 GB minimum that QuTS hero asks for. For RAID-6 on ZFS, with checksums and double parity on every block, that&rsquo;s weak hardware, as I explain further down. And it would be a downgrade from the DS1821+ I already had, which runs on a Ryzen V1500B.</p>
<p>I asked for another option, and after some back and forth I settled on a QNAP TS-873A, 8 bays, with the same Ryzen V1500B as my Synology, 8 GB of RAM, and 10 Toshiba MG11 24 TB enterprise-class drives.</p>
<p>This is why you have to do your own research and know, objectively, what you want to do with the equipment. The reseller didn&rsquo;t act in bad faith: they suggested an 8-bay NAS to someone who asked for an 8-bay NAS. The one who knew it would run ZFS in RAID-6 was me. If I had accepted the first proposal in a panic, I&rsquo;d have paid a lot for a worse machine than the one I was replacing.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-proposta-qnap.png" alt="Reseller proposal: QNAP TS-873A, 10 Toshiba MG11ACA24TE 24 TB drives, installation, training and 6 years of support, total R$ 185,320.00"  loading="lazy" /></p>
<p>R$ 185,320.00. Eye-watering.</p>
<h3>What it would cost in the United States<span class="hx:absolute hx:-mt-20" id="what-it-would-cost-in-the-united-states"></span>
    <a href="#what-it-would-cost-in-the-united-states" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I checked today&rsquo;s American list prices. About the exchange rate: the dollar is at R$ 4.97 right now, but that&rsquo;s the effect of Sunday&rsquo;s first-round election results, which knocked the rate down more than 4% on Monday. Through Friday it closed at R$ 5.22, and that&rsquo;s the fair rate to compare with, because that&rsquo;s the rate the reseller&rsquo;s stock was built at:</p>
<table>
  <thead>
      <tr>
          <th>Item</th>
          <th style="text-align: right">US price</th>
          <th style="text-align: right">Total</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>QNAP TS-873A-8G (<a href="https://store.qnap.com/ts-873a-8g-us.html"target="_blank" rel="noopener">QNAP store</a>, Newegg)</td>
          <td style="text-align: right">US$ 1,199</td>
          <td style="text-align: right">US$ 1,199</td>
      </tr>
      <tr>
          <td>10 × Toshiba MG11ACA24TE 24 TB (<a href="https://www.bhphotovideo.com/c/product/1889900-REG/toshiba_mg11aca24te_24tb_mg10_series_7200.html"target="_blank" rel="noopener">B&amp;H</a>)</td>
          <td style="text-align: right">US$ 999.99 each</td>
          <td style="text-align: right">US$ 9,999.90</td>
      </tr>
      <tr>
          <td>10 GbE card QXG-10G2T-X710 (<a href="https://www.bhphotovideo.com/c/product/1643883-REG/qnap_qxg_10g2t_x710_dual_port_10gbase_t_10gbe_network.html"target="_blank" rel="noopener">B&amp;H</a>)</td>
          <td style="text-align: right">US$ 351.99</td>
          <td style="text-align: right">US$ 351.99</td>
      </tr>
      <tr>
          <td><strong>Total</strong></td>
          <td style="text-align: right"></td>
          <td style="text-align: right"><strong>~US$ 11,550</strong></td>
      </tr>
  </tbody>
</table>
<p>At R$ 5.22, about R$ 60 thousand. I paid R$ 185 thousand, more than three times that.</p>
<p>The comparison isn&rsquo;t perfect: the Brazilian proposal includes installation, training and six years of technical support, and the equipment was on my desk by the end of the same day. But it gives the measure of the Brazil effect.</p>
<p>If the rule of thumb is &ldquo;double the American price,&rdquo; normal would be about R$ 120 thousand. The extra R$ 65 thousand is, in my own rough estimate, the sum of services, local stock of enterprise drives and my urgency. Whoever buys on a Monday morning with the array dying is in no position to haggle.</p>
<h3>And drives are expensive everywhere<span class="hx:absolute hx:-mt-20" id="and-drives-are-expensive-everywhere"></span>
    <a href="#and-drives-are-expensive-everywhere" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>To make it worse, this is the worst possible moment to buy hard drives. The AI bubble is sucking up production.</p>
<table>
  <thead>
      <tr>
          <th>Indicator</th>
          <th>Before</th>
          <th>Now</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>WD Blue 4 TB (Camelcamelcamel history)</td>
          <td>US$ 67 to 85</td>
          <td>US$ 99, <a href="https://winbuzzer.com/2026/02/18/wd-seagate-2026-hard-drive-shortage-ai-data-centers-xcxwbn/"target="_blank" rel="noopener">nearly 50% more in five months</a> (February 2026)</td>
      </tr>
      <tr>
          <td>Production capacity</td>
          <td>available at retail</td>
          <td>WD: 2026 practically sold out, with contracts through 2027 and 2028; Seagate: nearline capacity fully allocated</td>
      </tr>
      <tr>
          <td>WD gross margin</td>
          <td>41.0% a year earlier</td>
          <td><a href="https://datacenterdisk.com/news/hard-drive-prices-up-50-percent-2026"target="_blank" rel="noopener">54.1% in the quarter ended July 2026</a></td>
      </tr>
  </tbody>
</table>
<p>I thought Seagate had left the consumer market. I checked, and that&rsquo;s not quite it: it hasn&rsquo;t left, but in February 87% of its hard drive sales were already nearline drives, for datacenters, and WD gets 89% of its revenue from cloud customers and only 5% from retail. WD&rsquo;s CEO said the company is &ldquo;pretty much sold out for calendar year 2026.&rdquo;</p>
<p>Seagate has said it won&rsquo;t expand production capacity; growth comes only from higher-density drives. For someone buying drives at home, the practical effect is similar to them having left: you get what the hyperscalers didn&rsquo;t want, at whatever price the seller asks.</p>
<h2>QNAP instead of another Synology<span class="hx:absolute hx:-mt-20" id="qnap-instead-of-another-synology"></span>
    <a href="#qnap-instead-of-another-synology" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>After spending the morning and afternoon between negotiation, paperwork and payment, by the end of the day I had the QNAP powered on next to the Synology.</p>
<p>Choosing QNAP went beyond availability. Synology has been making questionable business decisions.</p>
<p>In 2025 it launched the Plus line <a href="https://www.tomshardware.com/pc-components/nas/synology-walks-back-controversial-compatibility-policy-for-2025-nas-units-third-party-hdd-and-ssd-support-returns-with-diskstation-manager-7-3-update"target="_blank" rel="noopener">requiring its own-brand drives or drives it certified</a>.</p>
<p>The own-brand ones are Seagate, Toshiba and WD drives rebadged with custom firmware, and anyone using something else was left without pool creation, health monitoring, deduplication or firmware updates. After the outcry, it <a href="https://www.howtogeek.com/synology-is-walking-back-its-drive-requirements/"target="_blank" rel="noopener">walked that back in DSM 7.3</a> and allowed HDDs and SATA SSDs from other brands.</p>
<p>Except M.2 pools still require the compatibility list. The uncertainty is left hanging: what stops them from doing it again?</p>
<p>Setting up the QNAP is very easy. QuTS hero, their ZFS-based system, is as simple to use as Synology&rsquo;s DSM.</p>
<h2>Fixing my mistake: RAID-6 and ZFS<span class="hx:absolute hx:-mt-20" id="fixing-my-mistake-raid-6-and-zfs"></span>
    <a href="#fixing-my-mistake-raid-6-and-zfs" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>This time I knew I had to configure RAID-6, which survives two drives failing at the same time. The downside is less total capacity, because the equivalent of two drives becomes parity instead of one. The upside is that I shouldn&rsquo;t have to go through an emergency like this again.</p>
<p>One of QNAP&rsquo;s strengths is supporting ZFS out of the box, well integrated and easy to use.</p>
<p>For those who don&rsquo;t know it: ZFS was born at Sun Microsystems, for Solaris, in the mid-2000s, and today lives on as OpenZFS on Linux and FreeBSD. It merges into one system what used to be three layers, the RAID, the volume manager and the filesystem. It keeps a checksum of every block, so it notices when a drive silently returns corrupted data, and repairs it on its own from parity. And it never overwrites data in place: every write goes to a new block, which makes snapshots practically free.</p>
<p>Its reputation as a RAM hog comes from two things. The first is the ARC, its cache, which by design takes up almost all the machine&rsquo;s free memory. That scares anyone looking at the graph, but it&rsquo;s cache: it gives the memory back when another process asks. The second is deduplication, which keeps a huge table with the hash of every block and needs it in RAM or it becomes unusably slow. The old rule of &ldquo;1 GB of RAM per TB of disk&rdquo; comes from there, and only applies to people who turn deduplication on.</p>
<p>Even without it, ZFS is heavier than ext4 or a simple Btrfs. Computing checksums, compression and double parity for every block costs CPU, and with little memory the cache shrinks and everything gets slow. On weak hardware, like an entry-level NAS with an ARM processor and 2 GB of RAM, it suffers.</p>
<p>QNAP asks for at least 8 GB for QuTS hero. Mine came with 8 GB and a quad-core Ryzen, which works for my use, which is storing large files and serving them over the network. Deduplication stays off.</p>
<p>I bought 10 drives for an 8-bay NAS, so two are left in the drawer as spares, for the day one fails.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-qnap-criando-pool-raid6.png" alt="QNAP storage pool creation wizard: 8 disks in RAID 6, 126 TB, 5% over-provisioning, 5% guaranteed snapshot space and an 85% alert threshold"  loading="lazy" /></p>
<p>For my simple setup, three settings:</p>
<ul>
<li><strong>5% over-provisioning.</strong> It&rsquo;s a slice of the pool the system reserves and never hands out for data. ZFS always writes to new space and gets slow when the pool fills up near the limit; this slack keeps performance stable. On SSD pools it also extends drive life, but these are hard drives, so the minimum is enough.</li>
<li><strong>5% guaranteed snapshot space.</strong> It&rsquo;s the reserve for snapshots, the &ldquo;photos&rdquo; of the state of your files that let you go back after an accident or ransomware. Guaranteeing the space means the snapshots keep existing even if I fill up the rest. My data changes little (it&rsquo;s mostly cold archive), so snapshots take almost nothing and 5% is plenty.</li>
<li><strong>Alert at 85%.</strong> The system warns me when the pool passes 85% usage, with room for me to act before hitting the ceiling. It was exactly the lack of room that had me swapping drives in a hurry on the Synology.</li>
</ul>
<p>Adding the two reserves, about 13 TB are set aside and 112.9 TB are left for use.</p>
<p>A detail of my case: I&rsquo;m copying the 90 TB with snapshots turned off, and I&rsquo;ll only enable the schedule after the copy finishes. A snapshot protects a file&rsquo;s previous state, and on an empty NAS receiving its first load there is no previous state worth protecting. The source of truth is still the Synology.</p>
<p>And there&rsquo;s a practical cost. I stopped and restarted the job several times, deleted folders that had gone in by mistake and copied again. With snapshots on, every deleted thing would keep taking up space inside some snapshot, and I&rsquo;d be filling the pool with the migration&rsquo;s own garbage. When everything is on this side and checked, then yes: scheduled snapshots, to protect me from deleting what I shouldn&rsquo;t and from ransomware.</p>
<p>After that I created one giant &ldquo;thick&rdquo; partition. In ZFS you can create a &ldquo;thin&rdquo; folder, which only takes up what&rsquo;s inside it and can promise more space than exists, or a &ldquo;thick&rdquo; one, which reserves the full size at creation. Since it&rsquo;s a pool with one owner and one use, thick is more predictable: the space is mine and nothing competes for it.</p>
<p>Finally, I created the shares and installed the HBS 3 app.</p>
<h2>Copying 90 TB<span class="hx:absolute hx:-mt-20" id="copying-90-tb"></span>
    <a href="#copying-90-tb" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>HBS 3 (Hybrid Backup Sync) is QNAP&rsquo;s app for backup and synchronization. It connects to another NAS, to rsync, FTP, SMB or the cloud.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-hbs3-criar-job.png" alt="HBS 3 sync job creation screen, with the destination options: local NAS, remote NAS, Rsync, FTP and CIFS/SMB"  loading="lazy" /></p>
<p>I connected to the Synology&rsquo;s SMB share and told it to sync everything.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-hbs3-job-sync.png" alt="HBS 3 syncing from the Synology TERACHAD to the QNAP TS-873A"  loading="lazy" /></p>
<p>I had to stop and restart a few times, tweaking the job settings, because it was absurdly slow. And the file count wouldn&rsquo;t stop growing:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-hbs3-snapshot-explosao.png" alt="HBS 3 job status showing 45 million files and 865 TB calculated, at an average of 350 MB/s"  loading="lazy" /></p>
<p>865 TB on a 108 TB NAS, and that&rsquo;s when I understood what was going on.</p>
<p>I excluded two kinds of path from the job:</p>
<ul>
<li><strong>The <code>#snapshot</code> directories.</strong> On Synology, when you check &ldquo;make snapshot visible,&rdquo; each shared folder gets a <code>#snapshot</code> subfolder with a full view of the folder at each point in time. Inside the Synology that takes no space, because a snapshot only stores the difference. Seen from outside, over SMB, every snapshot looks like a full copy of everything, and HBS was trying to copy them all.</li>
<li><strong>The restic backup directories.</strong> restic stores everything in hundreds of thousands of pack files of a few megabytes each. Network copying is fast with large files and suffers with small ones, because each one costs an open, metadata and a close. And those backups I&rsquo;ll redo from scratch on the new NAS.</li>
</ul>
<p>With that, throughput rose to an average of 400 MB/s.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-qnap-400mbs.png" alt="QNAP resource monitor showing 414.8 MB/s received on the 10 GbE interface"  loading="lazy" /></p>
<p>That&rsquo;s less than half of what a 10 GbE network delivers, but it&rsquo;s more than three times the gigabit most people have at home. Both sides really are at 10 gigabits:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-qnap-10gbe.png" alt="QNAP 10 GbE adapter connected at 10 Gbps"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-10gbe.png" alt="Synology LAN 5 interface at 10000 Mbps, full duplex"  loading="lazy" /></p>
<p>Why doesn&rsquo;t it reach 1,000 MB/s? My main hypothesis is drive 8. The source array is degraded, and every read that goes through the affected pieces has to be recomputed from parity, with a drive that on top of that returns errors and retries. It makes sense that the Synology can&rsquo;t go faster than this.</p>
<p>There&rsquo;s a second factor, which my own screenshots give away: the Synology has jumbo frames on (MTU 9000), and the QNAP image shows the default MTU of 1500. That screenshot is from earlier; I&rsquo;ve already changed the QNAP to 9000 as well.</p>
<p>Why it matters: MTU is the maximum size of each packet that travels on the network. At the default 1500 bytes, filling a 10-gigabit link means processing more than 800 thousand packets per second, and each packet costs CPU on both sides, for the header, the checksum and the interrupt. With jumbo frames each packet carries six times more data, so it&rsquo;s one sixth of the packets for the same volume, and more processor is left for moving files. It only works if everything in the path matches: both cards and the switch. If one side stays at 1500, the connection negotiates down to the smaller value and the other side&rsquo;s jumbo frames do nothing.</p>
<p>At 400 MB/s, 90 TB takes about two and a half days, and I prefer two and a half guaranteed days.</p>
<h2>What Synology explained<span class="hx:absolute hx:-mt-20" id="what-synology-explained"></span>
    <a href="#what-synology-explained" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>In parallel, I opened a ticket with Synology support. After some back and forth, I sent the logs. They responded fast, the same day, and I&rsquo;ll give them that. The engineer&rsquo;s explanation, summarized:</p>
<ul>
<li>Drive 8 had been logging media errors <strong>for over a month</strong>. A media error is when the NAS tells the drive to read a sector and the drive answers that it couldn&rsquo;t, even after several retries. It&rsquo;s always an internal drive failure, and the drive should have been replaced.</li>
<li>It was drive 8&rsquo;s poor health that took it down during the rebuild of drive 1.</li>
<li>In practice I lost two drives, and the pool should have crashed completely. It didn&rsquo;t because of how SHR works.</li>
</ul>
<p>SHR isn&rsquo;t a single RAID-5. To use drives of different sizes without wasting space, it slices the drives and builds several smaller RAIDs, one per size band, and joins everything into one volume. The engineer sent this example diagram:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-shr-particoes.png" alt="Synology support diagram showing how SHR splits drives of different sizes into partitions 1, 2 and 3"  loading="lazy" /></p>
<p>In my case, the pool is made of two partitions. In the first, the rebuild of drive 1 was far enough along and the failure of drive 8 was mild enough that both kept working. In the second, both are missing. That&rsquo;s why the pool didn&rsquo;t crash entirely and I can still see the files.</p>
<p>His recommendation was the one I was already following: copy everything out before anything else, because there&rsquo;s no telling whether the situation gets worse or whether it&rsquo;s fixable. After the backup he can even try forcing drive 8 back into the pool so the rebuild continues, but there&rsquo;s no guarantee the drive will go back in, or that it will survive the process. In his words, copying and recreating the pool from scratch is probably the quickest way forward.</p>
<p>Classic RAID-5, by the way, requires all drives to be the same size, and to grow capacity I&rsquo;d have to replace all of them at once. SHR is what let me swap one at a time over the years. Ironically, it&rsquo;s also what saved me from losing everything at once.</p>
<h2>The two mistakes<span class="hx:absolute hx:-mt-20" id="the-two-mistakes"></span>
    <a href="#the-two-mistakes" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The way I understood it, there are two mistakes, one mine and one theirs, and that&rsquo;s where the whole disaster lives.</p>
<p><strong>Mine:</strong> I underestimated RAID-6, which on Synology is called SHR-2. With two parity drives, drive 8 failing in the middle of the rebuild would have been just a scare. I traded safety for 20 TB of space and spent five years thinking I&rsquo;d made a good deal.</p>
<p><strong>Theirs:</strong> from the logs, the software knew drive 8 had been failing for at least a month, and I never got a single notification. I have email notifications configured. And they work: when the critical situation blew up, I got a flood of emails. This one, about drive 8&rsquo;s bad sectors, arrived at one in the afternoon on Monday, five hours after the disaster:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-email-bad-sectors.png" alt="Synology email warning that the number of bad sectors increased on drive 8, a Seagate ST22000NT001"  loading="lazy" /></p>
<p>Before that, nothing. No warning about drive 8 until it failed catastrophically.</p>
<p>If I had received one, I would never have tried to swap drive 1 before replacing drive 8. The whole situation was avoidable, and it was the result of a human mistake plus some software bug.</p>
<p>That&rsquo;s the other reason I didn&rsquo;t buy another Synology. I have a principle: if the horse throws me, I put the horse down. I can&rsquo;t trust it again.</p>
<h2>Where I am now<span class="hx:absolute hx:-mt-20" id="where-i-am-now"></span>
    <a href="#where-i-am-now" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>So far I&rsquo;ve copied about 20 TB of the original 90 TB, and HBS 3 keeps working. I still don&rsquo;t know whether I lost any data permanently. I hope not, but the most likely outcome is that I lost what was in the RAID slice the engineer described as crashed.</p>
<p>I have offsite backup on AWS Glacier of what matters most, but not of everything.</p>
<p>A lot of people ask why I don&rsquo;t use S3 or some other cloud storage for everything. I return the question: think for ten seconds. How long does it take to transfer 90 TB over your internet connection? Do the math:</p>
<ul>
<li>With a solid 1 gigabit of upload, never dropping for a second: more than 8 days.</li>
<li>With 500 megabits: almost 17 days.</li>
<li>With 100 megabits, which is good upload for many homes: 83 days.</li>
</ul>
<p>And that&rsquo;s to upload. When disaster hits you need to download it all back, with the clock running. Not counting the rent: 90 TB on standard S3 costs about US$ 2 thousand a month at list price. Cloud is great for the critical subset, which is how I use it. For the whole archive, physics and the invoice won&rsquo;t allow it.</p>
<h2>Lessons<span class="hx:absolute hx:-mt-20" id="lessons"></span>
    <a href="#lessons" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I was put in an impossible situation on a Monday morning, I acted right away as best I could, and I&rsquo;ll probably come out of this mostly unscathed. We&rsquo;ll see, HBS 3 is still syncing. What I take from it:</p>
<ol>
<li><strong>Single parity isn&rsquo;t enough for big drives.</strong> With 20 TB drives, a rebuild takes days and reads everything. That&rsquo;s the window in which the second drive tends to show up. RAID-6, SHR-2 or RAIDZ2.</li>
<li><strong>Don&rsquo;t rely only on the vendor&rsquo;s notifications.</strong> Look at SMART and the drive logs before starting any swap. I didn&rsquo;t, because I trusted I&rsquo;d be warned.</li>
<li><strong>Replace the sick drive first, then the small one.</strong> A capacity upgrade on an array with a suspect drive is roulette.</li>
<li><strong>Leave slack.</strong> I was swapping drives because the volume was at the ceiling. An 85% alert exists for that.</li>
<li><strong>Have somewhere to copy to.</strong> My way out cost R$ 185 thousand because I had no plan B. A second NAS, even a modest one, with what matters, would have bought me time to shop calmly.</li>
<li><strong>RAID is not backup.</strong> I know that, I teach that, and I still only had offsite for part of it.</li>
</ol>
<p>The copy is done and I already know the size of the damage: zero. The full recovery comes next.</p>
<h2>Six days later: the end of the story<span class="hx:absolute hx:-mt-20" id="six-days-later-the-end-of-the-story"></span>
    <a href="#six-days-later-the-end-of-the-story" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>When I published the first part, I had about 20 TB copied and no certainty. The outcome: I finished evacuating the ~91 TB, verified file by file, and lost nothing by accident. Not even what was on the fallen RAID slice, because it stayed readable until the end.</p>
<p>Who operated the recovery were my two agents. The desktop agent controls my PC and both NASes over SSH, and the homelab agent takes care of the server where my 57 containers run. The two coordinate by message: one leaves a report in the other&rsquo;s inbox, and I follow the conversation.</p>
<p><img src="/2026/10/06/desastre-no-meu-nas-como-quase-perdi-90-tb-de-dados/nas-ai-memory-agentes.png" alt="The desktop agent’s terminal checking the ai-memory message inbox, where the homelab agent leaves its reports"  loading="lazy" />)</p>
<p>This part matters because nothing that follows was me typing commands. I made the decisions; they executed, measured and screwed up badly a few times, as you&rsquo;ll see.</p>
<h3>HBS 3 was quietly betraying me<span class="hx:absolute hx:-mt-20" id="hbs-3-was-quietly-betraying-me"></span>
    <a href="#hbs-3-was-quietly-betraying-me" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>In the first part I told how HBS 3, pulling the Synology shares over CIFS, reached 400 MB/s after the adjustments. What I didn&rsquo;t know: speed was the smallest of its problems.</p>
<p>Two defects showed up when the agent checked the source inventory against what HBS reported:</p>
<ul>
<li><strong>It didn&rsquo;t go where it had no permission, and told nobody.</strong> HBS mounts the Synology as my user. The <code>Bitcoin/data</code> directory, 901 GB with my node&rsquo;s wallet and chainstate, belongs to uid 101 and is closed to anyone else. CIFS couldn&rsquo;t get in there, and HBS reported success without recording the omission. A &ldquo;green&rdquo; backup that leaves the entire Bitcoin node behind.</li>
<li><strong>It skipped files with &ldquo;unsupported&rdquo; names.</strong> 113 files, recorded only on a detail screen nobody opens during a days-long copy.</li>
</ul>
<blockquote>
  <p>A backup tool that reports success while omitting what it couldn&rsquo;t read is worse than one that fails screaming. Verify the backup against an independent inventory of the source, never against the tool&rsquo;s own log.</p>

</blockquote>
<p>We abandoned HBS. The agent opened SSH on both NASes, enabled the rsync server on the QNAP, and the Synology started pushing straight to its rsyncd, with nothing going through the PC. Each job&rsquo;s wrapper: <code>rsync -a --numeric-ids -W --partial --timeout=600</code>, attempts with retry, and <strong>never <code>--delete</code></strong>. The <code>--numeric-ids</code> preserved the original uids with no chown afterwards, <code>--partial</code> turned any interruption into a continuation, and the absence of <code>--delete</code> made the destination always a superset: re-sharding or killing a job never cost data.</p>
<h3>The bottleneck wasn&rsquo;t the broken drive<span class="hx:absolute hx:-mt-20" id="the-bottleneck-wasnt-the-broken-drive"></span>
    <a href="#the-bottleneck-wasnt-the-broken-drive" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Some context was missing from the first part. This Synology was born with eight consumer-line Seagate IronWolfs, and over the years I had been swapping them one by one for 20 TB IronWolf Pros. The disaster happened exactly during one of those upgrades: I swapped drive 1 for the new Pro and, during the rebuild, drive 8 failed. One single old drive was left in the array: drive 2.</p>
<p>And it, not drive 8, was the evacuation bottleneck. Measured under load:</p>
<ul>
<li>Drive 2 (the old consumer one, with 37 thousand hours of use): 91% utilization, delivering 27 MB/s, each I/O taking 4 to 15 times longer than its siblings.</li>
<li>The six Pros: between 34% and 45% utilization, sitting idle waiting for it.</li>
</ul>
<p>In RAID-5 (and in SHR), a sequential read spans the whole stripe, and the stripe only completes when the slowest member answers. One slow drive caps the whole array: because of it, the array couldn&rsquo;t go past about 200 MB/s while six good drives had room to spare. And its SMART was clean, zero reallocated sectors. Consumer drives usually ship without time-limited error recovery (ERC/TLER) active, so the drive retries a marginal sector for seconds, internally, without accounting for anything. The Synology&rsquo;s NVMe cache diverted part of the reads from the platters and helped mask the symptom.</p>
<blockquote>
  <p>The drive that fails and the drive that slows you down don&rsquo;t have to be the same, and SMART &ldquo;PASSED&rdquo; says nothing about latency. A slow array is diagnosed with per-drive utilization and response time, not with SMART.</p>

</blockquote>
<h3>Parallelizing rsync is science, not guesswork<span class="hx:absolute hx:-mt-20" id="parallelizing-rsync-is-science-not-guesswork"></span>
    <a href="#parallelizing-rsync-is-science-not-guesswork" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>First instinct: more streams. Wrong, twice.</p>
<p>The first time, a bug of ours: a &ldquo;clean&rdquo; job relaunch didn&rsquo;t kill anything, because the Synology&rsquo;s busybox <code>ps</code> truncates the command line and the verification grep didn&rsquo;t find the old processes. Result: 32 rsyncs running into 7 destinations, four to five writers per tree, the QNAP with its receive buffers clogged, and the cable transmitting 516 MB/s while the array read 140. After the cleanup (enumerating <code>/proc/*/cmdline</code>, never trusting <code>ps</code> again), useful throughput <strong>rose</strong> from 140 to 181 MB/s while total load collapsed.</p>
<p>The second time, measurement inverted common sense: with large-file trees, fewer streams were faster, because the average size of each read request tripled and the array did long sequential runs instead of jumping around.</p>
<p>But small-file trees are another sport. <code>TERACHAD/Emulators</code> has 21 TB in 3.4 million files averaging 6 MB: latency-bound work (stat, creation, metadata), not bandwidth-bound. And there aggressive parallelization worked, with one detail: the right shard is chosen by <strong>file count</strong>, not by size. A single directory, <code>ROMS_FINAL/xbox-hdd-ready</code>, held 995 thousand of ROMS_FINAL&rsquo;s 1.59 million files, averaging 1.7 MB per file. Isolating it from the multi-gigabyte ISOs:</p>
<table>
  <thead>
      <tr>
          <th>Configuration</th>
          <th style="text-align: right">GB/hour</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>single stream, 6-hour average</td>
          <td style="text-align: right">~157</td>
      </tr>
      <tr>
          <td>two shards</td>
          <td style="text-align: right">272</td>
      </tr>
      <tr>
          <td><strong>three shards</strong></td>
          <td style="text-align: right"><strong>436 to 501</strong></td>
      </tr>
  </tbody>
</table>
<p>Between 2.8 and 3.2 times the single stream&rsquo;s average throughput, on the same machine, on the same network. And there&rsquo;s a beautiful reason: with the array missing a member, reads of data that doesn&rsquo;t live on drive 2 don&rsquo;t need it, so the extra concurrency let the other drives work while it choked.</p>
<blockquote>
  <p>&ldquo;More parallelism&rdquo; and &ldquo;less parallelism&rdquo; are both wrong answers as a general rule. Bandwidth-bound work splits little; latency-bound work splits a lot, and by file count. Measure before choosing.</p>

</blockquote>
<h3>Copying is half the work; proving is the other half<span class="hx:absolute hx:-mt-20" id="copying-is-half-the-work-proving-is-the-other-half"></span>
    <a href="#copying-is-half-the-work-proving-is-the-other-half" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Every job that finished went through a dry-run of the same rsync, with the same excludes: zero files needing transfer means an identical destination by size and date. And even so it almost went wrong, because <strong>the verification inherited the copy&rsquo;s excludes</strong>: a tree excluded from the transfer stayed excluded from its own proof. Re-running the check without excludes turned up 952 files and 191 GB missing in Downloads (exactly the batches I had set aside as &ldquo;only existing copy&rdquo;) and the old restic repositories, which a similar directory name on the QNAP pretended to cover: the repositories&rsquo; internal ID proved they were different ones, not copies.</p>
<p>There was source drift too: a tree verified as complete received 16 GB of new checkpoints on the Synology after the check, and only the final re-verification caught it. Verification has an expiration date.</p>
<p>And a conscious decision, recorded so nobody accuses me of hiding it: the old backup histories (about 2 TB of restic snapshots and a duplicity set from 2025) were left behind by my choice, because the new repositories on the QNAP already cover the same content from now on. Deliberate loss of history, not of data.</p>
<p>Closing the books: <strong>17 rsync jobs, all exit 0, all verified, about 5.9 million byte-identical files</strong>, and the Synology shutdown only happened after every tree was proven.</p>
<p><img src="/2026/10/06/desastre-no-meu-nas-como-quase-perdi-90-tb-de-dados/nas-homelab-containers-status.png" alt="Recovery status table mid-process: the containers already released and the ones still held waiting for each tree’s rsync verification"  loading="lazy" />)</p>
<h3>Meanwhile, in the homelab<span class="hx:absolute hx:-mt-20" id="meanwhile-in-the-homelab"></span>
    <a href="#meanwhile-in-the-homelab" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>On the other side of the house, the marvin server, which mounts the NAS over NFS and runs the 57 containers, almost died standing up. Dead NAS plus &ldquo;hard&rdquo; NFS mounts plus 57 containers restarting on their own made an infinite queue of uninterruptible I/O: the machine answered ping, but sshd couldn&rsquo;t complete a handshake. And every insistent SSH attempt only made it worse, because OpenSSH punishes a repetitive source by extending the ban. We knocked on the door and the door added a lock.</p>
<p>The way out was to stop knocking: I paused the torrents through the qBittorrent API (which kept responding), went to the physical console, disabled Docker, rebooted, and the server came back. A watchdog I didn&rsquo;t even remember existed had, on its own, taken down the 14 NAS-dependent containers before any database corrupted: bitcoind&rsquo;s LevelDB opened clean on the first try.</p>
<p>The migration itself was elegant: fstab pointed at the QNAP and the old mount points became symlinks into the new one. <strong>No docker-compose changed.</strong> Then came the QNAP NFS gotcha: permission is per client. The desktop was allowed without squash, but marvin&rsquo;s writes came in as uid 1001, blocking everything that writes to the NAS. The first fix attempt still flipped the mount to read-only in the middle of the first restic backup, which survived on retries. Final configuration: read and write, no squash for anyone, tested with <code>touch</code> and <code>ls -ln</code> from each client, because &ldquo;it worked on the desktop&rdquo; proves nothing about the server.</p>
<p><img src="/2026/10/06/desastre-no-meu-nas-como-quase-perdi-90-tb-de-dados/nas-qnap-configurando-nfs.png" alt="QNAP NFS permissions screen, with the per-host rule for the homelab server"  loading="lazy" />)</p>
<p>After that it was resurrection tree by tree: each service only came up when its tree was verified on the QNAP. In two days, 57 of 57 containers back, and a brand-new restic backup running daily to the QNAP.</p>
<h3>The new kernel took down the NVMe<span class="hx:absolute hx:-mt-20" id="the-new-kernel-took-down-the-nvme"></span>
    <a href="#the-new-kernel-took-down-the-nvme" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>In the middle of the recovery, a free scare from the server. Seven minutes after the nightly backup started, the NVMe controller vanished silently: every binary took SIGBUS, Docker and sshd died, and nothing remained recorded anywhere, because the log lives on the device that died. Not even a kernel panic: just silence.</p>
<p>The same workload had run fine hours earlier on kernel 7.2.6 and died on 7.2.9. Disabling APST (the NVMe&rsquo;s power management) didn&rsquo;t fix it: a second identical crash, a 7.2.9 regression confirmed by elimination. MicroOS is transactional and snapshot-based, so the rollback was a reboot into the 7.2.6 lineage, not a reinstall. Automatic updates were deliberately left broken on that lineage so nobody goes back to 7.2.9 unsupervised, and testing 7.2.8 waits until I can sit next to it.</p>
<blockquote>
  <p>A transactional operating system turns a kernel regression into a reboot. Without system snapshots, that night would have cost a lot more.</p>

</blockquote>
<h3>The thick provisioning scare<span class="hx:absolute hx:-mt-20" id="the-thick-provisioning-scare"></span>
    <a href="#the-thick-provisioning-scare" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>On the QNAP, the whole copy ran with no snapshots at all: in a bulk migration, snapshots are dead weight. With the copy done, I went to enable the ZFS snapshot schedule and got a dry error: &ldquo;pool free space is not enough&rdquo;. With 20 TB free.</p>
<p><img src="/2026/10/06/desastre-no-meu-nas-como-quase-perdi-90-tb-de-dados/nas-qnap-thick-thin-erro-snapshot.png" alt="QNAP error when trying to create a snapshot: insufficient pool free space, even with terabytes free"  loading="lazy" />)</p>
<p>Both statements were true about different things. The CHAD folder had been created as <strong>thick</strong>: out of the 100 TB quota, ZFS reserves the full 100 TB, and to snapshot a dataset with a reservation, the pool has to guarantee space to overwrite every referenced block while the snapshot pins the old copies. The math: 80 TB written plus 100 TB reserved, against 126 TB usable in the pool. It doesn&rsquo;t close. And <code>df</code> lies on both sides, because it measures the folder against the quota, not against the pool.</p>
<p>The fix was converting the folder to <strong>thin</strong>, which QuTS hero does in-place, hidden on the folder&rsquo;s Management screen, not in the edit dialog where &ldquo;Thick&rdquo; looks like static text. In thin, the number becomes a ceiling instead of a reservation: the 10 TB reservation evaporated, the pool ended up with 39 TB free, and the first snapshot came out consuming 334 KB over 79.9 TB referenced. ZFS needed almost nothing; the reservation was what demanded a 100 TB guarantee to authorize.</p>
<p><img src="/2026/10/06/desastre-no-meu-nas-como-quase-perdi-90-tb-de-dados/nas-qnap-chad-properties.png" alt="CHAD folder properties after the conversion, now as thin"  loading="lazy" />)</p>
<p>Now the QNAP has a snapshot schedule with a retention policy, as it should have had since day one:</p>
<p><img src="/2026/10/06/desastre-no-meu-nas-como-quase-perdi-90-tb-de-dados/nas-qnap-snapshot-schedule.png" alt="CHAD folder snapshot schedule on the QNAP"  loading="lazy" />)</p>
<p><img src="/2026/10/06/desastre-no-meu-nas-como-quase-perdi-90-tb-de-dados/nas-qnap-snapshot-retention.png" alt="Snapshot retention policy: how many to keep per day, week and month"  loading="lazy" />)</p>
<p>Two footnotes for anyone building one of these. If your thin folder has an 80% alert, raise the quota for aesthetic slack, because the alert measures against the quota and not against the pool. And this QNAP&rsquo;s real bottleneck is RAM: 7 GB for a 174 TB pool. QuTS hero&rsquo;s official minimum is 8 GB, and sensible practice at this scale is 16 and up. The size of the ARC cache governs metadata speed, and that&rsquo;s why the whole-tree sweeps during the migration were slow. A RAM upgrade is worth more than any fine-tuning.</p>
<h3>The Synology is born again<span class="hx:absolute hx:-mt-20" id="the-synology-is-born-again"></span>
    <a href="#the-synology-is-born-again" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>With everything verified on the QNAP, the Synology could finally be operated on. I swapped drives 2 and 8 for new IronWolf Pros, deleted the wounded pool and recreated it from scratch, this time in <strong>SHR-2</strong>, the two-parity-drive mode, their RAID-6. The new pool came out with 100 TB, all drives healthy:</p>
<p><img src="/2026/10/06/desastre-no-meu-nas-como-quase-perdi-90-tb-de-dados/nas-synology-pool-novo-saudavel.png" alt="Synology Storage Manager showing the new SYNOCHAD pool in SHR with two-drive tolerance, 100 TB and all drives Healthy"  loading="lazy" />)</p>
<p>It goes back to work as a replica target, and no longer as a single point of failure. The QNAP stays as primary, in RAIDZ2, with 87 TB of the 126 TB used and snapshots scheduled:</p>
<p><img src="/2026/10/06/desastre-no-meu-nas-como-quase-perdi-90-tb-de-dados/nas-qnap-storage-manager-final.png" alt="QNAP Storage Manager with the RAID 6 Ready pool and the CHAD folder in thin with 79.9 TB of 95 TB"  loading="lazy" />)</p>
<h3>What I take from the second half<span class="hx:absolute hx:-mt-20" id="what-i-take-from-the-second-half"></span>
    <a href="#what-i-take-from-the-second-half" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ol>
<li><strong>Distrust silent success.</strong> HBS reported green while leaving 901 GB behind. A backup is verified against a source inventory.</li>
<li><strong>SMART doesn&rsquo;t measure slowness.</strong> The drive that nearly killed the evacuation was &ldquo;healthy.&rdquo; Per-drive utilization and latency tell the truth.</li>
<li><strong>Parallelism is calibrated by measuring.</strong> Bandwidth-bound splits little, latency-bound splits a lot, and the shard is chosen by file count.</li>
<li><strong>Verification doesn&rsquo;t inherit excludes.</strong> If the proof uses the same filters as the copy, it only reaffirms the copy&rsquo;s assumptions. And verify again immediately before destroying the source.</li>
<li><strong>NFS permission is per client.</strong> Test writes from every machine that will use it.</li>
<li><strong>Don&rsquo;t hammer a dying sshd.</strong> Each attempt extends the ban. One patient attempt, or the physical console.</li>
<li><strong>The dangerous repair comes last.</strong> The array stayed degraded and slow until the last byte was proven somewhere else; only then did rebuilding stop costing anything.</li>
</ol>
<p>I came out of this better protected than I went in: two NASes, each with its own RAID-6, scheduled snapshots on the QNAP, daily restic, and Glacier for what&rsquo;s critical. The disaster cost me a QNAP, two drives and a week. It could have cost 90 TB.</p>
]]></content:encoded><category>storage-and-backup</category><category>homelab</category><category>hardware</category></item><item><title>Removing DRM from Kindle Ebooks in 2026</title><link>https://akitaonrails.github.io/en/2026/07/30/removing-drm-from-kindle-ebooks-in-2026/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/07/30/removing-drm-from-kindle-ebooks-in-2026/</guid><pubDate>Thu, 30 Jul 2026 18:00:00 -0300</pubDate><description>&lt;p&gt;I&amp;rsquo;ve been an Amazon customer for far too long. I&amp;rsquo;ve bought Kindle books for years, owned several of their devices, and I still hate the way the company treats digital content.&lt;/p&gt;
&lt;p&gt;You pay for a book, but you can only read it where Amazon allows. There is no decent native Linux app. Moving to another reader turns into an exercise in archaeology. I bought a new Xteink X4 and, naturally, my library couldn&amp;rsquo;t simply come along. The file was &amp;ldquo;in my account,&amp;rdquo; but it wasn&amp;rsquo;t under my control.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;ve been an Amazon customer for far too long. I&rsquo;ve bought Kindle books for years, owned several of their devices, and I still hate the way the company treats digital content.</p>
<p>You pay for a book, but you can only read it where Amazon allows. There is no decent native Linux app. Moving to another reader turns into an exercise in archaeology. I bought a new Xteink X4 and, naturally, my library couldn&rsquo;t simply come along. The file was &ldquo;in my account,&rdquo; but it wasn&rsquo;t under my control.</p>
<p>For years, I worked around this through the official route. I&rsquo;d sign into Amazon&rsquo;s website, use <strong>Download &amp; Transfer via USB</strong>, download the <code>.azw</code> files for books I&rsquo;d bought, and import everything into <a href="https://calibre-ebook.com/about"target="_blank" rel="noopener">calibre</a>. With the DeDRM plugin configured for my Kindle&rsquo;s serial number, I&rsquo;d convert them to EPUB and be done with it.</p>
<p>On <strong>February 26, 2025</strong>, Amazon <a href="https://www.vice.com/en/article/amazon-is-killing-your-ability-to-download-kindle-books-next-week/"target="_blank" rel="noopener">shut that option down</a>. No decent announcement, no replacement that actually handed over the file, and no concern for anyone who wanted a local backup. My newer books have been trapped in their ecosystem ever since. I tried again last year, updated the plugin, copied files from my Kindle Paperwhite, entered the correct serial number, and got nowhere.</p>
<p>To be fair, on <strong>January 20, 2026</strong>, Amazon began allowing verified buyers to download EPUB or PDF files when the publisher <a href="https://kdp.amazon.com/pt_BR/help/topic/GDDXGH9VR22ACM8U"target="_blank" rel="noopener">marks the book as DRM-free</a>. Great. That doesn&rsquo;t help with protected books, and the decision still belongs to the publisher. Older DRM-free titles must also be confirmed one by one by the publisher. This did not magically add a download button to my library.</p>
<p>I decided to try again now. The new DeDRM version can handle the current DRM, but the process has changed quite a bit. It takes work, requires Windows for one step, and relies on a community tool still in pre-release. It worked. I recovered <strong>106 books I&rsquo;d bought</strong>, tested the EPUB conversion, and can now read them on whatever device I choose.</p>
<p>This is the state of play on <strong>July 30, 2026</strong> (updated August 19). Amazon could change the encryption or the app tomorrow. Ignore old tutorials telling you to install Kindle for PC 1.17, disable updates, and enter the device serial number in Calibre. That world is gone.</p>
<p>And let&rsquo;s get the obvious out of the way: I&rsquo;m talking about books <strong>I bought</strong>. I&rsquo;m not talking about downloading Kindle Unlimited titles, library loans, or somebody else&rsquo;s books. Laws covering DRM circumvention vary by country. Check the law where you live and take responsibility for what you do.</p>
<h2>First things first: don&rsquo;t throw out an old Kindle<span class="hx:absolute hx:-mt-20" id="first-things-first-dont-throw-out-an-old-kindle"></span>
    <a href="#first-things-first-dont-throw-out-an-old-kindle" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Amazon doesn&rsquo;t make it easy to repurpose an old Kindle either. The hardware remains perfectly good, the battery is usually replaceable, and an e-ink screen lasts forever, yet the software becomes increasingly closed and limited.</p>
<p>If you want to revive one of these devices, I recommend the <a href="https://www.youtube.com/@DammitJeff"target="_blank" rel="noopener">Dammit Jeff</a> channel. He covers jailbreaks, KOReader, alternative stores, and ways to keep Kindles useful after Amazon loses interest. His video about <a href="https://www.youtube.com/watch?v=l4ZliC82RtA"target="_blank" rel="noopener">AdBreak and recent jailbreaks</a> is a good place to start. Read the <a href="https://kindlemodding.org/"target="_blank" rel="noopener">current Kindle Modding guide</a> before doing anything, because supported firmware and methods change constantly.</p>
<p>Jailbreaking the device and removing DRM from an ebook are separate problems. You do not need to unlock your Kindle to follow the rest of this article. I mention both because they start from the same premise: hardware and media we&rsquo;ve already paid for should remain useful without asking the manufacturer for eternal permission.</p>
<h2>Crash course: AZW, EPUB, and KFX<span class="hx:absolute hx:-mt-20" id="crash-course-azw-epub-and-kfx"></span>
    <a href="#crash-course-azw-epub-and-kfx" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>DRM and file format are separate things. The format defines how text, images, fonts, the table of contents, and metadata are packaged. DRM adds a cryptographic lock on top and decides which account, app, or device can open it.</p>
<p>These are the three names that matter here:</p>
<table>
  <thead>
      <tr>
          <th>Format</th>
          <th>What it is</th>
          <th>In practice</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>AZW / AZW3</strong></td>
          <td>An older family of proprietary Kindle formats. AZW3 is also known as Kindle Format 8.</td>
          <td>This is what normally came through the old USB download. Tools and alternative readers understand it well after the DRM is removed.</td>
      </tr>
      <tr>
          <td><strong>EPUB</strong></td>
          <td>An open W3C standard. It is a single package containing HTML-based content, CSS, SVG, fonts, and metadata.</td>
          <td>It is the most portable format for storage and reading outside Amazon&rsquo;s ecosystem. This is the destination I want.</td>
      </tr>
      <tr>
          <td><strong>KFX</strong></td>
          <td>Amazon&rsquo;s modern delivery format, used for features such as Enhanced Typesetting and Page Flip. A purchased book may be split across several containers, auxiliary resources, and a DRM voucher.</td>
          <td>This is what the current Kindle app downloads. We need to gather the pieces, remove the DRM, and only then convert it.</td>
      </tr>
  </tbody>
</table>
<p>The extension alone can be misleading. Pieces of a KFX book may show up as <code>.azw</code>, <code>.azw.res</code>, <code>.voucher</code>, and other variations. The <a href="https://www.mobileread.com/forums/showthread.php?t=291290"target="_blank" rel="noopener">KFX Input</a> plugin exists specifically to understand that bundle.</p>
<p>EPUB is much less mysterious. The <a href="https://www.w3.org/TR/epub-33/"target="_blank" rel="noopener">EPUB 3.3 specification</a> defines it as a single-file container for structured Web content. It&rsquo;s basically a small packaged website. Any halfway decent reader can support it without depending on a secret Amazon key.</p>
<h2>Calibre and the two plugins<span class="hx:absolute hx:-mt-20" id="calibre-and-the-two-plugins"></span>
    <a href="#calibre-and-the-two-plugins" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><a href="https://calibre-ebook.com/"target="_blank" rel="noopener">calibre</a> is the Swiss Army knife of ebooks. It&rsquo;s open source, runs on Linux, macOS, and Windows, organizes libraries, fetches metadata, edits covers, transfers books, and converts dozens of formats. It has been around since 2006 and was born precisely because the first Sony Reader didn&rsquo;t work properly on Linux.</p>
<p>On Omarchy or any other Arch system, install the package:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo pacman -S calibre</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>For other distributions, see the <a href="https://calibre-ebook.com/download"target="_blank" rel="noopener">official download page</a>. Don&rsquo;t install some random package from a download site.</p>
<p>One gotcha for environments using <code>mise</code>: if Calibre dies with <code>ModuleNotFoundError: msgpack</code> or <code>BrokenPipeError</code>, a Python shim probably got ahead of <code>/usr/bin</code>. The Arch package needs the system Python. Start it like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">env <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;/usr/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span> /usr/bin/python3 /usr/bin/calibre</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This has nothing to do with DRM or the plugins. It&rsquo;s just Calibre&rsquo;s helper calling the wrong Python. I left a permanent launcher with this <code>PATH</code> on my desktop.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/30/kindle-dedrm/calibre-library-epub.webp" alt="My Calibre library with a converted book available in both AZW3 and EPUB."  loading="lazy" /></p>
<p>Calibre alone does not remove DRM. We need two plugins:</p>
<ol>
<li><strong>DeDRM</strong> recognizes and removes locks from several ecosystems during import. It only runs when the book enters the library. Clicking Convert afterward removes nothing.</li>
<li><strong>KFX Input</strong> understands Amazon&rsquo;s modern containers, gathers the pieces of a book, and makes EPUB conversion possible.</li>
</ol>
<p>At the time of writing, I&rsquo;m using <strong>DeDRM 10.0.28</strong>, published on July 14 in the <a href="https://github.com/Satsuoni/DeDRM_tools/releases/tag/v10.0.28"target="_blank" rel="noopener">fork maintained by Satsuoni</a>. It is a pre-release. Download the asset named <code>DeDRM_tools.zip</code>, never a standalone executable from some obscure mirror.</p>
<p><strong>Update (August 2026):</strong> the Kindle app on the Microsoft Store keeps moving, and each new app version requires a new version of the tool. Since this article was published, the app moved past 1.0.18632: <a href="https://github.com/Satsuoni/DeDRM_tools/releases/tag/v10.0.29"target="_blank" rel="noopener">10.0.29</a> added support for app 1.22326, and <a href="https://github.com/Satsuoni/DeDRM_tools/releases/tag/v10.0.30"target="_blank" rel="noopener">10.0.30</a> supports app 1.0.22920. The practical rule is a single one: the executable&rsquo;s name carries the app version it understands (<code>MSIXKFXArchiverMobi1_22920.exe</code> for Store app 1.0.22920, and so on). Before you start, always check the <a href="https://github.com/Satsuoni/DeDRM_tools/releases"target="_blank" rel="noopener">releases page of Satsuoni&rsquo;s fork</a> and grab the <strong>newest</strong> pre-release. Everything else in the process described here stays the same.</p>
<p>I extracted everything into a directory that would later be shared with the Windows VM:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p ~/Windows/Kindle-DeDRM/v10.0.28
</span></span><span class="line"><span class="cl">unzip ~/Downloads/DeDRM_tools.zip -d ~/Windows/Kindle-DeDRM/v10.0.28</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The SHA-256 for the <code>DeDRM_tools.zip</code> I used was:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">520cce704edf9ae26e43196efe2871daf9b25d6cb489aa56051626801c362947</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Check it with <code>sha256sum</code>. This doesn&rsquo;t magically turn a community binary into safe software, but it at least confirms that you&rsquo;re using the same file I tested.</p>
<p>Inside that directory is <code>DeDRM_plugin.zip</code>. <strong>Do not extract this second ZIP.</strong> In Calibre, open <strong>Preferences → Plugins → Load plugin from file</strong>, choose <code>DeDRM_plugin.zip</code>, accept the warning, and restart the program.</p>
<p>Then go back to <strong>Preferences → Plugins → Get new plugins</strong>, search for <strong>KFX Input</strong>, and install it. My test used version <strong>2.33.0</strong>. Restart again.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/30/kindle-dedrm/calibre-plugins-dedrm-kfx.webp" alt="Calibre 9.11 with DeDRM 10.0.28 and KFX Input 2.33.0 installed."  loading="lazy" /></p>
<h2>Why copying from the Kindle over USB didn&rsquo;t work<span class="hx:absolute hx:-mt-20" id="why-copying-from-the-kindle-over-usb-didnt-work"></span>
    <a href="#why-copying-from-the-kindle-over-usb-didnt-work" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The simplest path would still be plugging in my new Kindle Paperwhite, copying the book, and configuring the device serial number in DeDRM. That was the first thing I tried.</p>
<p>The file came in as <code>KFX-ZIP</code>, the plugin ran, but it remained encrypted. I opened Calibre in debug mode and the log showed that the book used the <code>ACCOUNT_SECRET</code> strategy. The serial number was correct. The required key no longer came from the device alone.</p>
<p>That&rsquo;s why so many people follow an old tutorial, try five different serial numbers, and conclude that DeDRM is broken. New books with this DRM need the secret stored by the current Kindle app on Windows. That secret is protected by TPM APIs.</p>
<h2>A disposable Windows inside Omarchy<span class="hx:absolute hx:-mt-20" id="a-disposable-windows-inside-omarchy"></span>
    <a href="#a-disposable-windows-inside-omarchy" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I&rsquo;m not keeping a dual-boot setup just to download ebooks. I&rsquo;m also not going to pretend Wine runs everything. Fortunately, Omarchy <a href="https://learn.omacom.io/books/2/pages/100"target="_blank" rel="noopener">already ships with everything needed to install and launch a Windows VM</a>: it appears in the Omarchy menu, opens over RDP, and automatically shares <code>~/Windows</code> with the guest. I didn&rsquo;t have to build this integration from scratch.</p>
<p>Underneath, it uses <a href="https://github.com/dockur/windows"target="_blank" rel="noopener">dockur/windows</a>. Dockur packages QEMU/KVM inside a container and automates the Windows installation. It remains a real virtual machine with its own disk and kernel. The container handles distribution, configuration, and lifecycle.</p>
<p>To install it from the terminal, run:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">omarchy-windows-vm install</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Choose the RAM, CPU count, and disk size. The installer creates <code>~/.config/windows/docker-compose.yml</code>, stores the virtual disk in <code>~/.windows</code>, and shares <code>~/Windows</code> with the guest. Inside Windows, that directory appears as <strong>Shared</strong>, usually on <code>Z:</code>, and is also available at <code>\\host.lan\Data</code>.</p>
<p>The detail that mattered in my case was enabling <strong>TPM 2.0</strong>. Edit the generated Compose file and add <code>TPM: &quot;Y&quot;</code> to the <code>environment</code> block:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">windows</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">dockurr/windows</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">VERSION</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;11&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">TPM</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Y&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Then recreate the container. The Windows disk remains in the persistent volume:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">omarchy-windows-vm stop
</span></span><span class="line"><span class="cl">omarchy-windows-vm launch -k</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The <code>-k</code> keeps the VM running after you close RDP. You can also watch it boot in a browser at <code>http://127.0.0.1:8006</code>.</p>
<!-- Screenshot pending: Windows 11 in dockur, open through Omarchy. -->
<h2>Downloading the books with the right app<span class="hx:absolute hx:-mt-20" id="downloading-the-books-with-the-right-app"></span>
    <a href="#downloading-the-books-with-the-right-app" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Inside Windows, open the Microsoft Store and install <strong><a href="https://apps.microsoft.com/detail/9p8jq0jjstll"target="_blank" rel="noopener">Amazon Kindle: Reading App</a></strong>, product <code>9P8JQ0JJSTLL</code>.</p>
<p>Pay attention here. The old Kindle for PC installer found on blogs is not the same app. The tool I used looks for the Microsoft Store package <code>AMZNKindle.AmazonKindleReadingApp</code>. With the legacy program, it simply replies <code>No AmazonKindleReadingApp installation found</code>.</p>
<p>Sign in to your account and download every book you want to preserve. Opening the cover or seeing the title in your library isn&rsquo;t enough. The content must be available offline inside the app.</p>
<p>Unfortunately, the most annoying part is still manual. Amazon found room for a one-click purchase button, but apparently not for a &ldquo;download everything I&rsquo;ve paid for&rdquo; button.</p>
<p>I did this for 106 books. Yes, it was a pain in the ass.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/30/kindle-dedrm/kindle-store-library.webp" alt="The Microsoft Store Kindle app with the ebooks downloaded to its local library."  loading="lazy" /></p>
<h2>Generating KFX-ZIP files and the K4I key<span class="hx:absolute hx:-mt-20" id="generating-kfx-zip-files-and-the-k4i-key"></span>
    <a href="#generating-kfx-zip-files-and-the-k4i-key" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The <code>DeDRM_tools.zip</code> we extracted on Linux is already visible in Windows through the shared directory. Open PowerShell and switch to that directory:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nb">cd </span><span class="p">\\</span><span class="n">host</span><span class="p">.</span><span class="n">lan</span><span class="p">\</span><span class="n">Data</span><span class="p">\</span><span class="nb">Kindle-DeDRM</span><span class="p">\</span><span class="n">v10</span><span class="p">.</span><span class="py">0</span><span class="p">.</span><span class="py">28</span>
</span></span><span class="line"><span class="cl"><span class="p">.\</span><span class="n">MSIXKFXArchiverMobi1_18632</span><span class="p">.</span><span class="n">exe</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The banner may still mention an older <code>1.0.15230</code> build. The versioned executable above looks for Store app <code>1.0.18632</code>, and that&rsquo;s the one that worked for me. The number in the file name must match <strong>your</strong> app version: if the Store already delivered a newer one, download the matching release from the project&rsquo;s page (see the update in the plugins section).</p>
<p>This executable finds the current Microsoft Store app&rsquo;s cache, gathers each book&rsquo;s components, and produces two things:</p>
<ul>
<li><code>archived_kfx/</code>, containing one <code>.kfx-zip</code> for every downloaded ebook;</li>
<li><code>oldbooks.k4i</code>, the keyfile DeDRM needs to open those files.</li>
</ul>
<p>On my machine, the process found and archived <strong>106 books</strong>. The SHA-256 of the executable I ran was:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0c78b45ccea2c36a5fbb01b9f66bdd9e5d5960a68a340ba2ee96e138f5cddf4a</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It is a 32-bit binary. If it complains about a missing <code>MSVCP140.dll</code>, install the <a href="https://aka.ms/vc14/vc_redist.x86.exe"target="_blank" rel="noopener">official Microsoft Visual C++ Redistributable x86</a>. Do not hunt for a loose DLL on Google. That&rsquo;s a great way to turn a book backup into a malware backup.</p>
<p>The <code>oldbooks.k4i</code> file is sensitive. It is derived from your account secret in that Kindle installation. Do not publish it, attach it to a GitHub issue, screenshot its contents, or put it in a public repository.</p>
<!-- Screenshot pending: PowerShell after the archiver generates archived_kfx and oldbooks.k4i. -->
<h2>Configuring the key in DeDRM<span class="hx:absolute hx:-mt-20" id="configuring-the-key-in-dedrm"></span>
    <a href="#configuring-the-key-in-dedrm" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Back on Linux, open Calibre and follow this path:</p>
<p><strong>Preferences → Plugins → DeDRM → Customize plugin → Kindle for Mac/PC ebooks → Import Existing Keyfiles</strong></p>
<p>Select:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">~/Windows/Kindle-DeDRM/v10.0.28/oldbooks.k4i</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Use <strong>Import Existing Keyfiles</strong>. Do not use the generic <strong>Set Keyfile</strong> button on the main screen. It records a different kind of key candidate and does not register the K4I key in the right place.</p>
<p>Close the key list, click <strong>OK</strong> in the main configuration window, and restart Calibre. If you close it with Cancel, it quietly throws the change away.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/30/kindle-dedrm/calibre-dedrm-key.webp" alt="DeDRM with the oldbooks keyfile imported into the Kindle for Mac/PC key list."  loading="lazy" /></p>
<h2>Importing and converting to EPUB<span class="hx:absolute hx:-mt-20" id="importing-and-converting-to-epub"></span>
    <a href="#importing-and-converting-to-epub" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Before dumping a hundred files into the library, test one.</p>
<p>Choose a <code>.kfx-zip</code> from:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">~/Windows/Kindle-DeDRM/v10.0.28/archived_kfx/</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Use <strong>Add books</strong> in Calibre. Internally, the order is:</p>
<ol>
<li>KFX Input recognizes the package;</li>
<li>DeDRM tries the K4I keys during import;</li>
<li>KFX Input assembles the decrypted book as KFX;</li>
<li>Calibre can now open and convert the result.</li>
</ol>
<p>If the format still appears as <code>KFX-ZIP</code>, it failed. Clicking Convert ten times won&rsquo;t help. Remove that entry from the library, fix the plugin or key, and import it again. DeDRM only acts on <strong>input</strong>.</p>
<p>When the book appears as <code>KFX</code>, open it in the viewer and check a few pages. Then click <strong>Convert books</strong>, choose <strong>EPUB</strong> as the output, and test the resulting file on the reader you plan to use. Only then should you run the batch import.</p>
<p>In my case, the KFX opened, converted, and the EPUB worked outside Kindle. That told me the entire path was working: Microsoft Store, TPM, archiver, K4I, DeDRM 10.0.28, and KFX Input 2.33.0.</p>
<!-- Screenshot pending: book imported as KFX in Calibre. -->
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/30/kindle-dedrm/calibre-convert-epub.webp" alt="Calibre’s conversion dialog with EPUB selected as the output format."  loading="lazy" /></p>
<!-- Screenshot pending: final EPUB open on the Xteink X4. -->
<h2>Back up what actually matters<span class="hx:absolute hx:-mt-20" id="back-up-what-actually-matters"></span>
    <a href="#back-up-what-actually-matters" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>After the conversion, I preserve three things:</p>
<ul>
<li>the final EPUBs, which I can read on any device;</li>
<li>the original KFX-ZIP files, until I&rsquo;ve verified that every EPUB is intact;</li>
<li><code>oldbooks.k4i</code>, encrypted and stored separately from the library.</li>
</ul>
<p>My plaintext keyfile has <code>0600</code> permissions. I also keep an encrypted copy using SOPS and age, with the private key in a separate backup. Do not put a raw <code>oldbooks.k4i</code> in Git just because the repository is private. Private repositories leak too.</p>
<p>The EPUBs follow the same strategy I use for other important files: a local copy, NAS, and off-site backup. I explained this paranoia in <a href="/2023/10/19/akitando-146-protegendo-e-recuperando-dados-perdidos-git-backup-btrfs/">Protecting and Recovering Lost Data</a> and showed the same philosophy applied to movies in <a href="/en/2024/04/03/my-personal-netflix-with-docker-compose/">My Personal Netflix</a>.</p>
<p>Do not delete files from the Kindle or the VM the minute conversion finishes. Open the EPUBs and check the cover, table of contents, images, notes, and a few pages. A backup you&rsquo;ve never tested is just wishful thinking.</p>
<h2>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Amazon has remotely deleted books, publishers update covers and content, old apps stop working, and formats change. The end of Download &amp; Transfer via USB proved that a feature available for eighteen years can disappear with one line in a notice.</p>
<p>The classic example is still unbelievable: in 2009, Amazon deleted <code>1984</code> and <code>Animal Farm</code> by George Orwell from customers&rsquo; Kindles. Later, revisions to Roald Dahl, R.L. Stine, and Agatha Christie were pushed into digital copies people had already bought. The <a href="https://www.vice.com/en/article/amazon-is-killing-your-ability-to-download-kindle-books-next-week/"target="_blank" rel="noopener">Vice article</a> summarizes those cases. Dammit Jeff makes the same point often: even the original cover you chose can turn into an ugly poster for the streaming adaptation because the publisher and store decided to update your &ldquo;purchase.&rdquo;</p>
<p>It doesn&rsquo;t matter whether the next change is censorship, a legitimate correction, another horrible Netflix adaptation cover, or a plain bug. I bought an edition. I want to preserve the edition I bought.</p>
<p>DRM does not stop piracy. A popular book lands on torrents the day it comes out. DRM only gets in the paying customer&rsquo;s way, makes accessibility harder, locks up perfectly good hardware, and turns a purchase into an indefinite rental.</p>
<p>Calibre, DeDRM, KFX Input, and a disposable VM gave me my library back. I can now put those EPUBs on the Xteink, a Kobo, a tablet, my phone, or a reader that doesn&rsquo;t even exist yet. I can switch operating systems and turn off the internet. The files stay with me.</p>
<p>If it isn&rsquo;t on your machine, it isn&rsquo;t yours.</p>
]]></content:encoded><category>storage-and-backup</category><category>linux</category><category>open-source</category></item><item><title>How Do I Protect Myself From My Agents Deleting My Stuff?</title><link>https://akitaonrails.github.io/en/2026/07/11/how-to-protect-yourself-from-agents-deleting-your-stuff/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/07/11/how-to-protect-yourself-from-agents-deleting-your-stuff/</guid><pubDate>Sat, 11 Jul 2026 21:00:00 -0300</pubDate><description>&lt;p&gt;This week the talk on Twitter was LLMs deleting other people&amp;rsquo;s files. &lt;a href="https://x.com/mattshumer_/status/2075657271401390161"target="_blank" rel="noopener"&gt;Matt Shumer posted&lt;/a&gt; that GPT-5.6-Sol accidentally deleted almost ALL the files on his Mac. A review subagent expanded the &lt;code&gt;$HOME&lt;/code&gt; variable incorrectly and ran &lt;code&gt;rm -rf /Users/mattsdevbox&lt;/code&gt;. He killed the process midway, but the damage was already done.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/11/agentes-apagando/tweet-mattshumer.webp" alt="Matt Shumer’s tweet: GPT-5.6-Sol accidentally deleted almost all the files on his Mac, running rm -rf /Users/mattsdevbox because of a bad $HOME expansion." loading="lazy" /&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<p>This week the talk on Twitter was LLMs deleting other people&rsquo;s files. <a href="https://x.com/mattshumer_/status/2075657271401390161"target="_blank" rel="noopener">Matt Shumer posted</a> that GPT-5.6-Sol accidentally deleted almost ALL the files on his Mac. A review subagent expanded the <code>$HOME</code> variable incorrectly and ran <code>rm -rf /Users/mattsdevbox</code>. He killed the process midway, but the damage was already done.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/11/agentes-apagando/tweet-mattshumer.webp" alt="Matt Shumer’s tweet: GPT-5.6-Sol accidentally deleted almost all the files on his Mac, running rm -rf /Users/mattsdevbox because of a bad $HOME expansion."  loading="lazy" /></p>
<p>The next day, <a href="https://x.com/goodalexander/status/2076019436767584347"target="_blank" rel="noopener">goodalexander reproduced</a> the same behavior on a disposable devnet: an <code>rsync --delete-delay</code> against <code>/</code> swept away <code>/var</code>, <code>/etc</code> and his validators&rsquo; state, corrupting the filesystem to the point of needing an OS reinstall. His conclusion: &ldquo;you can&rsquo;t use this thing in prod&rdquo;.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/11/agentes-apagando/tweet-goodalexander.webp" alt="goodalexander’s tweet: reproduced GPT 5.6 Sol’s destructive behavior on a disposable devnet, with rsync –delete-delay wiping /var and /etc and corrupting the filesystem."  loading="lazy" /></p>
<p>Cases like these pop up every so often since coding agents went mainstream. And every time, the discussion degenerates into the same two extremes: on one side &ldquo;see? AI is dangerous, never let it run anything on its own&rdquo;, on the other &ldquo;whoever lets an agent loose on their system deserves what they get&rdquo;. I find both sides lazy. Let me tell you my actual experience and what I do about it.</p>
<h2>Five months of marathoning, zero accidents<span class="hx:absolute hx:-mt-20" id="five-months-of-marathoning-zero-accidents"></span>
    <a href="#five-months-of-marathoning-zero-accidents" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Since February I&rsquo;ve been marathoning Claude Code, Codex and OpenCode. Almost every day, almost all the time, in very long sessions that sometimes span multiple days. Blowing past the limits of my Anthropic Max plan and my OpenAI Plus plan with room to spare. The concrete result so far: almost 40 new repositories on my GitHub and more than 500 thousand lines of code produced.</p>
<p>And for the past two months I&rsquo;ve been running everything exclusively in &ldquo;YOLO mode&rdquo;. For those unfamiliar with the jargon: by default, these agents ask for permission before each command they&rsquo;re about to execute in your terminal. YOLO mode turns that safety off. In Claude Code it&rsquo;s the <code>--dangerously-skip-permissions</code> flag, in Codex it&rsquo;s <code>--yolo</code>, in OpenCode you configure the permissions to approve everything. The agent runs whatever it wants, whenever it wants, without asking me anything.</p>
<p>In five months under this regime, not once has an agent deleted a file I didn&rsquo;t ask for. Not once has it run a destructive command out of scope. The frustrations I have with agents are of a different nature (stubbornness, lazy solutions, context getting lost), annoying things but miles away from an <code>rm -rf</code> in my home.</p>
<p>So when I see these reports, my first hypothesis always goes to the same place: the prompt. For an agent to &ldquo;go berserk&rdquo; like that, in general the user wrote very bad instructions. Vague, ambiguous, asking for cleanup without defining what counts as trash, telling the agent to &ldquo;fix it&rdquo; without giving context. Lack of communication skills combined with lack of technical knowledge to notice the request was poorly formulated. The agent executes the instruction you wrote, with all the ambiguities you left in it.</p>
<p>That said, I do NOT trust LLMs completely. Nobody should.</p>
<blockquote>
  <p>The difference is that my distrust turns into engineering instead of turning into a tweet.</p>

</blockquote>
<p>Layer by layer, this is how I sleep well at night.</p>
<h2>Layer 1: sandboxing with ai-jail<span class="hx:absolute hx:-mt-20" id="layer-1-sandboxing-with-ai-jail"></span>
    <a href="#layer-1-sandboxing-with-ai-jail" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>When I genuinely suspect a session might do something stupid (I&rsquo;m about to touch system scripts, I&rsquo;m going to let the agent run for hours unsupervised on code that touches the filesystem), I open the harness inside <a href="https://github.com/akitaonrails/ai-jail"target="_blank" rel="noopener">ai-jail</a>, a tool of mine, open source, written in Rust.</p>
<p>ai-jail is a sandbox wrapper for coding agents. On Linux it uses <code>bwrap</code> (bubblewrap, the same foundation as Flatpak), on macOS it uses <code>sandbox-exec</code>. The idea: the process sees a filesystem that looks normal, but only the current project directory is real and persistent. The rest of <code>$HOME</code>, the sibling directories, <code>/tmp</code>, everything lives in tmpfs and evaporates when the session ends. The agent&rsquo;s state (<code>~/.claude</code>, <code>~/.codex</code> etc) is mounted for real so it stays logged in and configured, but <code>~/.ssh</code>, <code>~/.gnupg</code>, <code>~/.aws</code> and browser profiles never enter the jail.</p>
<p>Installing is one command:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Arch Linux</span>
</span></span><span class="line"><span class="cl">yay -S ai-jail-bin
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># macOS / Linux via Homebrew</span>
</span></span><span class="line"><span class="cl">brew tap akitaonrails/tap <span class="o">&amp;&amp;</span> brew install ai-jail
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># or straight from crates.io</span>
</span></span><span class="line"><span class="cl">cargo install ai-jail</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And using it is even simpler:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> ~/Projects/my-app
</span></span><span class="line"><span class="cl">ai-jail claude       <span class="c1"># Claude Code in the jail</span>
</span></span><span class="line"><span class="cl">ai-jail codex        <span class="c1"># Codex in the jail</span>
</span></span><span class="line"><span class="cl">ai-jail --dry-run claude  <span class="c1"># shows the policy without running</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>If the agent decides to run <code>rm -rf ~</code> in there, it deletes a tmpfs that was going to vanish anyway. My real home was never even mounted. For anyone starting out with YOLO mode without battle scars yet, it&rsquo;s the cheapest way to make mistakes without consequences.</p>
<h2>Layer 2: BTRFS and snapshots (the real safety net)<span class="hx:absolute hx:-mt-20" id="layer-2-btrfs-and-snapshots-the-real-safety-net"></span>
    <a href="#layer-2-btrfs-and-snapshots-the-real-safety-net" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Now the main point of this post. I&rsquo;ve been advocating for YEARS that Linux users should use BTRFS instead of EXT4. I made <a href="/2023/10/19/akitando-146-protegendo-e-recuperando-dados-perdidos-git-backup-btrfs/">an entire Akitando episode about it in 2023</a>, long before coding agents existed to take the blame. The argument was the same: your data needs to survive human error. An LLM running <code>rm -rf</code> in the wrong place is just a new category of human error, outsourced.</p>
<p>Why is BTRFS superior for desktops? Copy-on-write (CoW): the filesystem never overwrites a block in place, it writes the new version to another block and updates the pointers. Checksums for data and metadata, so silent bitrot gets detected instead of quietly corrupting your file. Transparent compression. Subvolumes, which work like flexible partitions without fixed sizes. And the most valuable consequence of CoW: <strong>lightweight snapshots</strong>.</p>
<p>A BTRFS snapshot is instantaneous and costs close to zero space the moment it&rsquo;s created. It freezes the pointers to the current blocks instead of copying data. Only what diverges from that point on (new or modified blocks) starts taking up extra space. In practice you can take dozens of snapshots a day without feeling it. At this very moment my machine keeps 23 system snapshots, and the cost is the difference between them, a few gigs.</p>
<h3>Scheduling snapshots<span class="hx:absolute hx:-mt-20" id="scheduling-snapshots"></span>
    <a href="#scheduling-snapshots" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The two classic tools are <strong>Timeshift</strong> and <strong>snapper</strong>. Timeshift is the friendlier one: graphical interface, you set &ldquo;how many snapshots to keep&rdquo; per hour/day/week/month and it handles the rest. It expects the <code>@</code> (root) and <code>@home</code> subvolume layout, which is the default for most distros that install on BTRFS today. snapper (born at openSUSE) is more granular: per-subvolume configs, automatic timeline, package manager integration to snapshot before and after every install.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Timeshift: manual snapshot before a suspicious session</span>
</span></span><span class="line"><span class="cl">sudo timeshift --create --comments <span class="s2">&#34;antes de soltar o agente&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># snapper: create a config for /home and enable the timeline</span>
</span></span><span class="line"><span class="cl">sudo snapper -c home create-config /home
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> --now snapper-timeline.timer</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>My setup runs Timeshift on a schedule, plus <code>grub-btrfsd</code> on standby (I&rsquo;ll explain why in a moment).</p>
<h3>Recovering deleted files<span class="hx:absolute hx:-mt-20" id="recovering-deleted-files"></span>
    <a href="#recovering-deleted-files" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Here&rsquo;s the magic that everyone who lost files to an agent wishes they had. A BTRFS snapshot is browsable like a regular directory. The agent deleted your project at 3pm? The 2pm snapshot still has everything:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># with Timeshift, snapshots are mounted under /run/timeshift</span>
</span></span><span class="line"><span class="cl">sudo ls /run/timeshift/backup/timeshift-btrfs/snapshots/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># found the snapshot from before the damage? copy it back and done</span>
</span></span><span class="line"><span class="cl">sudo cp -a /run/timeshift/backup/timeshift-btrfs/snapshots/2026-07-11_14-00-01/@home/akitaonrails/Projects/meu-app ~/Projects/</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>With snapper it&rsquo;s the same thing via <code>.snapshots</code> inside the subvolume, and it also has <code>snapper undochange</code> to revert specific changes between two snapshots. Recovery in seconds, no drama, no forensic tooling.</p>
<h3>What if the system won&rsquo;t even boot?<span class="hx:absolute hx:-mt-20" id="what-if-the-system-wont-even-boot"></span>
    <a href="#what-if-the-system-wont-even-boot" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>goodalexander&rsquo;s case went beyond losing files: the agent swept <code>/var</code> and <code>/etc</code> and the system became unusable, requiring an OS reinstall. With BTRFS not even that would be necessary. This is the scenario <a href="https://github.com/Antynea/grub-btrfs"target="_blank" rel="noopener">grub-btrfs</a> exists for: a daemon that watches your snapshots and regenerates the GRUB menu with a boot entry for each one.</p>
<p>System won&rsquo;t come up anymore? Reboot, hold the GRUB menu, pick &ldquo;snapshots&rdquo; and boot straight into yesterday&rsquo;s snapshot, read-only. From inside it, run <code>timeshift --restore</code>, pick the good snapshot, reboot again. Entire system back to its pre-disaster state, in less time than it would take you to find the installation USB stick. In the absolute worst case (GRUB itself went down with the ship), any live USB mounts the BTRFS partition, points the default subvolume back to the good snapshot and that&rsquo;s that.</p>
<h3>What about macOS? And Windows?<span class="hx:absolute hx:-mt-20" id="what-about-macos-and-windows"></span>
    <a href="#what-about-macos-and-windows" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Mac users have the direct equivalent: <strong>APFS</strong>, the default filesystem since High Sierra (2017), is also copy-on-write and also has lightweight snapshots. Time Machine is what manages them: with it enabled, macOS takes automatic local snapshots every hour and keeps the last 24 hours on the disk itself, independent of the external backup. And you can create one by hand before letting the agent loose:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># create an APFS local snapshot right now</span>
</span></span><span class="line"><span class="cl">tmutil localsnapshot
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># list the existing snapshots</span>
</span></span><span class="line"><span class="cl">tmutil listlocalsnapshots /</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>To recover, open Time Machine itself (&ldquo;Browse Time Machine Backups&rdquo; in the menu bar): it browses the local snapshots even without any external disk plugged in, and you restore the file from there. If you prefer the terminal, mount the snapshot read-only with <code>mount_apfs -s</code> and copy it back. In other words, Matt Shumer&rsquo;s case from the beginning of this post had a native solution on his own Mac: a snapshot from one hour earlier would have brought almost everything back.</p>
<p>On Windows the story is weaker. NTFS has the Volume Shadow Copy service (VSS), which feeds restore points and the &ldquo;Previous Versions&rdquo; tab, but it&rsquo;s aimed at system files and rarely happens to be covering your data at the moment you need it. Nothing with the lightness and predictability of a scheduled CoW snapshot. If you develop on Windows, treat external backup as mandatory, no discussion (and the restic from the next section runs fine on Windows).</p>
<h2>Layer 3: external backups with restic<span class="hx:absolute hx:-mt-20" id="layer-3-external-backups-with-restic"></span>
    <a href="#layer-3-external-backups-with-restic" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Snapshots live on the same disk as the data. If the NVMe dies, they die together. Which is the other thing I&rsquo;ve been repeating for years: <strong>everybody needs external backups</strong>, whether to an external hard drive or a NAS. I use a Synology on the local network, mounted via NFS, and <a href="/en/2025/04/17/configuring-my-synology-nas-with-nfs-on-linux/">documented the complete setup here</a>.</p>
<p>On top of it runs <a href="https://restic.net/"target="_blank" rel="noopener">restic</a>, which in my opinion is the best backup tool for this purpose: deduplication, encryption, incremental snapshots and simple restores. My actual configuration is a script fired by a systemd timer every day at 3am. It checks the NAS is mounted, backs up <code>/home</code> and <code>/mnt/data/Projects</code> skipping caches (<code>node_modules</code>, Rust&rsquo;s <code>target/</code>, <code>__pycache__</code> and friends, all in an excludes file), and then applies the retention policy: 7 daily, 4 weekly, 6 monthly, 1 yearly.</p>
<p>Reproducing the essentials looks like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># once: create the repository on the NAS</span>
</span></span><span class="line"><span class="cl">restic init --repo /mnt/nas/desktop-backup
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># every day (via cron or a systemd timer):</span>
</span></span><span class="line"><span class="cl">restic -r /mnt/nas/desktop-backup backup ~ /mnt/data/Projects <span class="se">\
</span></span></span><span class="line"><span class="cl">    --exclude-file<span class="o">=</span>~/.config/restic/excludes --exclude-caches
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">restic -r /mnt/nas/desktop-backup forget <span class="se">\
</span></span></span><span class="line"><span class="cl">    --keep-daily <span class="m">7</span> --keep-weekly <span class="m">4</span> --keep-monthly <span class="m">6</span> --keep-yearly <span class="m">1</span> --prune</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And if the truly bad day arrives (destructive agent, dead disk, ransomware, the cause doesn&rsquo;t matter):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># list the available snapshots</span>
</span></span><span class="line"><span class="cl">restic -r /mnt/nas/desktop-backup snapshots
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># restore a specific file or directory</span>
</span></span><span class="line"><span class="cl">restic -r /mnt/nas/desktop-backup restore latest <span class="se">\
</span></span></span><span class="line"><span class="cl">    --target /tmp/recuperado --include /home/akitaonrails/Projects/meu-app
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># or mount the whole repository as a filesystem and browse at your leisure</span>
</span></span><span class="line"><span class="cl">restic -r /mnt/nas/desktop-backup mount /mnt/restic</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Notice the layered architecture: the BTRFS snapshot solves the accident from minutes ago in seconds. restic solves the disaster from days ago, including dead hardware. An AI agent would have to be very dedicated to punch through both layers, and even then there would still be yesterday&rsquo;s copy on the NAS, which it can&rsquo;t even reach.</p>
<h2>What about companies? Professionalism<span class="hx:absolute hx:-mt-20" id="what-about-companies-professionalism"></span>
    <a href="#what-about-companies-professionalism" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Everything above is about my personal machine. Now let&rsquo;s raise the bar: corporate environments.</p>
<p>Rule number one is simple and non-negotiable: <strong>no AI agent should ever have access to production. Ever.</strong> No direct SSH into production servers, no production database credentials, no admin tokens for production services. If your agent can run a <code>DELETE</code> against the database that serves customers, the problem stopped being the AI a long time ago: any intern with the same access is the same time bomb, just with a slower typing speed.</p>
<p>Agents in <strong>staging</strong>, on the other hand, I advocate for. That&rsquo;s what staging is for. But even in staging there&rsquo;s a right way: you resist the temptation of prompting one-shot actions (&ldquo;run an <code>apt install</code> over there&rdquo;, &ldquo;do a <code>systemctl restart</code>&rdquo;, &ldquo;connect to postgres and execute this&rdquo;). A loose command executed by hand is knowledge that evaporates. Your infrastructure needs to be automated and reproducible: Ansible, Kubernetes, Terraform, whatever makes sense for your stack. The agent&rsquo;s role becomes writing and refining those recipes, which live versioned in git.</p>
<h3>A small example, right here from my desk<span class="hx:absolute hx:-mt-20" id="a-small-example-right-here-from-my-desk"></span>
    <a href="#a-small-example-right-here-from-my-desk" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I have a gaming environment on my main PC, but I refuse to mix emulator packages with my software development environment. So I isolated gaming inside a <a href="https://github.com/89luca89/distrobox"target="_blank" rel="noopener">distrobox</a>. For those who don&rsquo;t know it: distrobox creates containers (via podman or docker) that integrate with your desktop as if they were native. They share your home (or a dedicated home), GPU access, Wayland, audio. In practice it&rsquo;s a complete distro inside your distro, with a separate package space.</p>
<p><a href="https://github.com/akitaonrails/distrobox-gaming"target="_blank" rel="noopener">distrobox-gaming</a> is a repository of Ansible playbooks that builds an Arch box called <code>gaming</code> from scratch: ES-DE as the frontend, standalone emulators (shadPS4, RPCS3, PCSX2, Dolphin, DuckStation, Eden and a dozen more), RetroArch cores, per-game optimized configs, launcher shortcuts on the host. And <a href="https://github.com/akitaonrails/distrobox-llm"target="_blank" rel="noopener">distrobox-llm</a> does the same for a local LLM work box: CUDA, cuDNN, Ollama, whisper.cpp and LM Studio isolated in a box with <code>--nvidia</code>, while the host only carries the driver. A CUDA major version upgrade no longer rattles my development environment.</p>
<p>Who wrote the overwhelming majority of these playbooks? The agents. Instead of asking &ldquo;install RPCS3 for me&rdquo;, my prompt is &ldquo;write the idempotent Ansible recipe that installs and configures RPCS3, run it, verify the result and fix it until it passes&rdquo;. Here&rsquo;s a real excerpt from the role that creates the LLM box:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Check if distrobox exists</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ansible.builtin.shell</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;distrobox list 2&gt;/dev/null | awk &#39;{print $3}&#39; | grep -qx {{ dl_box_name }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">register</span><span class="p">:</span><span class="w"> </span><span class="l">_box_exists</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">changed_when</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">failed_when</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create distrobox</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ansible.builtin.command</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">    distrobox create
</span></span></span><span class="line"><span class="cl"><span class="sd">    --name {{ dl_box_name }}
</span></span></span><span class="line"><span class="cl"><span class="sd">    --image {{ dl_image }}
</span></span></span><span class="line"><span class="cl"><span class="sd">    --home {{ dl_box_home }}
</span></span></span><span class="line"><span class="cl"><span class="sd">    {{ &#39;--nvidia&#39; if dl_nvidia_enabled else &#39;&#39; }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">when</span><span class="p">:</span><span class="w"> </span><span class="l">_box_exists.rc != 0</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Idempotent: it checks whether the box exists before creating it, running it twice gives the same result. If my PC catches fire tomorrow, I recreate both environments in full with one <code>ansible-playbook site.yml</code> in each repo. The knowledge lives in the recipe, versioned, auditable. Zero commands lost in shell history.</p>
<h3>Scaling the same principle to production<span class="hx:absolute hx:-mt-20" id="scaling-the-same-principle-to-production"></span>
    <a href="#scaling-the-same-principle-to-production" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>At a company, swap distrobox for Kubernetes and the principle is identical. The agent writes the manifest, tests it on the staging cluster, you promote it to production manually:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">minha-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">minha-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">minha-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">minha-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">registry.exemplo.com/minha-api:v42</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">readinessProbe</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">httpGet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">/healthz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="l">512Mi</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The professional flow: the agent produces and adjusts the manifest (or the Helm chart, or the Terraform module), applies it to staging (<code>kubectl apply -n staging</code>), runs the tests, watches for side effects, bugs, unintended behavior, and iterates until you&rsquo;re satisfied. Then YOU, a human, promote it to production (via a reviewed PR in a GitOps flow, or a manual and deliberate <code>kubectl apply</code>), monitoring every step. The agent did all the heavy lifting: the artifacts, the procedures, the checklists. You can even ask it to generate the audit documentation at the end. But the finger that presses the production button has a name on a payroll.</p>
<h3>Even the &ldquo;simple&rdquo; maintenance SQL<span class="hx:absolute hx:-mt-20" id="even-the-simple-maintenance-sql"></span>
    <a href="#even-the-simple-maintenance-sql" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A case that looks harmless and bothers me a lot: maintenance SQL commands in production. Plenty of people have always run those by hand, straight into the production client, and are now asking the agent to write AND execute them. That is unacceptable, with or without AI. Your system should have a <strong>migrations</strong> workflow: the change becomes versioned code, runs in staging first, gets validated, and only then executes in production inside the normal deploy process.</p>
<p>And &ldquo;production&rdquo;, by definition, means two things many companies pretend to have. First: real backups, tested, with rehearsed restores.</p>
<blockquote>
  <p>Never run anything in production that you can&rsquo;t recover from.</p>

</blockquote>
<p>Second: monitoring. If you have no visibility into the state of the system, you don&rsquo;t have production, you have a server plugged into an outlet and a prayer.</p>
<h2>Monitoring: even on my humble PC<span class="hx:absolute hx:-mt-20" id="monitoring-even-on-my-humble-pc"></span>
    <a href="#monitoring-even-on-my-humble-pc" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>And monitoring is so much a part of the discipline that I apply it even to my personal machine. I recently developed a system health widget for my <a href="https://github.com/akitaonrails/clock-tui"target="_blank" rel="noopener">clock-tui</a> (my maintained fork of the original clock-tui, with command widgets in clock mode). It sits open in a corner showing this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/11/agentes-apagando/system-health-widget.webp" alt="clock-tui’s system health widget: restic backups ok 19h ago, 23 timeshift snapshots, dev-cache cleanup ok, btrfs scrub and trim up to date, zero IO errors, load, memory and storage usage per filesystem."  loading="lazy" /></p>
<p>One verdict line (&ldquo;all systems healthy&rdquo;) and the state of everything this post described: the restic backup ran 19 hours ago and the next one is at 03:03; Timeshift keeps 23 snapshots; the development cache cleanup ran 32 hours ago; the BTRFS scrub is up to date on each filesystem, with a recent trim, healthy allocation and zero I/O errors; plus zombies, load, memory and each disk&rsquo;s usage. The widget (<code>tclock-system-health</code>) ships with the repository and the AUR package, and detects most of this on its own.</p>
<p>Behind it are the automated maintenance jobs, all systemd timers: the daily restic at 3am; <code>dev-cache-clean</code> (on the 1st and the 15th) sweeping orphaned Rust <code>target/</code> dirs and the yay cache, which together give back dozens of gigs; the monthly per-filesystem <code>btrfs-scrub</code> validating checksums; the weekly <code>fstrim</code>. None of it requires me to remember anything. When one of them is late or fails, the widget changes color right in my face.</p>
<p>This is the home-grade level, proportional to a personal PC. At a company you go full-blown: Prometheus collecting metrics, Grafana drawing the dashboards, Alertmanager waking up the on-call person at 3am. The scale changes, the principle is the same: the state of the system visible at all times, and someone (or something) getting notified when it goes off the rails.</p>
<h2>Conclusion: discipline<span class="hx:absolute hx:-mt-20" id="conclusion-discipline"></span>
    <a href="#conclusion-discipline" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Go back to the two tweets at the beginning. In both cases, a single wrong command caused real loss, and the public discussion became &ldquo;who do I trust more&rdquo;. The right question is a different one: <strong>why does a single wrong command, from any origin, have the power to cause unrecoverable loss on your machine?</strong></p>
<p>Being a professional is being disciplined, and discipline starts at home. My personal PC operates with the same rigor I would demand from a corporate environment: a filesystem with lightweight scheduled snapshots, recoverable boot via GRUB, daily external backups with a retention policy, a sandbox for suspicious sessions, isolated environments rebuildable from versioned recipes, automated maintenance and a monitor that warns me when any piece falls behind. Monthly maintenance cost of all this: close to zero. It was set up once (in good part by the agents themselves) and runs on its own.</p>
<p>With these layers, the question in the title almost loses its meaning. The agent deleted a directory? Snapshot from an hour ago, two minutes of recovery. Destroyed the system? Boot into a snapshot via GRUB, restore, ten minutes. Fried the disk along with it? restic on the NAS, yesterday&rsquo;s backup. In five months of YOLO mode I never needed any of these nets for an AI accident. But I sleep well precisely because they&rsquo;re there.</p>
<p>An LLM running loose on your system is just the most recent stress test of your discipline. If an out-of-control agent can cause permanent loss to your data, the vulnerability was always there, waiting for a mistake of yours, a bad disk or a ransomware. The agent was just the messenger that arrived first.</p>
]]></content:encoded><category>ai-jail</category><category>coding-agents</category><category>security</category><category>storage-and-backup</category></item><item><title>Backing Up Gmail to Maildir on Linux</title><link>https://akitaonrails.github.io/en/2026/05/28/backing-up-gmail-to-maildir-on-linux/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/05/28/backing-up-gmail-to-maildir-on-linux/</guid><pubDate>Thu, 28 May 2026 16:00:00 -0300</pubDate><description>&lt;p&gt;Most people just trust Google and leave everything there. Anyone who follows this blog knows I don&amp;rsquo;t trust anyone: if it&amp;rsquo;s in the cloud, it&amp;rsquo;s not mine. That&amp;rsquo;s why I keep a NAS at home to back up everything that matters. I don&amp;rsquo;t trust Google Drive, OneDrive, Dropbox, or Gmail.&lt;/p&gt;
&lt;p&gt;Email has always been the most annoying piece of this puzzle. My old workflow was to open Thunderbird, let it pull everything from Gmail over IMAP, and back up the &lt;code&gt;~/.thunderbird&lt;/code&gt; directory. Works, but it&amp;rsquo;s manual, tedious (remembering to open Thunderbird every so often), and locked to Thunderbird itself: if I want to read those emails in another client, they&amp;rsquo;re sitting in some proprietary internal format.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Most people just trust Google and leave everything there. Anyone who follows this blog knows I don&rsquo;t trust anyone: if it&rsquo;s in the cloud, it&rsquo;s not mine. That&rsquo;s why I keep a NAS at home to back up everything that matters. I don&rsquo;t trust Google Drive, OneDrive, Dropbox, or Gmail.</p>
<p>Email has always been the most annoying piece of this puzzle. My old workflow was to open Thunderbird, let it pull everything from Gmail over IMAP, and back up the <code>~/.thunderbird</code> directory. Works, but it&rsquo;s manual, tedious (remembering to open Thunderbird every so often), and locked to Thunderbird itself: if I want to read those emails in another client, they&rsquo;re sitting in some proprietary internal format.</p>
<p>After a while it clicked: Linux has had native email support forever. The modern protocols (IMAP, SMTP) are open standards. And the local storage format, <strong>Maildir</strong>, has been around and working well since the 90s. Thunderbird isn&rsquo;t needed for any of this.</p>
<h2>What is Maildir<span class="hx:absolute hx:-mt-20" id="what-is-maildir"></span>
    <a href="#what-is-maildir" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Maildir was created by Daniel J. Bernstein (djb) as part of qmail in 1995. The idea was to fix the file-locking pain of the mbox format, where the entire mailbox is a single file and two processes writing at the same time could corrupt the whole thing. In Maildir, <strong>each message becomes a separate file</strong> inside a three-subdirectory structure:</p>
<ul>
<li><code>tmp/</code> — where the email is written while being delivered</li>
<li><code>new/</code> — newly arrived messages, not yet seen</li>
<li><code>cur/</code> — messages the client has already seen, with flags encoded in the filename (<code>:2,S</code> = seen, <code>:2,F</code> = flagged, etc.)</li>
</ul>
<p>Delivery is atomic: the MTA writes the message into <code>tmp/</code>, then does a <code>rename()</code> into <code>new/</code>. <code>rename()</code> on a POSIX filesystem is atomic, so there&rsquo;s no corruption window. No locking, no corruption, and every email is just a plain-text file you can read with <code>cat</code>, index with <code>grep</code>, or version with git if you feel like it.</p>
<p>Thirty years later, it&rsquo;s still the default local format for most Linux email clients. And it&rsquo;s the format I&rsquo;ll use to mirror Gmail.</p>
<h2>The tool: mbsync (isync)<span class="hx:absolute hx:-mt-20" id="the-tool-mbsync-isync"></span>
    <a href="#the-tool-mbsync-isync" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The bridge between Gmail&rsquo;s IMAP and the local Maildir is <a href="https://isync.sourceforge.io/"target="_blank" rel="noopener"><code>mbsync</code></a>, the binary in the <code>isync</code> package. It reads a simple config (<code>~/.config/isyncrc</code>), connects to Gmail&rsquo;s IMAP, and copies the messages to disk. What makes it different from <code>offlineimap</code> (the better-known alternative) is that <code>mbsync</code> is written in C, it&rsquo;s significantly faster on large mailboxes, and it has fewer weird edge cases with Gmail.</p>
<p>The config is straightforward. Here&rsquo;s a sketch of what my <code>~/.config/isyncrc</code> looks like (no password, obviously):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">IMAPAccount my-account</span>
</span></span><span class="line"><span class="cl"><span class="na">Host imap.gmail.com</span>
</span></span><span class="line"><span class="cl"><span class="na">User you@example.com</span>
</span></span><span class="line"><span class="cl"><span class="na">PassCmd &#34;/home/akitaonrails/.config/mbsync/bin/mbsync-password you@example.com&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">TLSType IMAPS</span>
</span></span><span class="line"><span class="cl"><span class="na">CertificateFile /etc/ssl/certs/ca-certificates.crt</span>
</span></span><span class="line"><span class="cl"><span class="na">AuthMechs LOGIN</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="na">IMAPStore my-remote</span>
</span></span><span class="line"><span class="cl"><span class="na">Account my-account</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="na">MaildirStore my-local</span>
</span></span><span class="line"><span class="cl"><span class="na">SubFolders Verbatim</span>
</span></span><span class="line"><span class="cl"><span class="na">Path ~/.local/share/mail/mbsync/you.example.com/</span>
</span></span><span class="line"><span class="cl"><span class="na">Inbox ~/.local/share/mail/mbsync/you.example.com/INBOX</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="na">Channel my-backup</span>
</span></span><span class="line"><span class="cl"><span class="na">Far :my-remote:</span>
</span></span><span class="line"><span class="cl"><span class="na">Near :my-local:</span>
</span></span><span class="line"><span class="cl"><span class="na">Patterns * !&#34;[Gmail]/Trash&#34; !&#34;[Gmail]/Spam&#34; !&#34;[Gmail]/Drafts&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">Create Near</span>
</span></span><span class="line"><span class="cl"><span class="na">Sync Pull New Flags</span>
</span></span><span class="line"><span class="cl"><span class="na">Expunge None</span>
</span></span><span class="line"><span class="cl"><span class="na">Remove None</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Three things worth flagging in that config:</p>
<ol>
<li><strong><code>Sync Pull</code></strong>: only pulls from the server to local. Nothing I do in the Maildir propagates back to Gmail. That&rsquo;s accident protection (if I delete a file locally, it doesn&rsquo;t disappear from Gmail).</li>
<li><strong><code>Expunge None</code> + <code>Remove None</code></strong>: if a message disappears from Gmail (someone deleted it or Google decided to move it nowhere), it <strong>stays</strong> in my Maildir. The backup accumulates history, it doesn&rsquo;t mirror the current state.</li>
<li><strong><code>PassCmd</code> instead of a plaintext password</strong>: the password goes through a script that does <code>secret-tool lookup</code> against the desktop keyring. No password sitting in a config file on disk.</li>
</ol>
<h3>The password block<span class="hx:absolute hx:-mt-20" id="the-password-block"></span>
    <a href="#the-password-block" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The <code>mbsync-password</code> script is a tiny wrapper that isolates the secret:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="nb">set</span> -euo pipefail
</span></span><span class="line"><span class="cl"><span class="nv">account</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">1</span><span class="p">:?usage: mbsync-password ACCOUNT</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">password</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>secret-tool lookup service mbsync account <span class="s2">&#34;</span><span class="nv">$account</span><span class="s2">&#34;</span> 2&gt;/dev/null <span class="o">||</span> <span class="nb">true</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$password</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="o">{</span> <span class="nb">echo</span> <span class="s2">&#34;missing keyring secret for </span><span class="nv">$account</span><span class="s2">&#34;</span> &gt;<span class="p">&amp;</span>2<span class="p">;</span> <span class="nb">exit</span> 1<span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">printf</span> <span class="s1">&#39;%s&#39;</span> <span class="s2">&#34;</span><span class="nv">$password</span><span class="s2">&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The password gets stored once with <code>secret-tool store --label=&quot;mbsync gmail&quot; service mbsync account you@example.com</code>. Modern Gmail requires a specific <strong>app password</strong> (16 characters, generated at <a href="https://myaccount.google.com/apppasswords"target="_blank" rel="noopener">myaccount.google.com/apppasswords</a>) because it no longer accepts plain basic auth — you don&rsquo;t paste your Google password there, you paste a key generated exclusively for the app, revocable any time without touching the rest of your account.</p>
<h3>Automation with a systemd user timer<span class="hx:absolute hx:-mt-20" id="automation-with-a-systemd-user-timer"></span>
    <a href="#automation-with-a-systemd-user-timer" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>To avoid running mbsync by hand, I set up two systemd user units. First the service, which is just a call to the wrapper script:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># ~/.config/systemd/user/mbsync-backup.service</span>
</span></span><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Mirror Gmail accounts locally with mbsync</span>
</span></span><span class="line"><span class="cl"><span class="na">Wants</span><span class="o">=</span><span class="s">network-online.target</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">%h/.config/mbsync/bin/mbsync-run</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And the timer that fires the service periodically:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># ~/.config/systemd/user/mbsync-backup.timer</span>
</span></span><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Run mbsync periodically</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Timer]</span>
</span></span><span class="line"><span class="cl"><span class="na">OnBootSec</span><span class="o">=</span><span class="s">10m</span>
</span></span><span class="line"><span class="cl"><span class="na">OnUnitActiveSec</span><span class="o">=</span><span class="s">30m</span>
</span></span><span class="line"><span class="cl"><span class="na">Persistent</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">Unit</span><span class="o">=</span><span class="s">mbsync-backup.service</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">timers.target</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><code>OnBootSec=10m</code> waits 10 minutes after boot before the first run (avoids fighting with <code>network-online</code> early in the session). <code>OnUnitActiveSec=30m</code> refires 30 minutes after each run completes. <code>Persistent=true</code> makes the timer catch up: if the machine was off when it would&rsquo;ve fired, it runs at the next boot instead of skipping. The wrapper itself grabs <code>flock</code> so two mbsync runs never overlap, so if a sync ever takes longer than 30 minutes the next one just waits. Everything logs to <code>~/.local/state/mbsync/mbsync.log</code>.</p>
<p>To manage the timer, the basic commands:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Re-read the unit files after creating/editing</span>
</span></span><span class="line"><span class="cl">systemctl --user daemon-reload
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start now + enable for the next boot</span>
</span></span><span class="line"><span class="cl">systemctl --user <span class="nb">enable</span> --now mbsync-backup.timer
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Check whether the timer is active and when it fires next</span>
</span></span><span class="line"><span class="cl">systemctl --user status mbsync-backup.timer
</span></span><span class="line"><span class="cl">systemctl --user list-timers mbsync-backup.timer
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Trigger an immediate sync without waiting for the interval</span>
</span></span><span class="line"><span class="cl">systemctl --user start mbsync-backup.service
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Tail the logs in real time</span>
</span></span><span class="line"><span class="cl">journalctl --user -u mbsync-backup.service -f
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Inspect the status of the last service run</span>
</span></span><span class="line"><span class="cl">systemctl --user status mbsync-backup.service
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># To shut the whole thing down</span>
</span></span><span class="line"><span class="cl">systemctl --user disable --now mbsync-backup.timer</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>For the timer to keep running when you&rsquo;re not logged in (handy on a desktop that stays up 24/7), enable linger once: <code>sudo loginctl enable-linger $USER</code>. Without that, user units only run while you have an active session.</p>
<p>Enable it, check the status once to make sure it&rsquo;s green, and forget it exists.</p>
<h2>Backing up the Maildir with restic<span class="hx:absolute hx:-mt-20" id="backing-up-the-maildir-with-restic"></span>
    <a href="#backing-up-the-maildir-with-restic" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Now that Gmail is mirrored under <code>~/.local/share/mail/</code>, it rides into the same backup pipeline as the rest of my home directory. I use <a href="https://restic.net/"target="_blank" rel="noopener">restic</a> to push encrypted snapshots to the NAS. Restic is incremental via content-addressable storage, so adding 100MB of email to today&rsquo;s snapshot costs basically the delta, not the full 100MB every time. For the off-site copy, my Synology NAS already has the Glacier Backup package built in, which handles pushing encrypted snapshots to AWS Glacier without me having to configure anything beyond the bucket and the key. The Maildir rides along on the same plan, so the off-site backup is essentially free in terms of extra configuration.</p>
<p>Maildir is friendly to incremental backup by construction: every email is an immutable file (the filename doesn&rsquo;t even change after download, except for the seen/flagged flag bit). Restic dedups perfectly. A 5GB mailbox snapshot has near-zero marginal cost after the first one.</p>
<h2>The open-format upside: many clients<span class="hx:absolute hx:-mt-20" id="the-open-format-upside-many-clients"></span>
    <a href="#the-open-format-upside-many-clients" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Here&rsquo;s the part that sealed it for me. Maildir is an open standard, so <strong>any client that speaks Maildir reads the same backup</strong>. I can:</p>
<ul>
<li>Open it in <a href="http://www.mutt.org/"target="_blank" rel="noopener">mutt</a> or <a href="https://neomutt.org/"target="_blank" rel="noopener">neomutt</a> when I want something quick in the terminal.</li>
<li>Open it in <a href="https://aerc-mail.org/"target="_blank" rel="noopener">aerc</a> when I want a more modern TUI.</li>
<li>Point Thunderbird or <a href="https://www.claws-mail.org/"target="_blank" rel="noopener">Claws Mail</a> at <code>~/.local/share/mail/...</code> when I want a GUI.</li>
<li>Migrate to a different client down the road without an export/import dance.</li>
</ul>
<p>The emails are mine. They live on my disk. In a plain text format, readable by anything.</p>
<h2>Bonus: scripts and bots read directly<span class="hx:absolute hx:-mt-20" id="bonus-scripts-and-bots-read-directly"></span>
    <a href="#bonus-scripts-and-bots-read-directly" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A less obvious consequence: if I want to build a filter, a bot, a classifier, or anything that touches the emails, I <strong>don&rsquo;t need to go through Gmail OAuth, create a Google Cloud project, manage refresh tokens, deal with quotas</strong> — none of it. I just read the Maildir.</p>
<p>Python has native Maildir support in the standard library&rsquo;s <code>mailbox</code> module. A trivial example to list subjects from the inbox:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">mailbox</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">email.header</span> <span class="kn">import</span> <span class="n">decode_header</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">inbox</span> <span class="o">=</span> <span class="n">mailbox</span><span class="o">.</span><span class="n">Maildir</span><span class="p">(</span><span class="s2">&#34;/home/akitaonrails/.local/share/mail/mbsync/you.example.com/INBOX&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">msg</span> <span class="ow">in</span> <span class="n">inbox</span><span class="o">.</span><span class="n">iteritems</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">subject</span> <span class="o">=</span> <span class="n">msg</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Subject&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">sender</span> <span class="o">=</span> <span class="n">msg</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;From&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># decode_header handles Subject in UTF-8 / latin-1 / etc</span>
</span></span><span class="line"><span class="cl">    <span class="n">decoded</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">part</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="n">enc</span> <span class="ow">or</span> <span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">part</span><span class="p">,</span> <span class="nb">bytes</span><span class="p">)</span> <span class="k">else</span> <span class="n">part</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">part</span><span class="p">,</span> <span class="n">enc</span> <span class="ow">in</span> <span class="n">decode_header</span><span class="p">(</span><span class="n">subject</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">sender</span><span class="p">[:</span><span class="mi">40</span><span class="p">]</span><span class="si">:</span><span class="s2">&lt;40</span><span class="si">}</span><span class="s2">  </span><span class="si">{</span><span class="n">decoded</span><span class="p">[:</span><span class="mi">60</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Runs in milliseconds, consumes zero API quota, has no rate limit, never needs a credential refresh. It&rsquo;s just file reads. If you want to build something more serious (attachment parsing, LLM-based classification, automated rules), the same principle holds.</p>
<h2>Summary<span class="hx:absolute hx:-mt-20" id="summary"></span>
    <a href="#summary" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The combination that finally let me relax about this:</p>
<ul>
<li><strong>mbsync</strong> (isync) pulls from Gmail&rsquo;s IMAP into a local Maildir, automatically via a systemd timer.</li>
<li><strong>Maildir</strong> keeps the open format, one file per email, pull-only protects against accidental deletion.</li>
<li><strong>restic</strong> snapshots the Maildir to the NAS with ridiculous deduplication, and Synology&rsquo;s Glacier Backup carries that off-site as an encrypted copy.</li>
<li><strong>Free choice of mail client</strong> — mutt, neomutt, aerc, Thunderbird, Claws, anything reads it.</li>
<li><strong>Light automation</strong> — bot or script reads straight off disk, no OAuth, no API.</li>
</ul>
<p>Gmail becomes just a convenient web interface for reading and replying. The real ownership of the emails sits on my disk, replicated to the NAS, replicated to an encrypted off-site backup. If Google shuts my account down tomorrow (it&rsquo;s happened to people), I lose the interface but keep the entire history. For me, this is the definitive way to handle it. Recommended.</p>
]]></content:encoded><category>storage-and-backup</category><category>linux</category><category>automation</category></item><item><title>Installing Omarchy 2.0 from Scratch - Personal Notes</title><link>https://akitaonrails.github.io/en/2025/08/29/new-omarchy-2-0-install/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/08/29/new-omarchy-2-0-install/</guid><pubDate>Fri, 29 Aug 2025 16:00:00 -0300</pubDate><description>&lt;p&gt;I&amp;rsquo;ve been recommending DHH&amp;rsquo;s &lt;a href="https://omarchy.org/"target="_blank" rel="noopener"&gt;Omarchy 2.0&lt;/a&gt; to anyone who wants a lightweight, beautiful, and fully-equipped development environment. It&amp;rsquo;s a set of configs for Hyprland running on top of Arch Linux and it ships with smart defaults ready to use. Watch the &lt;a href="https://www.youtube.com/watch?v=TcHY0AEd2Uw"target="_blank" rel="noopener"&gt;video tour&lt;/a&gt; showing the main features.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250828_043020.jpg" alt="my omarchy" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;In my case, I spent 10 years using Manjaro GNOME as my daily driver. Manjaro is an Arch derivative. The reason was the easier install and the pre-made configs. But these days, with &lt;a href="TODO"&gt;archinstall&lt;/a&gt;, I don&amp;rsquo;t think it&amp;rsquo;s worth it anymore. I know there are other derivatives, but it&amp;rsquo;s better to stick with the original.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;ve been recommending DHH&rsquo;s <a href="https://omarchy.org/"target="_blank" rel="noopener">Omarchy 2.0</a> to anyone who wants a lightweight, beautiful, and fully-equipped development environment. It&rsquo;s a set of configs for Hyprland running on top of Arch Linux and it ships with smart defaults ready to use. Watch the <a href="https://www.youtube.com/watch?v=TcHY0AEd2Uw"target="_blank" rel="noopener">video tour</a> showing the main features.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250828_043020.jpg" alt="my omarchy"  loading="lazy" /></p>
<p>In my case, I spent 10 years using Manjaro GNOME as my daily driver. Manjaro is an Arch derivative. The reason was the easier install and the pre-made configs. But these days, with <a href="TODO">archinstall</a>, I don&rsquo;t think it&rsquo;s worth it anymore. I know there are other derivatives, but it&rsquo;s better to stick with the original.</p>
<p>For Ubuntu folks or those coming from Debian derivatives, Fedora, etc, there&rsquo;s a small learning curve on the package manager: Pacman and Yay instead of Apt or Dnf. But once you understand the AUR, you&rsquo;ll never want anything else. It has literally everything you need, without having to deal with third-party repositories.</p>
<p>I took a while to test Omarchy because I was already using Hyprland before Omarchy was created. DHH previously had Omakub, which was a set of scripts to customize Ubuntu into a good development environment. I liked it, but since I preferred Manjaro, I made a fork called Omakub-MJ. After that I decided to uninstall GNOME and GDM and install another set of scripts called <a href="https://www.ml4w.com/"target="_blank" rel="noopener">ML4W</a>, which makes Hyprland look great as well. So Omarchy wasn&rsquo;t that novel, for me.</p>
<p>The thing is, I&rsquo;d been using the same Manjaro for years. It had a ton of old configs and scripts all mixed up. My Manjaro GNOME no longer even had GNOME. My Hyprland was a Frankenstein. The only barrier was my <strong>QEMU/Libvirt</strong> setup to run Windows with <a href="https://akitaonrails.com/2023/02/01/akitando-137-games-em-maquina-virtual-com-gpu-passthrough-entendendo-qemu-kvm-libvirt/"target="_blank" rel="noopener">GPU passthrough</a>, which is what I used to edit videos back when I was still posting on my <a href="https://www.youtube.com/@Akitando"target="_blank" rel="noopener">YouTube channel</a>.</p>
<p>But since I stopped editing videos, that Windows isn&rsquo;t as important anymore. I had another Windows VM with GPU passthrough for games. But currently I have a separate mini-PC with Windows that I use just for games. I also had another VM with another Linux just for my AI experiments, but since then I migrated to running natively while isolating everything with Docker. In other words, my VMs are no longer important. Check my previous blog posts to learn about AI in Docker.</p>
<p>With the release of Omarchy 2.0, it looks like a good time to reinstall Arch from scratch with Omarchy on top and start over clean and beautiful.</p>
<h2>1 - My Setup<span class="hx:absolute hx:-mt-20" id="1---my-setup"></span>
    <a href="#1---my-setup" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250829_113734.jpg" alt="my PC"  loading="lazy" /></p>
<p>Every case will be different. This post&rsquo;s main purpose is to serve as my personal reference for the future, in case I need to reinstall everything again.</p>
<p>No, I&rsquo;m not going to use <strong>NixOS</strong>. I really don&rsquo;t think it&rsquo;s worth it. It only pays off if you&rsquo;re a computer lab admin or server infrastructure admin and your day-to-day involves rebuilding identical setups reliably. For a normal user who only reinstalls every 2 or 3 years, the learning curve and maintenance work aren&rsquo;t even remotely worth it. It&rsquo;s a waste of time. I&rsquo;d spend days learning now, finish the setup, go 2 years and I&rsquo;d already have forgotten everything. Arch Linux by itself is stable enough that I don&rsquo;t need this.</p>
<p>My setup is still the same from the days I was editing videos for the channel. It&rsquo;s a <strong>Ryzen 9 7850X3D</strong> with 16 cores, 32 threads, 5GHz peak. It&rsquo;s still an excellent CPU with power to spare for my use cases, which is why I don&rsquo;t see myself wanting to upgrade anytime soon. It&rsquo;s rare for me to saturate all those cores at those speeds at the same time. Better yet: it has an integrated AMD GPU which makes installing Linux a thousand times easier than if I had NVIDIA.</p>
<p>But I also have a separate discrete GPU, the <strong>NVIDIA RTX 4090</strong> (no, I don&rsquo;t need a 5090, it has the same amount of VRAM). It&rsquo;ll stay for secondary tasks and not for carrying my graphical environment. Things like Cycles rendering in Blender, or running LLMs with ollama and the like. Before I used GPU passthrough to a VM in QEMU/Libvirt to run Windows in parallel with Linux. Both to edit videos and to game.</p>
<p>Since I no longer edit videos, I don&rsquo;t need this complicated setup, so I won&rsquo;t bother installing Libvirt back. If I need Windows, I&rsquo;ll leave it in dual boot. If I need to edit video, these days we have DaVinci Resolve Studio which runs natively on Linux (a thousand times better than Adobe Premiere). For games I have a separate mini PC with an RTX 4060 hooked up to my TV. The best thing is to have a totally separate PC for games.</p>
<p>So this setup is meant to be pure Linux, with access to my <strong>Synology DS 1821+ NAS</strong> with 100TB. The only extra thing I&rsquo;ll need is to configure NFS mounts, as I&rsquo;ve already detailed in <a href="https://akitaonrails.com/en/2025/04/17/configuring-my-synology-nas-with-nfs-on-linux/"target="_blank" rel="noopener">this other post</a>.</p>
<p>One detail is that I have <strong>4 NVMEs</strong> in this PC. 2TB for my old Linux. 2 TB for dual boot/Windows VM. 2 TB just as a Steam drive and 1 TB for random stuff. Since I&rsquo;m wiping the Steam VM, I also no longer need to dedicate an entire NVME just for that. Instead, I&rsquo;ll make the new Linux&rsquo;s &ldquo;/home&rdquo; directory this entire 2 TB.</p>
<p>This will let me stop worrying about Docker filling my drive and freezing my entire system from lack of space. Last time I had &ldquo;solved&rdquo; this by making a <a href="https://akitaonrails.com/en/2025/04/17/configuring-my-synology-nas-with-nfs-on-linux/"target="_blank" rel="noopener">remote drive with iSCSI</a> on my NAS (which has infinite space). Since removing my Steam VM will give me 2TB back, I&rsquo;ll solve the problem locally and with fewer configurations.</p>
<p>RAM I have in spades, <strong>96GB of DDR5</strong>. With that, I also don&rsquo;t need to worry about a swap partition. Swap only matters if you have less than 32GB of RAM, if you&rsquo;re running lots of Docker and keep hitting the max RAM consumption very fast. Or if you&rsquo;re on a laptop and want the option of hibernation (being able to record RAM to disk, fully power off the machine, and come back later exactly where you left off — which is different from suspend, which still uses power).</p>
<p>If you want to hibernate, you need a bit more than the size of your RAM. So if you have 32GB, it&rsquo;s good to leave a margin, around 34GB or more of swap. Since my PC is a desktop at home, I don&rsquo;t need hibernation, just enable <strong>ZRAM</strong> for the rare cases where there&rsquo;s a spike over 96GB (which is super rare). Plus, it&rsquo;s easier for me to just bump up to 128GB of RAM if I really need it. Swap to compensate for RAM only pays off on laptops that don&rsquo;t have room for more real RAM.</p>
<h2>2 - Why Omarchy?<span class="hx:absolute hx:-mt-20" id="2---why-omarchy"></span>
    <a href="#2---why-omarchy" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>As I said, I&rsquo;ve been an Arch user for a decade and I don&rsquo;t see any package repository bigger and more complete than the <strong>AUR</strong>. The only other repository that actually competes is NixOS&rsquo;s. But since Nix is ruled out for my use case, the AUR remains unbeatable.</p>
<p>Arch has Pacman as its package manager. To access the AUR you need a second manager. There are several like the old Pacaur, Paru, and many others. But the best, in my opinion, is Yay. It has exactly the same syntax as Pacman and replaces Pacman. In practice, we just use Yay for everything.</p>
<p>To search packages by name just do <code>yay -Ss spotify</code>.</p>
<p>To remove packages just do <code>yay -Rns spotify</code>.</p>
<p>To update all system packages just do <code>yay -Syu</code></p>
<p>To remove orphan packages that pile up over time just do <code>sudo pacman -Rns $(pacman -Qdtq)</code></p>
<p>And those are the commands I use 99% of the time.</p>
<p>I&rsquo;ve already used every type of desktop manager out there: GNOME (which behaves more like a Mac), KDE (which behaves more like Windows for power users), XFCE (which is GTK-based and lighter), Cinnamon (also GTK-based), i3 (which is a tiling window system), Sway (like i3, also tiling but if I&rsquo;m not mistaken was designed with Wayland in mind).</p>
<p>In practice, everyone uses apps in two ways: a full-screen window or 2 vertical windows splitting the monitor in the middle (editor on one side, browser on the other), or 3 windows (a vertical editor taking half the monitor, a browser taking 1/4, and a terminal taking another 1/4). Then we organize more open apps into virtual desktops/workspaces. That&rsquo;s the easiest way to work.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_12-12-58.png" alt="ideal layout"  loading="lazy" /></p>
<p>This means being able to drag floating windows is a waste of time. I prefer that windows already open sized into one of these layouts I mentioned above. You can configure GNOME or KDE to do that. But it smells like a kludge. Instead it really is better to use i3 or Sway.</p>
<p>Setting <a href="https://x.com/LundukeJournal/status/1942389827518808384"target="_blank" rel="noopener">controversies</a> aside (yes, I&rsquo;m a bit wary of Wayland and I like that XLibre exists). But it&rsquo;s a fact that <strong>X11</strong> is behind the times and is a very old and complicated architecture. Wayland really does have a better &ldquo;appeal,&rdquo; despite having taken a while to round off basic things like screen sharing (OBS, Zoom). But I&rsquo;ve been using Wayland for some years now without headaches. These days I&rsquo;d say it&rsquo;s pretty stable. I&rsquo;ve been using GNOME on Wayland for a long time. I don&rsquo;t stop using things because I&rsquo;m wary of them: with technology I&rsquo;ll always be absolutely promiscuous and use everything.</p>
<p>Knowing I&rsquo;ll use <strong>Wayland</strong> and knowing I want automatic window layouts, and don&rsquo;t need floating windows, the best is a desktop manager with a <strong>tiling windows</strong> system (tiles are &ldquo;azulejos&rdquo; and we&rsquo;re talking about tiles being side by side and never one on top of another), which organizes windows side by side in a smart and automatic way.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_12-19-25.png" alt="windows side by side"  loading="lazy" /></p>
<p>In this case we only have 2 options: <strong>Sway</strong> or <strong>Hyprland</strong> (either one is good). Hyprland, installed from scratch, is completely bare — it has absolutely nothing. We need to configure everything through files in <code>.config/hypr</code>. It&rsquo;s simple enough, but it&rsquo;s work.</p>
<p>The only good option for having a pre-configured and well-done Hyprland was <strong>ML4W</strong>, which I was already using. But now we have Omarchy, which installs and pre-configures everything to work smoothly on Hyprland. This is the best option we have today.</p>
<p>Besides configuring Hyprland to work with <strong>Waybar</strong>, Rofi, and other components, it has its own system for switching themes and styles to leave the system with the <em>&ldquo;look and feel&rdquo;</em> you want (and all the ones that come with it are super pretty), but it also installs things I&rsquo;ve been using for a long time like Neovim with the <strong>LazyVim</strong> package (in my opinion, the easiest way to make Neovim not fall short compared to VS Code), <strong>Mise</strong> (the best way to organize development environments to have multiple versions of your favorite programming language).</p>
<p>If you&rsquo;re installing a distro from scratch, the only two things you need to have are Mise and LazyVim. Plus Docker and LazyDocker. That&rsquo;s enough to start a productive development environment. And Omarchy already brings all of that pre-configured. No one &ldquo;needs&rdquo; Omarchy, but it already brings everything integrated and beautiful. So why bother doing everything from scratch to end up almost the same?</p>
<p>Also, this is Linux. Don&rsquo;t like one of the components? Just swap it out.</p>
<h2>3 - Back Up<span class="hx:absolute hx:-mt-20" id="3---back-up"></span>
    <a href="#3---back-up" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The vast majority of everything I have already lives on the remote server, my 100TB Synology NAS. That already helps a lot. Even so, there&rsquo;s still a good amount of stuff locally on my PC, so the first thing to do is copy everything to the NAS. If you don&rsquo;t have a NAS, then an external HD. Any server will do; the important thing is to guarantee you won&rsquo;t lose anything before installing a new OS from scratch.</p>
<p>Don&rsquo;t forget directories like <code>.config</code> or <code>.local</code>. It&rsquo;s better to do an <code>rsync -avz --progress .config /mnt/nas/backup/</code>, for example, and copy everything. Even what you think you won&rsquo;t need. Better to copy more than to be missing stuff. As a rule, the ideal is to copy everything in your <code>$HOME</code> to an external drive or NAS.</p>
<p>Just to make sure I won&rsquo;t forget anything important, I&rsquo;m also going to record a list of all the packages I have installed on my old system:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -Q &gt; pacotes.txt
flatpak list &gt; flatpak.txt</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Also making sure I won&rsquo;t lose old configs of services I made in systemd, so also copy everything from <code>/etc/systemd/system</code>. It&rsquo;s also worth recording things like <code>/etc/fstab</code>. Also don&rsquo;t forget to back up your SSH keys with <code>tar cvfz ssh.tgz ~/.ssh</code>!!</p>
<p>If, like me, you were using QEMU/Libvirt for VMs, it&rsquo;s good to save the configs of the virtual machines you used. Usually the tool to manage VMs is virsh, so I just did:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo virsh list --all
sudo virsh dumpxml &lt;vmname&gt; &gt; &lt;vmname&gt;.xml</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>If I want to recreate the machines in the future, it will all be in the backup. In my case I keep all virtual drives on my NAS, but if yours are local, you also need to save those files that live in <code>/var/lib/libvirt/images/</code></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>rsync -avh --progress /var/lib/libvirt/images/ /backup/libvirt-images/</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h2>4 - Is Installing Arch Hard?<span class="hx:absolute hx:-mt-20" id="4---is-installing-arch-hard"></span>
    <a href="#4---is-installing-arch-hard" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Arch, less than Gentoo, has always had a reputation for being the most brutal Linux distro for a beginner to install. That&rsquo;s not a lie, but the truth is Arch is simultaneously the hardest distro and also the easiest.</p>
<p>Download <a href="https://archlinux.org/download/"target="_blank" rel="noopener">the official ISO</a> and burn it to a USB drive (on Windows, using <strong>Rufus</strong> or Balena Etcher). Now press F12 (depends on your BIOS) or configure the BIOS directly to boot from the USB drive. Doing that loads the Arch live environment, which is literally dropping into a command line. With no further help.</p>
<p>From this point you have to follow the <a href="https://wiki.archlinux.org/title/Installation_guide"target="_blank" rel="noopener">Installation Wiki</a> from the excellent ArchWiki and learn how to install a Linux from absolute scratch using only the command line, step by step, through trial and error (more erring than hitting the mark), and in the end you&rsquo;ll have a much better feel for how a Linux distro actually works.</p>
<p>I didn&rsquo;t make an Arch video but I did make videos of <a href="https://www.youtube.com/watch?v=iQkBbRPkASo"target="_blank" rel="noopener">Slackware</a> to explain how a package manager works. I also made a <a href="https://www.youtube.com/watch?v=cSyTjCUFx2A"target="_blank" rel="noopener">Gentoo</a> video to show how a distro that compiles everything from scratch works. And I made a video about how the <a href="https://www.youtube.com/watch?v=5F6BbhgvFOE"target="_blank" rel="noopener">Linux Boot Works</a> to understand where the kernel lives and how it&rsquo;s structured. But I didn&rsquo;t make an Arch video because I always left it as an exercise for beginners.</p>
<p>The manual way is viable, but if you&rsquo;re used to a graphical installer, like <strong>Calamaris</strong>, which most more popular distros like Ubuntu or Fedora have, you&rsquo;ll be totally lost. But here comes the bonus: you don&rsquo;t need to go through all of that to have Arch Linux on your machine.</p>
<p>For that there&rsquo;s <a href="https://wiki.archlinux.org/title/Archinstall"target="_blank" rel="noopener"><strong>archinstall</strong></a>.</p>
<p>When you boot the Arch USB and drop into the command line, just type archinstall and you&rsquo;re done — just follow the step by step it provides (which I consider even simpler than Calamaris) and it will install and configure everything automatically for you, the same way installing Fedora or any other distro is. Seriously, it shouldn&rsquo;t take more than 15 minutes.</p>
<p>archinstall looks like this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250828_045255.jpg" alt="archinstall"  loading="lazy" /></p>
<p>Just go into each of the items on the left and fill in your choices. Configure things like keyboard layout (I normally choose &lsquo;us-acentos&rsquo;). Edit the hostname of your machine. Choose &ldquo;NetworkManager&rdquo; in network configuration (it will configure both ethernet and wifi for you), add Bluetooth and Audio support (choose &ldquo;Pipewire&rdquo;). Choose the &ldquo;desktop&rdquo; type instead of &ldquo;minimal&rdquo; to already get the main packages needed for a desktop. Choose the timezone (&ldquo;America/Sao_Paulo&rdquo; in my case).</p>
<p>I also recommend choosing the Hyprland option, I think under Applications or Additional Packages. It will install SDDM to give you a graphical login.</p>
<p>Only two options need more attention: for bootloader, many will be tempted to use <strong>Limine</strong>, which is more modern and lightweight. But I still recommend using <strong>GRUB</strong>, I&rsquo;ll explain why in a moment.</p>
<p>The second important option is &ldquo;Disk Configuration&rdquo;. Don&rsquo;t worry, unlike fdisk, it&rsquo;s super intuitive. Just choose the correct drive where you want to install (if you have dual boot, careful not to wipe your Windows). Then let it do the automatic partition layout (only edit manually if you have some very special need). Most importantly, choose <a href="https://wiki.archlinux.org/title/Btrfs"target="_blank" rel="noopener"><strong>BTRFS</strong></a> instead of the traditional ext4.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250828_045312.jpg" alt="btrfs"  loading="lazy" /></p>
<p>Every Linux always formats its Linux partitions with ext4, which is the oldest, most stable, and best-documented file system. These days I recommend BTRFS; it&rsquo;s much more modern, safer (has better corruption protection systems), uses less space (learn about CoW - <strong>Copy on Write</strong>), and most importantly: it has cheap snapshot support (think of Apple Time Machine, only better). It has most of the features of a file system dedicated to a file server but is much simpler and more intuitive to use than the monster that is ZFS (I don&rsquo;t recommend it for normal users).</p>
<p>It has some maintenance needs that are worth reading about in the Wiki to learn. But overall it&rsquo;s plug and play, install and forget. In the next sections I&rsquo;ll show some things you need to know, but trust me: choose BTRFS.</p>
<p>I know Omarchy 2.0 has an option for a <a href="https://iso.omarchy.org/omarchy-online.iso"target="_blank" rel="noopener">pre-configured ISO</a> which I imagine is normal Arch with archinstall and Omarchy on top all at once. But I don&rsquo;t know if they allow this choice, which is why I preferred going straight to pure Arch first. In the end the effect is the same. Comment below later if the Omarchy ISO also has the same options. Just make sure in the end you have BTRFS. Future you will thank you.</p>
<h2>5 - Separate HOME<span class="hx:absolute hx:-mt-20" id="5---separate-home"></span>
    <a href="#5---separate-home" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>As I explained before, I used one of my 4 NVMEs as the Linux drive. But now I&rsquo;ve freed up another NVME and want to move my $HOME there and have 2 TB dedicated just to user space.</p>
<p>I won&rsquo;t explain how to partition and format that other drive. It&rsquo;s simple: just use <a href="https://wiki.archlinux.org/title/Fdisk"target="_blank" rel="noopener">fdisk</a> or later install <strong>Gparted</strong> which is the graphical version of the <a href="https://wiki.archlinux.org/title/Parted"target="_blank" rel="noopener">parted</a> tool that you can use from the command line. There are several options. The important thing is just to wipe my old Windows partition and also format it with BTRFS.</p>
<p>Each person will have different device and partition names than mine. To find out which are yours, use the <code>lsblk</code> command. See my example:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_12-54-42.png" alt="lsblk"  loading="lazy" /></p>
<p>In my example <code>nvme1n1</code> is the 1TB drive for random stuff. <code>nvme2n1</code> is my new Arch Linux. <code>nvme0n1</code> is the old Windows dual boot drive. <code>nvme3n1</code> is the Steam drive I formatted with BTRFS to become my new HOME drive.</p>
<p>If you didn&rsquo;t get it, HOME is the <code>/home/akitaonrails</code> directory. It will live on a separate drive. After archinstall finishes and I boot into the new Linux, I just do the following:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo mount -t btrfs /dev/nvme3n1p1 /mnt
sudo btrfs subvolume create /mnt/@home
rsync -aAXHv /home/ /mnt/@home/
sudo umount /mnt</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This mounts the new partition, then we use the &ldquo;btrfs&rdquo; command to create a new sub-volume called &ldquo;@home&rdquo; (again, read the BTRFS Wiki to understand concepts like sub-volumes).</p>
<p>The commands above create the sub-volume and copy everything from the old HOME to the new. Now we need <code>/home</code> to be mounted on this new partition at boot. For this we need to know the UUID of that partition:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>~ ❯ sudo blkid /dev/nvme3n1p1
/dev/nvme3n1p1: LABEL=&#34;HOME&#34; UUID=&#34;84e9a1b7-a30e-...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The <code>blkid</code> command returns the unique UUID of each partition and, with that, we can edit the <code>/etc/fstab</code> file using <code>sudo nvim</code> or <code>sudo nano</code> or the editor of your choice:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># /etc/fstab
...
# /dev/nvme2n1p2
# UUID=da3aea43-...-aeaf8c7224d7 /home      btrfs      rw,relatime,ssd,discard=async,space_cache=v2,subvol=/@home 0 0

# /dev/nvme3n1p1
UUID=84e9a1b7-...-7a6a17fd0395 /home      btrfs      rw,relatime,ssd,discard=async,space_cache=v2,subvol=/@home 0 0
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>See how I commented out the old line that mounted <code>/home</code> and added a new line with the new UUID. The rest is all the same. And that&rsquo;s it, just boot and from now on my <code>/home</code> will mount on the new drive. Now I have 2 TB dedicated just to my junk downloads lol 🤣</p>
<p>But not only that, I also have space for big things like Docker images or LLM models, without risking filling the operating system drive and running the risk of it no longer booting due to lack of space — as has happened to me several times in the past. Always remember to do maintenance on Docker and LLMs and delete old things.</p>
<p>Speaking of which, BTRFS supports snapshots. And one of the basic mistakes is forgetting that Docker or LLM directories can end up in the snapshots and occupy space without you knowing. To avoid this it&rsquo;s better to mark those directories to not enter the snapshots. For that we have to tell BTRFS to ignore those directories.</p>
<p>The correct way is to create separate subvolumes for them. For this I&rsquo;ll mount that same partition on the separate drive where I mounted my $HOME:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>~ ❯ sudo mkdir /mnt/temp
~ ❯ sudo mount -o subvolid=5 -t btrfs /dev/nvme3n1p1 /mnt/temp

~ ❯ ls /mnt/temp
 @home   timeshift-btrfs

~ ❯ sudo btrfs subvolume create /mnt/temp/@docker
Create subvolume &#39;/mnt/temp/@docker&#39;

~ ❯ sudo btrfs subvolume create /mnt/temp/@ollama
Create subvolume &#39;/mnt/temp/@ollama&#39;

~ ❯ sudo umount /mnt/temp</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now just edit <code>/etc/fstab</code> to mount correctly just like we did with HOME:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># /etc/fstab
...
# /dev/nvme3n1p1
UUID=84e9a1b7-...-7a6a17fd0395 /home      btrfs      rw,relatime,ssd,discard=async,space_cache=v2,subvol=/@home 0 0
UUID=84e9a1b7-...-7a6a17fd0395 /var/lib/docker      btrfs      rw,relatime,ssd,discard=async,space_cache=v2,subvol=/@docker 0 0
UUID=84e9a1b7-...-7a6a17fd0395 /var/lib/ollama      btrfs      rw,relatime,ssd,discard=async,space_cache=v2,subvol=/@ollama 0 0
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Done, clean subvolumes. More important in my case: I moved directories that have a tendency to grow without control out of the operating system partition, making it harder to end up with an inaccessible system due to lack of space. Do the same with other similar directories, with things like Kubernetes, for example. Anything that keeps downloading large files, like system images.</p>
<p>Now just mount and bring things back:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># every time you edit fstab, run this:
sudo systemctl daemon-reload

# create the mount points
sudo mkdir /var/lib/docker
sudo mkdir /var/lib/ollama

# mount
sudo mount /var/lib/docker
sudo mount /var/lib/ollama

# copy the contents of the old directory back
sudo rsync -aHAX --inplace /var/lib/docker.old/ /var/lib/docker/
sudo systemctl start docker</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Besides space management, what&rsquo;s the other advantage of making separate subvolumes? Because the next section will be about making the system even more robust by adding snapshot and rollback capability to past snapshots. And the ideal is to keep huge files out of snapshots that will sit in the past. Docker images are easy to recover: just <code>docker pull</code> again, no need for backup.</p>
<h2>6 - Installing Omarchy<span class="hx:absolute hx:-mt-20" id="6---installing-omarchy"></span>
    <a href="#6---installing-omarchy" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>This is the easiest part. When you&rsquo;re done installing with archinstall, if you chose the Hyprland option, SDDM will appear for you to log in. And when you enter you&rsquo;ll be shocked at how <strong>raw</strong> Hyprland is. You basically can&rsquo;t do anything.</p>
<p>So this doesn&rsquo;t become an entire day of manual configuration, we&rsquo;ll use Omarchy. Hit &ldquo;Super Enter&rdquo; to open the terminal and don&rsquo;t mind how ugly things look right now. It&rsquo;ll get better.</p>
<p>In the terminal just do this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>curl -fsSL https://omarchy.org/install | bash</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Just follow the on-screen instructions and let it do the rest. And that&rsquo;s it, done, there&rsquo;s no next step.</p>
<p>I HIGHLY recommend you read the <a href="https://learn.omacom.io/2/the-omarchy-manual"target="_blank" rel="noopener">OMARCHY ONLINE MANUAL</a> that DHH made. It&rsquo;s very clean, simple, and direct. All the keyboard shortcuts, for example, are in the <a href="https://learn.omacom.io/2/the-omarchy-manual/53/hotkeys"target="_blank" rel="noopener">HOTKEYS section</a>. Spend an hour reading the basics to be able to operate it. But by now you already have practically everything you need: Alacritty as terminal, Chromium as browser (you can install Brave if you prefer, as I do).</p>
<p>&ldquo;Super Alt Space&rdquo; opens the Omarchy main menu. &ldquo;Super Space&rdquo; opens Rofi to type the name of the app you want to open. &ldquo;Super Enter&rdquo; opens the terminal. &ldquo;Super B&rdquo; opens the browser and so on.</p>
<h2>7 - Customizing Omarchy<span class="hx:absolute hx:-mt-20" id="7---customizing-omarchy"></span>
    <a href="#7---customizing-omarchy" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Omarchy is a customized Hyprland. We can edit whatever we want directly in <code>~/.config/hypr</code>. And since I was used to ML4W, I decided to tweak a bit to make it good for my setup. Your setup will vary depending on your needs.</p>
<p>Right after I finished installing Omarchy and tried to use Gparted to partition that second NVME of mine for HOME, it had an authentication problem. I don&rsquo;t remember if this was the solution but it&rsquo;s good to make sure you have Polkit installed:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># make sure polkit &#43; agent exist
sudo pacman -S --needed polkit polkit-gnome xorg-xhost

# Hyprland: export full env to systemd user bus once
dbus-update-activation-environment --systemd --all</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Back to it, the first thing I need to solve is that I have 2 monitors. An Asus 32&quot; 1440p 144fps as the main and another Samsung 24&quot; 4K 60fps vertical on the left side. The way to configure this in Hyprland is by editing a file like <code>~/.config/hypr/monitors.conf</code> which is normally loaded by <code>~/.config/hypr/hyprland.conf</code>.</p>
<p>Instead of doing this manually, I prefer to install:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -S nwg-displays</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_13-46-04.png" alt="nwg-displays"  loading="lazy" /></p>
<p>Here I configure things like transform 90 degrees to rotate the vertical monitor, resolution scaling, 10-bit color support, etc.</p>
<p>It will save two files: <code>~/.config/monitors.conf</code> and <code>~/.config/workspaces.conf</code>. Omarchy doesn&rsquo;t load this second file, so we have to edit <code>~/.config/hypr/hyprland.conf</code> to look like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
# Change your own setup in these files (and overwrite any settings from defaults!)
source = ~/.config/hypr/monitors.conf
source = ~/.config/hypr/workspaces.conf`
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>I also took the opportunity to add some extra configurations in this file:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
# animation
animations {
  enabled = yes
  bezier = easeOutQuint, 0.23, 1, 0.32, 1
  animation = workspaces, 1, 4, easeOutQuint, slide
  # optional extras:
  animation = specialWorkspace, 1, 4, easeOutQuint, slide
  animation = windows, 1, 4, easeOutQuint, popin 80%
  animation = fade, 1, 4, easeOutQuint
}

xwayland {
    force_zero_scaling = false # careful with mixing scaling wayland and x11
}

input {
  numlock_by_default = true
}

# dialogs/splash/utility ONLY
windowrulev2 = float,  xwayland:1, windowtype:^(dialog|splash|utility)$
windowrulev2 = center, xwayland:1, windowtype:^(dialog|splash|utility)$

# never center menus/tooltips
windowrulev2 = tile,   xwayland:1, windowtype:^(popup_menu|dropdown_menu|tooltip)$</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Some people don&rsquo;t like it but I like having a quick animation when I switch from one workspace to another, which is why I added the <code>animations</code> block.</p>
<p>When you use apps made for X11, they&rsquo;ll run on top of XWayland. One annoying thing is that some of them open &ldquo;dialog boxes,&rdquo; which are floating windows. And they can end up in places hard to click, especially on a multi-monitor setup. That&rsquo;s why I added blocks like <code>xwayland</code> and <code>windowrulev2</code> rules to force those dialog boxes to always appear centered on screen.</p>
<p>Finally, I have an 8bitdo numpad keyboard connected via Bluetooth, but it doesn&rsquo;t start up with numlock on. To fix this there&rsquo;s that <code>input</code> block.</p>
<p>Back to the monitor layout. After nwg-displays generated the files, I still had to modify a bit for my case, like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># ~/.config/monitors.conf
# Generated by nwg-displays on 2025-08-28 at 14:30:17. Do not edit manually.

#monitor=HDMI-A-3,2560x1440@143.97,1080x407,1.25,bitdepth,10
#monitor=DP-5,3840x2160@60.0,0x0,1.875,bitdepth,10
#monitor=DP-5,transform,1
#monitor=DP-1,0x0@60.0,-1x-1,1.0
#monitor=DP-1,disable

monitor=HDMI-A-3,2560x1440@143.97,2813x948,1.0,bitdepth,10
monitor=DP-5,3840x2160@60.0,1373x350,1.5,bitdepth,10
monitor=DP-5,transform,1
monitor = DP-1,disable</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The monitor file I didn&rsquo;t have to change anything, really, just remember to disable that DP-1 that appears on my main monitor (I think it&rsquo;s its DisplayPort, but I use HDMI, so I have to disable DP-1).</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># ~/.config/workspaces.conf
# Generated by nwg-displays on 2025-08-28 at 13:07:02. Do not edit manually.

workspace=5,monitor:DP-5,persistent:true
workspace=1,monitor:HDMI-A-3,persistent:true
workspace=2,monitor:HDMI-A-3,persistent:true
workspace=3,monitor:HDMI-A-3,persistent:true
workspace=4,monitor:HDMI-A-3,persistent:true</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This one I also didn&rsquo;t have to change much, in nwg-displays itself I said which workspaces I want on each monitor. In my personal preference I prefer to leave the vertical monitor only with workspace 5 and the others on the main monitor. The difference here was that I added those <code>persistent:true</code> options on all of them, to guarantee that everything will work correctly with the workspaces &ldquo;pinned&rdquo; in the right places. Workspaces in Hyprland are quite dynamic; it&rsquo;s worth reading the documentation later if you want more complicated cases.</p>
<p>This solves my two-monitor setup the way I prefer. Experiment with things like scaling until everything is okay for your setup.</p>
<p>Next I needed to solve the hotkeys (key bindings). I&rsquo;m used to some combinations that came from ML4W so I decided to copy them directly into <code>~/.config/hypr/bindings.conf</code>. First, switch chromium for Brave:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Application bindings
$terminal = uwsm app -- alacritty
$browser = brave # omarchy-launch-browser
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Then I added a bunch of binds at the end of the same file:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>unbind = SUPER, V
bind = SUPER, V, swapsplit
bind = SUPER, M, fullscreen, 1                                                           # Maximize Window
bind = SUPER, T, togglefloating                                                          # Toggle active windows into floating mode
bind = SUPER SHIFT, right, resizeactive, 100 0                                           # Increase window width with keyboard
bind = SUPER SHIFT, left, resizeactive, -100 0                                           # Reduce window width with keyboard
bind = SUPER SHIFT, down, resizeactive, 0 100                                            # Increase window height with keyboard
bind = SUPER SHIFT, up, resizeactive, 0 -100                                             # Reduce window height with keyboard

# Display
bind = SUPER SHIFT, mouse_down, exec, hyprctl keyword cursor:zoom_factor $(awk &#34;BEGIN {print $(hyprctl getoption cursor:zoom_factor | grep &#39;float:&#39; | awk &#39;{print $2}&#39;) &#43; 0.5}&#34;) # Increase display zoom
bind = SUPER SHIFT, mouse_up, exec, hyprctl keyword cursor:zoom_factor $(awk &#34;BEGIN {print $(hyprctl getoption cursor:zoom_factor | grep &#39;float:&#39; | awk &#39;{print $2}&#39;) - 0.5}&#34;) # Decrease display zoom
bind = SUPER SHIFT, Z, exec, hyprctl keyword cursor:zoom_factor 1 # Reset display zoom

# Workspaces
bind = SUPER, 1, workspace, 1 on HDMI-A-3 # Open workspace 1
bind = SUPER, 2, workspace, 2 on HDMI-A-3  # Open workspace 2
bind = SUPER, 3, workspace, 3 on HDMI-A-3 # Open workspace 3
bind = SUPER, 4, workspace, 4 on HDMI-A-3 # Open workspace 4
bind = SUPER, 5, workspace, 5 on DP-5 # Open workspace 5

bind = SUPER SHIFT, 1, movetoworkspace, 1  # Move active window to workspace 1
bind = SUPER SHIFT, 2, movetoworkspace, 2  # Move active window to workspace 2
bind = SUPER SHIFT, 3, movetoworkspace, 3  # Move active window to workspace 3
bind = SUPER SHIFT, 4, movetoworkspace, 4  # Move active window to workspace 4
bind = SUPER SHIFT, 5, movetoworkspace, 5  # Move active window to workspace 5

bind = SUPER, Tab, workspace, m&#43;1       # Open next workspace
bind = SUPER SHIFT, Tab, workspace, m-1 # Open previous workspace

bind = SUPER CTRL, 1, exec, $HOME/.config/hypr/moveTo.sh 1  # Move all windows to workspace 1
bind = SUPER CTRL, 2, exec, $HOME/.config/hypr/moveTo.sh 2  # Move all windows to workspace 2
bind = SUPER CTRL, 3, exec, $HOME/.config/hypr/moveTo.sh 3  # Move all windows to workspace 3
bind = SUPER CTRL, 4, exec, $HOME/.config/hypr/moveTo.sh 4  # Move all windows to workspace 4
bind = SUPER CTRL, 5, exec, $HOME/.config/hypr/moveTo.sh 5  # Move all windows to workspace 5</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Note that the last block of scripts needs a script to move all windows at once to another workspace. I copied this <a href="https://github.com/mylinuxforwork/dotfiles/blob/main/dotfiles/.config/hypr/scripts/moveTo.sh"target="_blank" rel="noopener">moveTo.sh</a> from ML4W&rsquo;s GitHub and placed it directly in <code>~/.config/hypr</code>. Don&rsquo;t forget to give execute permission with <code>chmod +x moveTo.sh</code> afterwards.
The various workspace ones below are how I prefer to move workspaces and move apps to different workspaces.</p>
<p>Omarchy already comes with &ldquo;SUPER J&rdquo; to change the orientation of windows, for example, if there are two horizontal windows one on top of the other, with SUPER J they become vertical, but &ldquo;SUPER V&rdquo; was missing which I added for &ldquo;swapsplit&rdquo; which is swapping windows&rsquo; places.</p>
<p>I also like &ldquo;SUPER M&rdquo; to temporarily make one of the windows full screen, and &ldquo;SUPER T&rdquo; to temporarily make a window floating (like a calculator, for example). I had to unbind the previous SUPER M that opened Spotify, but I barely use Spotify on desktop so I don&rsquo;t need it. When I want to open that app, I go to SUPER SPACE to open Rofi and type it there.</p>
<p>If you have questions about the default combinations, Omarchy has &ldquo;SUPER K&rdquo; to open a window with help for all of them.</p>
<p>I still haven&rsquo;t modified combinations for specific applications. The ones that come with Omarchy are already very useful, like SUPER A to open ChatGPT as if it were a separate app, or SUPER CTRL A to open Grok in a separate window. SUPER O to open Obsidian is also very useful for me. I recommend playing with these binds to make specific bindings and, again, reading the manual to learn everything else it already brings. But up to here it&rsquo;s enough to leave Hyprland the way I like to use it.</p>
<h2>8 - Timeshift, Snapshots and GRUB<span class="hx:absolute hx:-mt-20" id="8---timeshift-snapshots-and-grub"></span>
    <a href="#8---timeshift-snapshots-and-grub" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Remember I said to choose GRUB in archinstall? It&rsquo;s time now. The goal: let the system make periodic snapshots of itself, and in case you install some corrupted package or do things on your system that prevent it from booting, instead of having to resort to a USB drive to try to boot and diagnose the error, it&rsquo;s easier to just boot, choose a snapshot right before the destructive operation, and come back intact.</p>
<p>Before proceeding, an adjustment I think is important: my 4K monitor makes GRUB letters too small for me to read. I prefer bigger letters. Fortunately GRUB uses bitmap fonts so I can use any. I did the following:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo grub-mkfont -s 48 -o /boot/grub/fonts/caskaydia48.pf2 /usr/share/fonts/TTF/CaskaydiaMonoNerdFont-Regular.ttf</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This will generate a font in the format that GRUB can load. Choose any one you like; Omarchy already pre-installs several very good ones like JetBrains&rsquo;s. If size 48 is too big, lower it to 32, experiment until it&rsquo;s the way you like.</p>
<p>Then just edit <code>/etc/default/grub</code> to, at the end, have this line:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>GRUB_FONT=/boot/grub/fonts/caskaydia48.pf2</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And now just regenerate the bootloader:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo grub-mkconfig -o /boot/grub/grub.cfg</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Every time <code>yay -Syu</code> brings a kernel or driver update, GRUB will be regenerated with the same font. Look at how it turned out for me, I think it looks much better (this is a 4K monitor):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250828_124009.jpg" alt="Grub Font"  loading="lazy" /></p>
<p>Now the entire reason for having chosen GRUB: allowing you to choose BTRFS snapshots to boot into the past, before some destructive operation, as shown in the photo above. For this we need the following:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>pacman -S timeshift grub-btrfs
yay -S timeshift-autosnap
systemctl enable --now grub-btrfs.service</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Done. From now on, every time GRUB is rebuilt, it&rsquo;ll have a new option to choose snapshots. We just need to create those snapshots automatically, and for that we have the Timeshift app. Open it and follow its step by step. The important thing is to choose the BTRFS option (yes, you can use RSYNC if you&rsquo;re on ext4, but that occupies a lot of space, because it will literally keep making copies of your entire system&rsquo;s files. The beauty of BTRFS is that its snapshots don&rsquo;t duplicate files).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_15-37-52.png" alt="Timeshift Schedule"  loading="lazy" /></p>
<p>I leave it making snapshots and accumulating versions of the last hours, last days, last weeks, and months. If later on I remember <em>&ldquo;damn, there was that file I deleted last month but now I need it&rdquo;</em> just open Timeshift and browse a snapshot from last month. Like Mac folks do with Time Machine, but occupying almost no extra space.</p>
<p>Besides that, every package upgrade will generate a snapshot beforehand. That way, if by chance a corrupted package comes in or breaks some important config of yours, you can go back exactly to how it was before the update. And if it breaks catastrophically, where your Linux no longer boots, just go to the GRUB extra menu and boot from the previous snapshot, which worked. And everything comes back just like it was before.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_15-38-24.png" alt="Timeshift Snapshots"  loading="lazy" /></p>
<p>Nothing is more important for your system than having automatic snapshots that occupy almost no extra space. This alone makes BTRFS superior to any alternative.</p>
<p><strong>This does not replace external Backup!!</strong></p>
<p>This rollback and the ability to recover things easily is good for day-to-day accidents. But let&rsquo;s say your NVME breaks somehow, or your PC breaks somehow. That&rsquo;s what external backups are for (be it external HD, NAS, or Cloud). You want to be sure that, in a catastrophic situation, it&rsquo;s still possible to recover your files.</p>
<p>I have all the protection layers: local snapshots via BTRFS and Timeshift, backups done remotely to my NAS, and a backup of the most important parts of my NAS on <a href="https://aws.amazon.com/pt/s3/storage-classes/glacier/"target="_blank" rel="noopener">Amazon Glacier</a> which, unlike Amazon S3, is for keeping backups, where storage cost is ultra low but transfer cost is higher (S3 storage is more expensive but transfer is cheap, which is why it serves to serve website assets).</p>
<p>I like being prepared so that, in case my house catches fire, I don&rsquo;t lose anything important.</p>
<h2>9 - NFS on the NAS<span class="hx:absolute hx:-mt-20" id="9---nfs-on-the-nas"></span>
    <a href="#9---nfs-on-the-nas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Speaking of NAS, one thing I configured differently — since I primarily use Linux instead of Windows — was enabling the NFS server.</p>
<p>But beware that some NAS have a characteristic that can be annoying: your user on the NAS will have a UID and GID different from 1000!</p>
<p>When you create a user on any Linux, the default is to assign UID 1000 to the first user, 1001 to the second, and so on. But since on the Synology NAS the first user receives UID 1026, if I mount with NFS (which passes through UNIX permissions), I&rsquo;ll have authorization problems.</p>
<p>There are two solutions: try to change the UID on the NAS from 1026 to 1000. <a href="https://akitaonrails.com/en/2025/04/17/configuring-my-synology-nas-with-nfs-on-linux/"target="_blank" rel="noopener">I tried that</a> and I&rsquo;ll tell you up front it&rsquo;s a waste of time, you can end up breaking your NAS&rsquo;s configurations catastrophically.</p>
<p>It&rsquo;s much easier to change the user of your new local Linux to be 1026 and that&rsquo;s it. It&rsquo;s pretty easy.</p>
<p>First, we need to log out and leave Hyprland and go back to the SDDM login screen. Then we need to switch to another TTY with <strong>CTRL + ALT + F3</strong> (to go to tty3, for example). From there we have to log in as <strong>root</strong>.</p>
<p>Now we run: <code>systemctl isolate multi-user.target</code> to run in isolated recovery mode, where no other user has processes running. Since we&rsquo;re going to mess with the user, they can&rsquo;t be active.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>usermod -u 1026 akitaonrails
groupmod -g 1026 akitaonrails
chown -R 1026:1026 /home/akitaonrails</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>With the commands above we change from 1000 to 1026 and fix the permissions on the local files to reflect this change. Now yes, we can edit <code>/etc/fstab</code> to mount the shared directories on the NAS with NFS:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># NFS
192.168.0.x:/volume1/GIGACHAD /mnt/gigachad nfs4 _netdev,noauto,nofail,bg,x-systemd.automount,x-systemd.requires=network-online.target,x-systemd.after=network-online.target,x-systemd.idle-timeout=60s,noatime,nodiratime,async,rsize=65536,wsize=65536,actimeo=1  0 0
192.168.0.x:/volume1/TERACHAD /mnt/terachad nfs4 _netdev,noauto,nofail,bg,x-systemd.automount,x-systemd.requires=network-online.target,x-systemd.after=network-online.target,x-systemd.idle-timeout=60s,noatime,nodiratime,async,rsize=65536,wsize=65536,actimeo=1  0 0</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Don&rsquo;t forget to create the mount points:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mkdir /mnt/gigachad
mkdir /mnt/terachad</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Done, just reboot and everything will work correctly. The file permissions on the NAS — which are UID 1026 and GID 1026 — will be accessible to the local user without problems.</p>
<h2>10 - Utilities<span class="hx:absolute hx:-mt-20" id="10---utilities"></span>
    <a href="#10---utilities" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Here are the last small tips to keep everything in one place. The first is to replace the <code>ls</code> command with <code>eza</code> which offers a nicer face, with icons and everything, like this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_16-50-25.png" alt="eza"  loading="lazy" /></p>
<p>Just do <code>yay -S eza</code> and add this to your <code>.zshrc</code> or <code>.bashrc</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>alias ls=&#39;eza --icons --color=auto --group-directories-first&#39;
alias ll=&#39;eza -l --icons --color=auto --group-directories-first&#39;
alias la=&#39;eza -la --icons --color=auto --group-directories-first&#39;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Second tip is to install <a href="https://atuin.sh/"target="_blank" rel="noopener">Atuin</a>. For anyone who deals with infra, this is extremely important: backup of all the command history you&rsquo;ve used in your terminal to this day.</p>
<p>Install with <code>yay -S atuin</code> and do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>atuin login
atuin sync</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Read the official site, but first you need to create an account and a secret encryption key — yes, you can run it locally on a server at your house, but their cloud option is pretty convenient and secure because it&rsquo;s all encrypted, so there&rsquo;s no problem if you upload passwords from your history.</p>
<p>Just put this in your <code>.zshrc</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>export ATUIN_NOBIND=true
eval &#34;$(atuin init zsh)&#34;
bindkey &#34;^R&#34; atuin-search

# incremental history search with arrow keys
bindkey &#34;^[[A&#34; history-beginning-search-backward
bindkey &#34;^[[B&#34; history-beginning-search-forward]]</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>If you&rsquo;re using ZSH, its autocomplete will work normally but, if you type CTRL+R, it will open the Atuin history. It will save all your commands in a SQLite and sync. That way you can sync several machines and servers you use day-to-day or, as in my case, recover the whole history on a new install.</p>
<p>And to make it better, we can configure it to auto-sync:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mkdir -p ~/.config/atuin
sed -i &#39;s/^#\?auto_sync.*/auto_sync = true/; s/^#\?sync_frequency.*/sync_frequency = &#34;30m&#34;/&#39; ~/.config/atuin/config.toml 2&gt;/dev/null || \
printf &#39;auto_sync = true\nsync_frequency = &#34;30m&#34;\n&#39; &gt;&gt; ~/.config/atuin/config.toml</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Third tip: since we have Docker already installed and configured, I like to use <a href="https://github.com/tbckr/sgpt"target="_blank" rel="noopener"><strong>SGPT</strong></a> on the command line for when I want to remember the syntax of some command I rarely use, instead of opening an app or browser. Just add this to my <code>.zshrc</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>export OPENROUTER_API_KEY=&#34;sk-or-v1-8b8...17&#34;
export OPENAI_API_KEY=&#34;sk-or-v1-db...f3&#34;
export OPENAI_API_BASE=&#34;https://openrouter.ai/api/v1&#34;

alias sgpt=&#39;docker run --rm -e OPENAI_API_BASE=${OPENAI_API_BASE} -e OPENAI_API_KEY=${OPENROUTER_API_KEY} ghcr.io/tbckr/sgpt:latest txt &#39;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>I use <a href="https://openrouter.ai/"target="_blank" rel="noopener">OpenRouter</a> to have access to different commercial LLMs in a single API, but you can just use your OpenAI key. With this done, I&rsquo;d use it in the terminal similar to this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>~ ❯ sgpt &#34;what is the pacman command in Arch to find out if a particular file came from an installed package?&#34;
To find out if a particular file came from an installed package in Arch Linux, you can use the following pacman command:

``bash
pacman -Qo /path/to/file
``

Replace &#34;/path/to/file&#34; with the actual path to the file you want to check. This command will show the package which owns the file, or report that the file does not belong to any package if that is the case.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>If you want, it supports sessions, so it&rsquo;ll remember the conversation on each new call. Read the project&rsquo;s Github.</p>
<p>Since I backed up my old <code>~/.config</code>, things like open tabs from my Brave browser opened normally in this new Omarchy install and I can keep doing my things exactly from the point I left off. I didn&rsquo;t lose anything. The same goes for all the other apps like emails that I back up locally via Thunderbird. I don&rsquo;t need to have it all downloaded again.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_15-59-34.png" alt="Audio Output"  loading="lazy" /></p>
<p>If, like me, you have several audio output devices, it&rsquo;s possible that right after finishing the install you&rsquo;ll notice that it seems there&rsquo;s no sound. But that&rsquo;s not a problem because it may just be that the output is pointing to the wrong device or the wrong profile. Use SUPER ALT SPACE to open the Omarchy menu, choose SETUP and AUDIO to open that program which is text mode (I like text mode programs).</p>
<p>In my case, I use a Topping D70 DAC to process my audio and the &ldquo;Default&rdquo; profile doesn&rsquo;t work but switching to &ldquo;Pro Audio&rdquo; worked. Also be careful that there&rsquo;s a global volume setting and individual volume control per program. Overall, thanks to Pipewire, everything should work normally.</p>
<p>I tested apps like Discord, Blender, Bambu Studio, VLC and everything worked perfectly. Only VLC needed codecs from a separate package:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -S extra/vlc-plugin-ffmpeg</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Blender too, the best is to install from this package:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -S blender-bin</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>If you use the normal &ldquo;blender&rdquo; package, some of its add-ons don&rsquo;t install right. I had this problem with MACHIN3Tools and MESHMachine, for example.</p>
<p>Speaking of Blender, it&rsquo;s an X11 app. Wayland runs X11 applications using an adapter layer called <strong>XWayland</strong>. I had several problems with menu and dialog box positioning, even adding <code>windowrule</code>. One thing that worked, I&rsquo;m not sure why, was installing Blender and Bambu Studio via Flatpak:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -S flatpak
flatpak install org.blender.Blender
flatpak install com.bambulab.BambuStudio</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Finally, if you&rsquo;re new to LazyVim, don&rsquo;t forget to run the <a href="https://www.lazyvim.org/extras"target="_blank" rel="noopener"><code>:LazyExtras</code></a> command to install extra plugins for your favorite languages, like LSP, Linter, syntax highlight, snippets, and more. Pure LazyVim is already complete but without support for many languages so as not to be unnecessarily heavy; you need to install them separately afterwards.</p>
<p>Also learn to use <a href="https://mise.jdx.dev/dev-tools/"target="_blank" rel="noopener"><strong>Mise-en-place</strong></a>. In the Omarchy menu there&rsquo;s an option to install support for various languages and it will use Mise, which is the correct way to manage your languages.</p>
<p>Mise is really easy, in general, from inside your project directory, just do something like:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mise use ruby@3.4.3</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>With this, only inside your project will Ruby 3.4.3 be available, but outside there can be another global version. Don&rsquo;t forget to activate it in your <code>.zshrc</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>eval &#34;$(mise activate zsh)&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h2>Impressions and Conclusion<span class="hx:absolute hx:-mt-20" id="impressions-and-conclusion"></span>
    <a href="#impressions-and-conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Compared to my old Manjaro, there&rsquo;s a lot I no longer need to configure. For example, because of the amount of space Docker and LLMs needed, I had configured an iSCSI drive on my NAS just for that. But since I freed up 2 TB of local NVME, I no longer need to deal with that, I just use the new BTRFS sub-volumes I created above.</p>
<p>I also no longer need to configure QEMU/Libvirt because I no longer need to use Windows alongside Linux side by side. I kept the NVME with the Windows partitions and I can dual boot to it if I really need something. Overall I never need it, so it&rsquo;s one less complicated configuration.</p>
<p>I spent months using apps via Flatpak, with the aim of being more secure and isolated from the system. It&rsquo;s like running apps via Docker. But it&rsquo;s too annoying. Being isolated means it doesn&rsquo;t obey system themes, and in the end I&rsquo;m forced to keep opening permissions for the apps. And if it&rsquo;s going to keep opening them, it&rsquo;s easier to install natively via <code>yay</code> and everything works perfectly.</p>
<p>I think that completes my notes of everything I needed to do to leave Omarchy exactly the way I like. As I said repeatedly, what works for me doesn&rsquo;t mean it&rsquo;ll work for you. For that there&rsquo;s documentation like <a href="https://wiki.archlinux.org/title/Main_page"target="_blank" rel="noopener">ArchWiki</a> which is &ldquo;THE&rdquo; best place to learn everything about Linux. Use and abuse that documentation.</p>
<p>As you can see, there wasn&rsquo;t all that much I customized in Omarchy, which is why I like it: it already did 95% of the path for me, I just had to touch a few adjustments to leave it the way I need. And now without all the junk that piled up in my old Manjaro. I recommend everyone install Omarchy as a first Linux and learn to actually use it.</p>
]]></content:encoded><category>omarchy</category><category>linux</category><category>storage-and-backup</category></item><item><title>Accessing Your NAS Using iSCSI Instead of SMB</title><link>https://akitaonrails.github.io/en/2025/04/24/accessing-your-nas-using-iscsi-instead-of-smb/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/04/24/accessing-your-nas-using-iscsi-instead-of-smb/</guid><pubDate>Thu, 24 Apr 2025 01:00:00 -0300</pubDate><description>&lt;p&gt;If you have a NAS or any kind of file server on your network, odds are your default move is to create a shared folder and mount it on your operating system, whether Windows, Mac, or Linux, using the old, tired SMB (Samba) protocol.&lt;/p&gt;
&lt;p&gt;On Linux, this is a massive pain in the ass, because the SMB protocol (built for Windows) doesn&amp;rsquo;t properly carry concepts like ownership (chown) or permissions (chmod). It doesn&amp;rsquo;t understand things like execute permission (everything is executable). For simple stuff like a Downloads, Videos, or Photos directory, it kind of doesn&amp;rsquo;t matter.&lt;/p&gt;</description><content:encoded><![CDATA[<p>If you have a NAS or any kind of file server on your network, odds are your default move is to create a shared folder and mount it on your operating system, whether Windows, Mac, or Linux, using the old, tired SMB (Samba) protocol.</p>
<p>On Linux, this is a massive pain in the ass, because the SMB protocol (built for Windows) doesn&rsquo;t properly carry concepts like ownership (chown) or permissions (chmod). It doesn&rsquo;t understand things like execute permission (everything is executable). For simple stuff like a Downloads, Videos, or Photos directory, it kind of doesn&rsquo;t matter.</p>
<p>But let&rsquo;s say you want to edit source code in projects with Git. It&rsquo;s going to be a nightmare, because SMB overrides the permissions regardless of what&rsquo;s underneath, and every single time it&rsquo;s going to conflict with Git, which will think files were modified (changing permissions counts as a change it tracks), and it&rsquo;ll keep nagging you to commit that, and that will pollute your whole repository (never use Git projects on SMB over the network).</p>
<p>Instead, I&rsquo;m on Linux, my NAS is Linux. The right call is to use a Linux protocol. That protocol is NFS and I showed how I set it up in <a href="https://www.akitaonrails.com/en/2025/04/17/configurando-meu-nas-synology-com-nfs-no-linux"target="_blank" rel="noopener">last week&rsquo;s post</a>.</p>
<p>Anyway, I&rsquo;m using NFS and I have way fewer headaches around file permissions. But then I hit another problem: if you saw my recent posts, you saw I&rsquo;ve been experimenting A LOT with Docker. Spinning up new images like there&rsquo;s no tomorrow. My local 2 TB NVMe filled up fast and started complaining about lack of space. So I thought, <em>&ldquo;Ah, I&rsquo;ll just move the Docker storage directory, /var/lib/docker, to a directory mounted via NFS on my NAS&rdquo;</em></p>
<p>Doing that is very easy:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo mkdir -p /mnt/nfs/docker
sudo chown root:docker /mnt/nfs/docker
sudo chmod 771       /mnt/nfs/docker</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>That creates a new directory inside my NAS and applies the correct permissions.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo systemctl stop docker
sudo mv /var/lib/docker /mnt/nfs/docker
# sudo rm -Rf /var/lib/docker (this will free everything up for me)
sudo ln -s /mnt/nfs/docker /var/lib/docker</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>That will free up all the occupied space. I don&rsquo;t need to worry about the images because I can rebuild everything from my Dockerfiles. And I don&rsquo;t need to worry about volumes because I had nothing important that wasn&rsquo;t mounted externally, in real directories. Rule: never store important things in Docker volumes. It&rsquo;s bad practice!</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># edit /etc/docker/daemon.json
{
  &#34;data-root&#34;: &#34;/mnt/nfs/docker&#34;
}

sudo systemctl start docker</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Done, this changes the configuration from <code>/var/lib/docker</code> to the new <code>/mnt/nfs/docker</code> and after restarting the service, it&rsquo;ll start writing everything there. So the post is over, right?</p>
<p>This is as far as most people go. But if you work with this setup for 2 minutes, you&rsquo;ll feel something is VERY wrong.</p>
<p>I tried building a new image. And it was taking ABSURDLY long. It turns out NFS is indeed faster than SMB, but both were built for file servers, not for operating systems. That makes a huge difference.</p>
<p>These protocols are <strong>FILE-BASED</strong>, every operation is based on files. And that&rsquo;s extremely inefficient.</p>
<p>If you watched my videos on file systems, you should already know they are <strong>BLOCK-LEVEL</strong>. At the filesystem level, you work with fixed-size blocks of bytes, organized in some variation of B-TREE structures (depending on the filesystem). Operations happen at the block level. Files are abstractions over collections of blocks.</p>
<p>When you run a <code>chmod -R</code>, which is a recursive permission change, inside a Dockerfile build, for example, even an empty tree will cause recursion on every entry inside, be it hidden files, ACLs, etc. And it needs to look up every single permission of everything in there. That costs network time (because NFS/SMB are network protocols).</p>
<p>That causes latency. Every permission on every file is an individual remote call, an RPC call. Even on the fastest network, like my 10Gbps one, even if each RPC is under 0.1 ms, it quickly becomes a bottleneck. And that&rsquo;s exactly what started happening.</p>
<p>I used <code>sudo nfsiostat 1 10</code> to check. It was showing this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>write: ops/s=2417  kB/s≈295 MiB/s  avg exe=106 ms  avg queue=102 ms</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>An average of 106 ms per RPC. That&rsquo;s an eternity. Average wait time in the queue of 102 ms. Even if I make just 100 RPC calls, it will cost more than 10 seconds in latency alone. And in an OS build, installing packages with thousands of files, that will literally cost an eternity. Running Docker build on NFS/SMB is simply not viable.</p>
<h3>iSCSI<span class="hx:absolute hx:-mt-20" id="iscsi"></span>
    <a href="#iscsi" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Obviously I&rsquo;m not the first to run into this problem and make the mistake of using a file-based protocol for services with high-volume file churn like the Docker cache.</p>
<p>The right move is to create an iSCSI virtual drive on my NAS. If you&rsquo;ve never heard of it, SCSI is an advanced protocol for drives. Cheap PCs in the 90s and 2000s used IDE/ATA, which is a simpler and cheaper protocol. But the first Macs and UNIX workstations from the 80s and 90s always used SCSI, which was vastly superior.</p>
<p>It was <strong>asynchronous</strong>, with queue control. You could send dozens of commands at once and the protocol was smart enough to reorder those commands for better efficiency (for example, skipping a file read if a delete command came before it). IDE was blocking, one command at a time. A single SCSI BUS could handle up to 16 devices. Parallel ATA was 2 per cable. Ridiculous. SCSI had advanced error recovery and reporting systems. It had &ldquo;enterprise&rdquo; features for hot-swap, power management, timeout behaviors, recovery, clustering, etc. And on top of all that it was agnostic to the transport medium, it could be fiber optic, SAS, or even iSCSI, which is SCSI over Ethernet, which is what we&rsquo;re going to use.</p>
<p>IDE/ATA had none of that. It was the protocol for poor-person PC disks. Real workstations, SUN, IRIX, Silicon Graphics, Macs, used SCSI.</p>
<p>More importantly, iSCSI is a block protocol, not a file protocol. Anyway, this varies by NAS manager, but in the case of Synology DSM, which is what I use, it has an application called SAN Manager, which lets you create an iSCSI drive:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/fraxoryhuszmp4cmr0ldc8h7hiip" alt="iSCSI create"  loading="lazy" /></p>
<p>Just follow the wizard, give it a name, choose the size, enable CHAP if you need extra security (I don&rsquo;t, I&rsquo;m on a controlled local network). That creates the iSCSI drive (think of it like a remote USB drive) and a LUN (Logical Unit Number). A LUN is simply one of many potential &ldquo;virtual disks&rdquo; that a target can present. A LUN is like a numbered slot in a target&rsquo;s storage array. A target is a disk server. On a SAN you can have several virtual disks and the way to organize that is with LUNs. In our case there&rsquo;s no real need to go deep on LUN details.</p>
<p>It takes a while, but when it finishes you just go back to my Manjaro Linux and run:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -S open-iscsi
sudo systemctl enable --now iscsi </code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>That brings up the service to start automatically on every boot. Now to discover my drive on the network:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code></code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m discovery -t sendtargets -p 192.168.0.xx
192.168.0.xx:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...
[2804:1b3:....:fe18:3f7d]:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...
[fe80::9209:....:3f7d]:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Every virtual drive has an <strong>IQN</strong> which is like a &ldquo;URL&rdquo; for the drive. Now we log in:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m node --login
Login to [iface: default, target: iqn.2000-01.com.synology:TERACHAD.docker.7af6e9116c1, portal: 192.168.0.21,3260] successful.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Since I didn&rsquo;t enable CHAP it doesn&rsquo;t ask for a password or anything (in a company it should be enabled, obviously). And done. When you do this I hear on GNOME the same little sound as when you plug a pen drive into the PC and it automatically shows up as a disk. If you run <code>lsblk</code>, it&rsquo;ll show up as a normal <code>/dev/sdX</code> drive. In the GNOME Disks app (or in Windows Disk Management) it really does show up as if it were any other hard drive or SSD. It doesn&rsquo;t behave like a shared folder, it behaves like a real drive. You even have to format it, just like a new pen drive.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/nvgrl3bov311vl4h251dcj1wvchz" alt="Disks"  loading="lazy" /></p>
<p>The formatting itself takes quite a while, because in the process it has to check and map block by block. Since it&rsquo;s over the network, it&rsquo;s slower than on a real drive, obviously. But it&rsquo;s a one-time thing. Even on my 10Gbps network it&rsquo;ll format at around 120 MB/s, and on a 2TB drive that comes out to around 2 hours for a fast format. I forgot you could format with &ldquo;lazy&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mkfs.ext4 -v -E lazy_itable_init=1,lazy_journal_init=1 /dev/sdX</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Leaving that for next time, now I wait 2 hours. To make sure it mounts automatically at boot, you need to check its <code>node.startup</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m node -o show | grep -E &#39;Target:|node.startup&#39;

node.startup = manual
node.startup = manual
node.startup = manual</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It&rsquo;s set to &ldquo;manual&rdquo;, to switch it to &ldquo;automatic&rdquo; you do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m node \
  -T iqn.2000-01.com.synology:TERACHAD.docker.7af6... \
  -o update -n node.startup -v automatic</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>With the iSCSI daemon running and after logging in, besides <code>/dev/sda</code>, if you run <code>ls /dev/disk/by-path</code> you&rsquo;ll find something like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ip-192.168.0.xx:3260-iscsi-iqn.2000-01.com.synology:TERACHAD.docker.7af6exxxxx-lun-1 -&gt; ../../sdX</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Since I&rsquo;m using the AUTOFS service and it &ldquo;steals&rdquo; my <code>/mnt</code> directory, I&rsquo;m going to mount the iSCSI drive via fstab at <code>/media/docker</code> by adding this line at the end of <code>/etc/fstab</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
/dev/disk/by-path/ip-192.168.0.xx:3260-iscsi-iqn.2000-01.com.synology:TERACHAD.docker.7af6e9xxxx-lun-1  /media/docker  ext4      _netdev,nofail,x-systemd.automount,x-systemd.requires=iscsid.service,noatime,nodiratime,commit=60  0 2</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Then just reload and mount:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo systemctl daemon-reload
sudo mount -a</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And that&rsquo;s it. From now on I have a &ldquo;virtual drive&rdquo; on my remote NAS. Now, let&rsquo;s switch Docker over to start using it. I can reconfigure my Docker to cache images and volumes and everything else directly on that drive. Just edit the <code>/etc/docker/daemon.json</code> file with the mount point:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>{
  &#34;data-root&#34;: &#34;/media/docker/docker&#34;
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Don&rsquo;t forget to create the directory on the new drive with the right permissions (and it does accept permissions, because it&rsquo;s a drive formatted with ext4 like any other):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo mkdir -p /media/docker/docker
sudo chown root:docker /media/docker/docker
sudo chmod 771 /media/docker/docker</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Finally, just restart the Docker service, wipe out <code>/usr/lib/docker</code> to free up space on my main NVMe, and done. It should be WAY faster than NFS, though not as fast as a local disk, of course. Even at 10Gbps, the network introduces latency, there&rsquo;s no way around it. But the heaviest part is really the build process, which has a lot of writes. For loading an already-built image, it should be blazing fast. But at least by using a block protocol instead of a file protocol, latency should drop by at least an order of 10x or more.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xh40fu275xx9q6os00m6j5gyr9w9" alt="docker build"  loading="lazy" /></p>
<p>And is it really faster? <strong>YES</strong>. In this screenshot of the docker build running, on NFS the times on the right were HUNDREDS OF SECONDS for many commands. Now they&rsquo;re back below 1 second like they should be. For all intents and purposes, I&rsquo;m not feeling any difference from what a USB 3.2 Gen 2 external HD would give me.</p>
<p>The MASSIVE difference that a BLOCK-LEVEL virtual drive makes, compared to a FILE-LEVEL NFS/SMB protocol, is brutal, the protocol is gigantic overhead. Incidentally, this is more or less how it works when you go to AWS and hire EBS storage (Elastic BLOCK Store), that&rsquo;s why it&rsquo;s called &ldquo;BLOCK&rdquo;, because it&rsquo;s a BLOCK protocol like iSCSI and not NFS. I hope you learned how much of a difference it makes to understand how protocols and filesystems work. If you haven&rsquo;t watched my videos, there&rsquo;s <a href="https://www.youtube.com/watch?v=lxjBgxmDZAI&amp;list=PLdsnXVqbHDUcM0LTAxqrVrTy6Q7jQprjt&amp;pp=gAQB"target="_blank" rel="noopener">this playlist</a> that explains BLOCKS in detail.</p>
<p>But yes, iSCSI has downsides: being a drive, it cannot, or should not, be shared (think of two PCs sharing the same pen drive, it would be a huge sh*tshow). Block devices were built to be used by a single device. Precisely for sharing, that&rsquo;s why protocols like SMB or NFS exist: to manage concurrent access to the same file over the network.</p>
<p>Another downside: the virtual drive is one big BLOB of bits. Without &ldquo;mounting&rdquo; it, there&rsquo;s no way to see the files. On the NAS server, it doesn&rsquo;t show the files. It&rsquo;s like a VirtualBox .vhd file. You need to &ldquo;turn it on&rdquo; and &ldquo;mount&rdquo; it on the &ldquo;PC&rdquo;. That&rsquo;s why I&rsquo;m still going to use NFS for the usual stuff, like Videos or Downloads. And the virtual drive is exclusively for the Docker cache, in this case. That works well. No other PC accesses it. If I had a second Linux machine that needed a Docker cache, I&rsquo;d have to create a second LUN just for it. Remember, it&rsquo;s a drive. Virtual or not, it behaves like a real drive to your system.</p>
]]></content:encoded><category>homelab</category><category>storage-and-backup</category><category>performance</category></item></channel></rss>